diff --git a/client/src/components/jobs-close-lines/jobs-close-lines.component.jsx b/client/src/components/jobs-close-lines/jobs-close-lines.component.jsx index f662efe0f..bbb3f978e 100644 --- a/client/src/components/jobs-close-lines/jobs-close-lines.component.jsx +++ b/client/src/components/jobs-close-lines/jobs-close-lines.component.jsx @@ -205,7 +205,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseLines); const HasBeenConvertedTolabor = ({ value }) => { const { t } = useTranslation(); - console.log(value); + if (!value) return null; return ( diff --git a/server/accounting/qb-receivables-lines.js b/server/accounting/qb-receivables-lines.js index 221db1250..b4e39a0d6 100644 --- a/server/accounting/qb-receivables-lines.js +++ b/server/accounting/qb-receivables-lines.js @@ -1,6 +1,7 @@ const DineroQbFormat = require("./accounting-constants").DineroQbFormat; const Dinero = require("dinero.js"); +const { DiscountNotAlreadyCounted } = require("../job/job-totals"); const logger = require("../utils/logger"); exports.default = function ({ @@ -37,23 +38,22 @@ exports.default = function ({ amount: Math.round((jobline.act_price || 0) * 100), }).multiply(jobline.part_qty || 1); - if ( - (jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0) || - ((jobline.db_ref === "900511" || - jobline.db_ref === "900510" || - jobline.db_ref === "900500") && - jobline.prt_dsmk_m && - jobline.prt_dsmk_m !== 0) - ) { - // console.log("Have a part discount", jobline); - DineroAmount = DineroAmount.add( - jobline.prt_dsmk_m && jobline.prt_dsmk_m !== 0 + // console.log("Have a part discount", jobline); + DineroAmount = DineroAmount.add( + ((jobline.prt_dsmk_m && jobline.prt_dsmk_m !== 0) || + (jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0)) && + DiscountNotAlreadyCounted(jobline, jobs_by_pk.joblines) + ? jobline.prt_dsmk_m ? Dinero({ amount: Math.round(jobline.prt_dsmk_m * 100) }) - : DineroAmount.percentage( - Math.abs(jobline.prt_dsmk_p || 0) - ).multiply(jobline.prt_dsmk_p > 0 ? 1 : -1) - ); - } + : Dinero({ + amount: Math.round(jobline.act_price * 100), + }) + .multiply(jobline.part_qty || 0) + .percentage(Math.abs(jobline.prt_dsmk_p || 0)) + .multiply(jobline.prt_dsmk_p > 0 ? 1 : -1) + : Dinero() + ); + const account = responsibilityCenters.profits.find( (i) => jobline.profitcenter_part.toLowerCase() === i.name.toLowerCase() ); @@ -82,7 +82,11 @@ exports.default = function ({ state: jobs_by_pk.state_tax_rate === 0 ? false - : jobline.db_ref === "900511" || jobline.db_ref === "900510" + : jobline.db_ref === "900511" || + jobline.db_ref === "900510" || + (jobline.mod_lb_hrs === 0 && //Extending IO-1375 as a part of IO-2023 + jobline.act_price > 0 && + jobline.lbr_op === "OP14") ? true : jobline.tax_part, }, diff --git a/server/cdk/cdk-calculate-allocations.js b/server/cdk/cdk-calculate-allocations.js index bbafa4646..88cd5bc55 100644 --- a/server/cdk/cdk-calculate-allocations.js +++ b/server/cdk/cdk-calculate-allocations.js @@ -12,6 +12,7 @@ const CdkBase = require("../web-sockets/web-socket"); const Dinero = require("dinero.js"); const _ = require("lodash"); +const { DiscountNotAlreadyCounted } = require("../job/job-totals"); exports.default = async function (socket, jobid) { try { @@ -70,23 +71,20 @@ exports.default = async function (socket, jobid) { amount: Math.round(val.act_price * 100), }).multiply(val.part_qty || 1); - if ( - (val.prt_dsmk_p && val.prt_dsmk_p !== 0) || - ((val.db_ref === "900511" || - val.db_ref === "900510" || - val.db_ref === "900500") && - val.prt_dsmk_m && - val.prt_dsmk_m !== 0) - ) { - // console.log("Have a part discount", val); - DineroAmount = DineroAmount.add( - val.prt_dsmk_m && val.prt_dsmk_m !== 0 + DineroAmount = DineroAmount.add( + ((val.prt_dsmk_m && val.prt_dsmk_m !== 0) || + (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) && + DiscountNotAlreadyCounted(val, job.joblines) + ? val.prt_dsmk_m ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) - : DineroAmount.percentage(Math.abs(val.prt_dsmk_p || 0)).multiply( - val.prt_dsmk_p > 0 ? 1 : -1 - ) - ); - } + : Dinero({ + amount: Math.round(val.act_price * 100), + }) + .multiply(val.part_qty || 0) + .percentage(Math.abs(val.prt_dsmk_p || 0)) + .multiply(val.prt_dsmk_p > 0 ? 1 : -1) + : Dinero() + ); acc[val.profitcenter_part] = acc[val.profitcenter_part].add(DineroAmount); diff --git a/server/cdk/cdk-job-export.js b/server/cdk/cdk-job-export.js index 92d4fb018..e1b12f5b4 100644 --- a/server/cdk/cdk-job-export.js +++ b/server/cdk/cdk-job-export.js @@ -431,7 +431,7 @@ async function QueryDmsCustomerById(socket, JobData, CustomerId) { async function QueryDmsCustomerByName(socket, JobData) { const ownerName = ( - JobData.ownr_co_nm + JobData.ownr_co_nm && JobData.ownr_co_nm !== "" ? JobData.ownr_co_nm : `${JobData.ownr_ln},${JobData.ownr_fn}` ).replace(replaceSpecialRegex, ""); @@ -725,7 +725,7 @@ async function InsertDmsVehicle(socket) { manufacturer: {}, vehicle: { deliveryDate: moment() - // .tz(socket.JobData.bodyshop.timezone) + // .tz(socket.JobData.bodyshop.timezone) .format("YYYYMMDD"), licensePlateNo: socket.JobData.plate_no, make: socket.txEnvelope.dms_make, @@ -854,7 +854,7 @@ async function UpdateDmsVehicle(socket) { socket.DMSVeh.dealer.inServiceDate || socket.txEnvelope.inservicedate ) - // .tz(socket.JobData.bodyshop.timezone) + // .tz(socket.JobData.bodyshop.timezone) .toISOString(), }), }, diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index b32f837df..f16babd8b 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -209,6 +209,9 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) { prt_dsmk_p prt_dsmk_m tax_part + line_ref + unq_seq + lbr_op } } bodyshops(where: {associations: {active: {_eq: true}}}) { @@ -402,6 +405,8 @@ query QUERY_JOBS_FOR_PBS_EXPORT($id: uuid!) { profitcenter_part db_ref prt_dsmk_p + unq_seq + line_ref } } @@ -1112,6 +1117,7 @@ exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid! joblines(where: { removed: { _eq: false } }) { id db_ref + line_ref unq_seq line_ind tax_part @@ -1220,6 +1226,7 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT id db_ref unq_seq + line_ref line_ind tax_part line_desc @@ -1443,7 +1450,8 @@ exports.GET_CDK_ALLOCATIONS = `query QUERY_JOB_CLOSE_DETAILS($id: uuid!) { op_code_desc profitcenter_labor profitcenter_part - prt_dsmk_p + line_ref + unq_seq } } } diff --git a/server/job/job-costing.js b/server/job/job-costing.js index 19cd70f0a..9f3baad16 100644 --- a/server/job/job-costing.js +++ b/server/job/job-costing.js @@ -4,6 +4,7 @@ const queries = require("../graphql-client/queries"); const _ = require("lodash"); const GraphQLClient = require("graphql-request").GraphQLClient; const logger = require("../utils/logger"); +const { DiscountNotAlreadyCounted } = require("./job-totals"); // Dinero.defaultCurrency = "USD"; // Dinero.globalLocale = "en-CA"; Dinero.globalRoundingMode = "HALF_EVEN"; @@ -308,7 +309,8 @@ function GenerateCostingData(job) { job.bodyshop.md_responsibility_centers.defaults.profits; const allCenters = _.union( job.bodyshop.md_responsibility_centers.profits.map((p) => p.name), - job.bodyshop.md_responsibility_centers.costs.map((p) => p.name) + job.bodyshop.md_responsibility_centers.costs.map((p) => p.name), + ["Unknown"] ); const materialsHours = { mapaHrs: 0, mashHrs: 0 }; @@ -330,12 +332,15 @@ function GenerateCostingData(job) { } if (val.mod_lbr_ty) { const laborProfitCenter = - val.profitcenter_labor || defaultProfits[val.mod_lbr_ty] || "?"; + val.profitcenter_labor || + defaultProfits[val.mod_lbr_ty] || + "Unknown"; - if (laborProfitCenter === "?") + if (laborProfitCenter === "Unknown") console.log("Unknown type", val.line_desc, val.mod_lbr_ty); const rateName = `rate_${(val.mod_lbr_ty || "").toLowerCase()}`; + const laborAmount = Dinero({ amount: Math.round((job[rateName] || 0) * 100), }).multiply(val.mod_lb_hrs || 0); @@ -344,6 +349,19 @@ function GenerateCostingData(job) { acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add(laborAmount); + if ( + val.mod_lb_hrs === 0 && + val.act_price > 0 && + val.lbr_op === "OP14" + ) { + //Scenario where SGI may pay out hours using a part price. + acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add( + Dinero({ + amount: Math.round((val.act_price || 0) * 100), + }).multiply(val.part_qty) + ); + } + if (val.mod_lbr_ty === "LAR") { materialsHours.mapaHrs += val.mod_lb_hrs || 0; } @@ -359,9 +377,9 @@ function GenerateCostingData(job) { val.part_type !== "PASL" ) { const partsProfitCenter = - val.profitcenter_part || defaultProfits[val.part_type] || "?"; + val.profitcenter_part || defaultProfits[val.part_type] || "Unknown"; - if (partsProfitCenter === "?") + if (partsProfitCenter === "Unknown") console.log("Unknown type", val.line_desc, val.part_type); if (!partsProfitCenter) @@ -375,14 +393,18 @@ function GenerateCostingData(job) { }) .multiply(val.part_qty || 1) .add( - val.prt_dsmk_m && val.prt_dsmk_m !== 0 - ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) - : Dinero({ - amount: Math.round(val.act_price * 100), - }) - .multiply(val.part_qty || 0) - .percentage(Math.abs(val.prt_dsmk_p || 0)) - .multiply(val.prt_dsmk_p > 0 ? 1 : -1) + ((val.prt_dsmk_m && val.prt_dsmk_m !== 0) || + (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) && + DiscountNotAlreadyCounted(val, job.joblines) + ? val.prt_dsmk_m + ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(val.act_price * 100), + }) + .multiply(val.part_qty || 0) + .percentage(Math.abs(val.prt_dsmk_p || 0)) + .multiply(val.prt_dsmk_p > 0 ? 1 : -1) + : Dinero() ); if (!acc.parts[partsProfitCenter]) acc.parts[partsProfitCenter] = Dinero(); @@ -395,9 +417,9 @@ function GenerateCostingData(job) { (val.part_type === "PAS" || val.part_type === "PASL") ) { const partsProfitCenter = - val.profitcenter_part || defaultProfits[val.part_type] || "?"; + val.profitcenter_part || defaultProfits[val.part_type] || "Unknown"; - if (partsProfitCenter === "?") + if (partsProfitCenter === "Unknown") console.log("Unknown type", val.line_desc, val.part_type); if (!partsProfitCenter) @@ -411,14 +433,18 @@ function GenerateCostingData(job) { }) .multiply(val.part_qty || 1) .add( - val.prt_dsmk_m && val.prt_dsmk_m !== 0 - ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) - : Dinero({ - amount: Math.round(val.act_price * 100), - }) - .multiply(val.part_qty || 0) - .percentage(Math.abs(val.prt_dsmk_p || 0)) - .multiply(val.prt_dsmk_p > 0 ? 1 : -1) + ((val.prt_dsmk_m && val.prt_dsmk_m !== 0) || + (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) && + DiscountNotAlreadyCounted(val, job.joblines) + ? val.prt_dsmk_m + ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(val.act_price * 100), + }) + .multiply(val.part_qty || 0) + .percentage(Math.abs(val.prt_dsmk_p || 0)) + .multiply(val.prt_dsmk_p > 0 ? 1 : -1) + : Dinero() ); if (!acc.sublet[partsProfitCenter]) acc.sublet[partsProfitCenter] = Dinero(); @@ -433,17 +459,20 @@ function GenerateCostingData(job) { const partsProfitCenter = val.profitcenter_part || getAdditionalCostCenter(val, defaultProfits) || - "?"; + "Unknown"; - if (partsProfitCenter === "?") { + if (partsProfitCenter === "Unknown") { console.log("Unknown type", val.line_desc, val.part_type); - } else { - const partsAmount = Dinero({ - amount: Math.round((val.act_price || 0) * 100), - }) - .multiply(val.part_qty || 1) - .add( - val.prt_dsmk_m && val.prt_dsmk_m !== 0 + } + const partsAmount = Dinero({ + amount: Math.round((val.act_price || 0) * 100), + }) + .multiply(val.part_qty || 1) + .add( + ((val.prt_dsmk_m && val.prt_dsmk_m !== 0) || + (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) && + DiscountNotAlreadyCounted(val, job.joblines) + ? val.prt_dsmk_m ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) : Dinero({ amount: Math.round(val.act_price * 100), @@ -451,13 +480,13 @@ function GenerateCostingData(job) { .multiply(val.part_qty || 0) .percentage(Math.abs(val.prt_dsmk_p || 0)) .multiply(val.prt_dsmk_p > 0 ? 1 : -1) - ); + : Dinero() + ); - if (!acc.additional[partsProfitCenter]) - acc.additional[partsProfitCenter] = Dinero(); - acc.additional[partsProfitCenter] = - acc.additional[partsProfitCenter].add(partsAmount); - } + if (!acc.additional[partsProfitCenter]) + acc.additional[partsProfitCenter] = Dinero(); + acc.additional[partsProfitCenter] = + acc.additional[partsProfitCenter].add(partsAmount); } return acc; diff --git a/server/job/job-totals.js b/server/job/job-totals.js index 79a2337e1..35246ec8c 100644 --- a/server/job/job-totals.js +++ b/server/job/job-totals.js @@ -362,28 +362,27 @@ function CalculateRatesTotals(ratesList) { } function CalculatePartsTotals(jobLines) { - const ret = jobLines - .filter((jl) => !jl.removed) - .reduce( - (acc, value) => { - switch (value.part_type) { - case "PAS": - case "PASL": - return { - ...acc, - sublets: { - ...acc.sublets, - subtotal: acc.sublets.subtotal.add( - Dinero({ - amount: Math.round(value.act_price * 100), - }) - .multiply(value.part_qty || 0) - .add( - (value.db_ref === "900511" || - value.db_ref === "900510" || - value.db_ref === "900500") && - value.prt_dsmk_m && - value.prt_dsmk_m !== 0 + const jl = jobLines.filter((jl) => !jl.removed); + + const ret = jl.reduce( + (acc, value) => { + switch (value.part_type) { + case "PAS": + case "PASL": + return { + ...acc, + sublets: { + ...acc.sublets, + subtotal: acc.sublets.subtotal.add( + Dinero({ + amount: Math.round(value.act_price * 100), + }) + .multiply(value.part_qty || 0) + .add( + ((value.prt_dsmk_m && value.prt_dsmk_m !== 0) || + (value.prt_dsmk_p && value.prt_dsmk_p !== 0)) && + DiscountNotAlreadyCounted(value, jl) + ? value.prt_dsmk_m ? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) }) : Dinero({ amount: Math.round(value.act_price * 100), @@ -391,28 +390,28 @@ function CalculatePartsTotals(jobLines) { .multiply(value.part_qty || 0) .percentage(Math.abs(value.prt_dsmk_p || 0)) .multiply(value.prt_dsmk_p > 0 ? 1 : -1) - ) - ), - }, - }; + : Dinero() + ) + ), + }, + }; - default: - if ( - !value.part_type && - value.db_ref !== "900510" && - value.db_ref !== "900511" - ) - return acc; - return { - ...acc, - parts: { - ...acc.parts, - prt_dsmk_total: acc.parts.prt_dsmk_total.add( - (value.db_ref === "900511" || - value.db_ref === "900510" || - value.db_ref === "900500") && - value.prt_dsmk_m && - value.prt_dsmk_m !== 0 + default: + if ( + !value.part_type && + value.db_ref !== "900510" && + value.db_ref !== "900511" + ) + return acc; + return { + ...acc, + parts: { + ...acc.parts, + prt_dsmk_total: acc.parts.prt_dsmk_total.add( + ((value.prt_dsmk_m && value.prt_dsmk_m !== 0) || + (value.prt_dsmk_p && value.prt_dsmk_p !== 0)) && + DiscountNotAlreadyCounted(value, jl) + ? value.prt_dsmk_m ? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) }) : Dinero({ amount: Math.round(value.act_price * 100), @@ -420,47 +419,45 @@ function CalculatePartsTotals(jobLines) { .multiply(value.part_qty || 0) .percentage(Math.abs(value.prt_dsmk_p || 0)) .multiply(value.prt_dsmk_p > 0 ? 1 : -1) - ), - ...(value.part_type - ? { - list: { - ...acc.parts.list, - [value.part_type]: - acc.parts.list[value.part_type] && - acc.parts.list[value.part_type].total - ? { - total: acc.parts.list[ - value.part_type - ].total.add( - Dinero({ - amount: Math.round( - (value.act_price || 0) * 100 - ), - }).multiply(value.part_qty || 0) - ), - } - : { - total: Dinero({ + : Dinero() + ), + ...(value.part_type + ? { + list: { + ...acc.parts.list, + [value.part_type]: + acc.parts.list[value.part_type] && + acc.parts.list[value.part_type].total + ? { + total: acc.parts.list[value.part_type].total.add( + Dinero({ amount: Math.round( (value.act_price || 0) * 100 ), - }).multiply(value.part_qty || 0), - }, - }, - } - : {}), - subtotal: acc.parts.subtotal - .add( - Dinero({ - amount: Math.round(value.act_price * 100), - }).multiply(value.part_qty || 0) - ) - .add( - (value.db_ref === "900511" || - value.db_ref === "900510" || - value.db_ref === "900500") && - value.prt_dsmk_m && - value.prt_dsmk_m !== 0 + }).multiply(value.part_qty || 0) + ), + } + : { + total: Dinero({ + amount: Math.round( + (value.act_price || 0) * 100 + ), + }).multiply(value.part_qty || 0), + }, + }, + } + : {}), + subtotal: acc.parts.subtotal + .add( + Dinero({ + amount: Math.round(value.act_price * 100), + }).multiply(value.part_qty || 0) + ) + .add( + ((value.prt_dsmk_m && value.prt_dsmk_m !== 0) || + (value.prt_dsmk_p && value.prt_dsmk_p !== 0)) && + DiscountNotAlreadyCounted(value, jl) + ? value.prt_dsmk_m ? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) }) : Dinero({ amount: Math.round(value.act_price * 100), @@ -468,25 +465,26 @@ function CalculatePartsTotals(jobLines) { .multiply(value.part_qty || 0) .percentage(Math.abs(value.prt_dsmk_p || 0)) .multiply(value.prt_dsmk_p > 0 ? 1 : -1) - ), - }, - }; - } - }, - { - parts: { - list: {}, - prt_dsmk_total: Dinero(), - subtotal: Dinero({ amount: 0 }), - total: Dinero({ amount: 0 }), - }, - sublets: { - subtotal: Dinero({ amount: 0 }), - - total: Dinero({ amount: 0 }), - }, + : Dinero() + ), + }, + }; } - ); + }, + { + parts: { + list: {}, + prt_dsmk_total: Dinero(), + subtotal: Dinero({ amount: 0 }), + total: Dinero({ amount: 0 }), + }, + sublets: { + subtotal: Dinero({ amount: 0 }), + + total: Dinero({ amount: 0 }), + }, + } + ); return { parts: { @@ -706,7 +704,16 @@ function CalculateTaxesTotals(job, otherTotals) { exports.default = Totals; function DiscountNotAlreadyCounted(jobline, joblines) { - if (jobline.db_ref !== "900510") return true; + if ( + //If it's not a discount line, then it definitely hasn't been counted yet. + jobline.db_ref !== "900510" && + jobline.db_ref !== "900511" + ) + return true; + const ParentLine = joblines.find((j) => j.unq_seq === jobline.line_ref); + return ParentLine && !(ParentLine.prt_dsmk_m && ParentLine.prt_dsmk_m !== 0); } + +exports.DiscountNotAlreadyCounted = DiscountNotAlreadyCounted;