feature/IO-3255-simplified-parts-management - DO NOT SPLIT LABOR / PARTS

This commit is contained in:
Dave
2025-08-21 18:35:10 -04:00
parent 2c8f3a173e
commit 364813193f
2 changed files with 132 additions and 186 deletions

View File

@@ -56,11 +56,10 @@ const extractUpdatedJobData = (rq) => {
};
/**
* Extracts updated job lines from the request payload, mirroring the AddRq splitting rules:
* - PART lines carry only part pricing (act_price) and related fields
* - If LaborInfo exists on a part line, add a separate LABOR line at unq_seq + 400000
* - If RefinishLaborInfo exists, add a separate LABOR line at unq_seq + 500000 with mod_lbr_ty=LAR
* - SUBLET lines become PAS part_type with act_price=SubletAmount
* Extracts updated job lines from the request payload without splitting parts and labor:
* - Keep part and labor on the same jobline
* - Aggregate RefinishLabor into secondary labor fields and add its amount to lbr_amt
* - SUBLET-only lines become PAS part_type with act_price = SubletAmount
*/
const extractUpdatedJobLines = (addsChgs = {}, jobId) => {
const linesIn = Array.isArray(addsChgs.DamageLineInfo) ? addsChgs.DamageLineInfo : [addsChgs.DamageLineInfo || {}];
@@ -88,74 +87,74 @@ const extractUpdatedJobLines = (addsChgs = {}, jobId) => {
manual_line: line.ManualLineInd !== undefined ? coerceManual(line.ManualLineInd) : null
};
const lineOut = { ...base };
const hasPart = Object.keys(partInfo).length > 0;
const hasLaborOnly = Object.keys(laborInfo).length > 0 && !hasPart && Object.keys(subletInfo).length === 0;
const hasSublet = Object.keys(subletInfo).length > 0;
if (hasPart) {
const price = parseFloat(partInfo.PartPrice || partInfo.ListPrice || 0);
out.push({
...base,
part_type: partInfo.PartType ? String(partInfo.PartType).toUpperCase() : null,
part_qty: parseFloat(partInfo.Quantity || 0) || 1,
oem_partno: partInfo.OEMPartNum || partInfo.PartNum || null,
db_price: isNaN(price) ? 0 : price,
act_price: isNaN(price) ? 0 : price
});
lineOut.part_type = partInfo.PartType ? String(partInfo.PartType).toUpperCase() : null;
lineOut.part_qty = parseFloat(partInfo.Quantity || 0) || 1;
lineOut.oem_partno = partInfo.OEMPartNum || partInfo.PartNum || null;
lineOut.db_price = isNaN(price) ? 0 : price;
lineOut.act_price = isNaN(price) ? 0 : price;
// Split any attached labor on the part line into a derived labor jobline
const hrs = parseFloat(laborInfo.LaborHours || 0);
const amt = parseFloat(laborInfo.LaborAmt || 0);
const hasLabor =
(!!laborInfo.LaborType && String(laborInfo.LaborType).length > 0) ||
(!isNaN(hrs) && hrs !== 0) ||
(!isNaN(amt) && amt !== 0);
if (hasLabor) {
out.push({
...base,
unq_seq: (parseInt(line.UniqueSequenceNum || 0, 10) || 0) + 400000,
mod_lbr_ty: laborInfo.LaborType || null,
mod_lb_hrs: isNaN(hrs) ? 0 : hrs,
lbr_op: laborInfo.LaborOperation || null,
lbr_amt: isNaN(amt) ? 0 : amt
});
// Optional: taxability flag for parts
if (
partInfo.TaxableInd !== undefined &&
(typeof partInfo.TaxableInd === "string" ||
typeof partInfo.TaxableInd === "number" ||
typeof partInfo.TaxableInd === "boolean")
) {
lineOut.tax_part =
partInfo.TaxableInd === true ||
partInfo.TaxableInd === 1 ||
partInfo.TaxableInd === "1" ||
(typeof partInfo.TaxableInd === "string" && partInfo.TaxableInd.toUpperCase() === "Y");
}
} else if (hasSublet) {
out.push({
...base,
part_type: "PAS",
part_qty: 1,
act_price: parseFloat(subletInfo.SubletAmount || 0) || 0
});
const amt = parseFloat(subletInfo.SubletAmount || 0);
lineOut.part_type = "PAS";
lineOut.part_qty = 1;
lineOut.act_price = isNaN(amt) ? 0 : amt;
}
// Labor-only line (no PartInfo): still upsert as a labor entry
if (hasLaborOnly) {
out.push({
...base,
mod_lbr_ty: laborInfo.LaborType || null,
mod_lb_hrs: parseFloat(laborInfo.LaborHours || 0) || 0,
lbr_op: laborInfo.LaborOperation || null,
lbr_amt: parseFloat(laborInfo.LaborAmt || 0) || 0
});
// Primary labor on same line
const hrs = parseFloat(laborInfo.LaborHours || 0);
const amt = parseFloat(laborInfo.LaborAmt || 0);
const hasLabor =
(!!laborInfo.LaborType && String(laborInfo.LaborType).length > 0) ||
(!isNaN(hrs) && hrs !== 0) ||
(!isNaN(amt) && amt !== 0);
if (hasLabor) {
lineOut.mod_lbr_ty = laborInfo.LaborType || null;
lineOut.mod_lb_hrs = isNaN(hrs) ? 0 : hrs;
lineOut.lbr_op = laborInfo.LaborOperation || null;
lineOut.lbr_amt = isNaN(amt) ? 0 : amt;
}
// Separate refinish labor line
if (Object.keys(refinishInfo).length > 0) {
const rHrs = parseFloat(refinishInfo.LaborHours || 0);
const rAmt = parseFloat(refinishInfo.LaborAmt || 0);
if (!isNaN(rHrs) || !isNaN(rAmt)) {
out.push({
...base,
unq_seq: (parseInt(line.UniqueSequenceNum || 0, 10) || 0) + 500000,
line_desc: base.line_desc || "Refinish",
mod_lbr_ty: "LAR",
mod_lb_hrs: isNaN(rHrs) ? 0 : rHrs,
lbr_op: refinishInfo.LaborOperation || null,
lbr_amt: isNaN(rAmt) ? 0 : rAmt
});
// Refinish labor on same line using secondary fields; aggregate amount into lbr_amt
const rHrs = parseFloat(refinishInfo.LaborHours || 0);
const rAmt = parseFloat(refinishInfo.LaborAmt || 0);
const hasRefinish =
Object.keys(refinishInfo).length > 0 &&
((refinishInfo.LaborType && String(refinishInfo.LaborType).length > 0) ||
!isNaN(rHrs) ||
!isNaN(rAmt) ||
!!refinishInfo.LaborOperation);
if (hasRefinish) {
lineOut.lbr_typ_j = refinishInfo.LaborType || "LAR";
lineOut.lbr_hrs_j = isNaN(rHrs) ? 0 : rHrs;
lineOut.lbr_op_j = refinishInfo.LaborOperation || null;
if (!isNaN(rAmt)) {
lineOut.lbr_amt = (Number.isFinite(lineOut.lbr_amt) ? lineOut.lbr_amt : 0) + rAmt;
}
if (refinishInfo.PaintStagesNum !== undefined) lineOut.paint_stg = refinishInfo.PaintStagesNum;
if (refinishInfo.PaintTonesNum !== undefined) lineOut.paint_tone = refinishInfo.PaintTonesNum;
}
out.push(lineOut);
}
return out;