From c6af2b34b272186f9a24fa6884e8cc76d795c820 Mon Sep 17 00:00:00 2001 From: Dave Date: Tue, 5 May 2026 13:41:50 -0400 Subject: [PATCH 1/2] feature/IO-3676-Order-As-In-House-Quantity - Fix --- .../job-detail-lines/job-lines.component.jsx | 12 ++----- .../job-lines.in-house-bill-lines.utils.js | 11 +++++++ ...ob-lines.in-house-bill-lines.utils.test.js | 33 +++++++++++++++++++ 3 files changed, 46 insertions(+), 10 deletions(-) create mode 100644 client/src/components/job-detail-lines/job-lines.in-house-bill-lines.utils.js create mode 100644 client/src/components/job-detail-lines/job-lines.in-house-bill-lines.utils.test.js diff --git a/client/src/components/job-detail-lines/job-lines.component.jsx b/client/src/components/job-detail-lines/job-lines.component.jsx index 16a3d6c87..7e3ce8b85 100644 --- a/client/src/components/job-detail-lines/job-lines.component.jsx +++ b/client/src/components/job-detail-lines/job-lines.component.jsx @@ -44,6 +44,7 @@ import JoblineTeamAssignment from "../job-line-team-assignment/job-line-team-ass import JobSendPartPriceChangeComponent from "../job-send-parts-price-change/job-send-parts-price-change.component"; import PartsOrderDrawer from "../parts-order-list-table/parts-order-list-table-drawer.component"; import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container"; +import { buildInHouseBillLines } from "./job-lines.in-house-bill-lines.utils"; import JobLinesExpander from "./job-lines-expander.component"; import JobLinesPartPriceChange from "./job-lines-part-price-change.component"; import JobLinesExpanderSimple from "./jobs-lines-expander-simple.component"; @@ -595,16 +596,7 @@ export function JobLinesComponent({ isinhouse: true, date: dayjs(), total: 0, - billlines: selectedLines.map((p) => ({ - joblineid: p.id, - actual_price: p.act_price, - actual_cost: 0, - line_desc: p.line_desc, - line_remarks: p.line_remarks, - part_type: p.part_type, - quantity: p.quantity || 1, - applicable_taxes: { local: false, state: false, federal: false } - })) + billlines: buildInHouseBillLines(selectedLines) } } }); diff --git a/client/src/components/job-detail-lines/job-lines.in-house-bill-lines.utils.js b/client/src/components/job-detail-lines/job-lines.in-house-bill-lines.utils.js new file mode 100644 index 000000000..8a75d1755 --- /dev/null +++ b/client/src/components/job-detail-lines/job-lines.in-house-bill-lines.utils.js @@ -0,0 +1,11 @@ +export const buildInHouseBillLines = (lines) => + lines.map((line) => ({ + joblineid: line.id, + actual_price: line.act_price, + actual_cost: 0, + line_desc: line.line_desc, + line_remarks: line.line_remarks, + part_type: line.part_type, + quantity: line.part_qty ?? line.quantity ?? 1, + applicable_taxes: { local: false, state: false, federal: false } + })); diff --git a/client/src/components/job-detail-lines/job-lines.in-house-bill-lines.utils.test.js b/client/src/components/job-detail-lines/job-lines.in-house-bill-lines.utils.test.js new file mode 100644 index 000000000..27f5f731b --- /dev/null +++ b/client/src/components/job-detail-lines/job-lines.in-house-bill-lines.utils.test.js @@ -0,0 +1,33 @@ +import { describe, expect, it } from "vitest"; +import { buildInHouseBillLines } from "./job-lines.in-house-bill-lines.utils"; + +describe("buildInHouseBillLines", () => { + it("carries job line part quantity into the in-house bill line", () => { + const billLines = buildInHouseBillLines([ + { + id: "job-line-1", + act_price: 125, + line_desc: "Door shell", + line_remarks: "Left", + part_type: "PAA", + part_qty: 3 + } + ]); + + expect(billLines[0]).toMatchObject({ + joblineid: "job-line-1", + actual_price: 125, + actual_cost: 0, + line_desc: "Door shell", + line_remarks: "Left", + part_type: "PAA", + quantity: 3, + applicable_taxes: { local: false, state: false, federal: false } + }); + }); + + it("falls back to legacy quantity and then one when part quantity is absent", () => { + expect(buildInHouseBillLines([{ id: "legacy", quantity: 2 }])[0].quantity).toBe(2); + expect(buildInHouseBillLines([{ id: "missing" }])[0].quantity).toBe(1); + }); +}); From 88ae1fb1cc4d7293647b0eec92a58e38b48030c3 Mon Sep 17 00:00:00 2001 From: Dave Date: Tue, 5 May 2026 13:56:16 -0400 Subject: [PATCH 2/2] feature/IO-3673-Order-Parts-Receive-Bill-Bug - Fix --- .../parts-order-modal.component.jsx | 3 ++ .../parts-order-modal.container.jsx | 50 ++++++++----------- .../parts-order-modal.utils.js | 23 +++++++++ .../parts-order-modal.utils.test.js | 32 ++++++++++++ 4 files changed, 78 insertions(+), 30 deletions(-) create mode 100644 client/src/components/parts-order-modal/parts-order-modal.utils.js create mode 100644 client/src/components/parts-order-modal/parts-order-modal.utils.test.js diff --git a/client/src/components/parts-order-modal/parts-order-modal.component.jsx b/client/src/components/parts-order-modal/parts-order-modal.component.jsx index 3766b2e7b..f04b5f7bc 100644 --- a/client/src/components/parts-order-modal/parts-order-modal.component.jsx +++ b/client/src/components/parts-order-modal/parts-order-modal.component.jsx @@ -176,6 +176,9 @@ export function PartsOrderModalComponent({ > + { - const originalLine = linesToOrder?.[index]; - const jobLineId = isReturn ? originalLine?.joblineid : originalLine?.id; - - return { - ...p, - job_line_id: jobLineId, - ...(isReturn && { cm_received: false }) - }; + const forcedLines = buildSubmittedPartsOrderLines({ + submittedLines, + linesToOrder, + isReturn }); let insertResult; @@ -147,10 +143,7 @@ export function PartsOrderModalContainer({ type: isReturn ? "jobspartsreturn" : "jobspartsorder" }); - // Use linesToOrder from context instead of form values to preserve job line ids - const jobLineIds = (linesToOrder ?? []) - .filter((line) => (isReturn ? line.joblineid : line.id)) - .map((line) => (isReturn ? line.joblineid : line.id)); + const jobLineIds = getSubmittedPartsOrderJobLineIds(forcedLines); try { const jobLinesResult = await updateJobLines({ @@ -206,23 +199,20 @@ export function PartsOrderModalContainer({ isinhouse: true, date: dayjs(), total: 0, - billlines: forcedLines.map((p, index) => { - const originalLine = linesToOrder?.[index]; - return { - joblineid: isReturn ? originalLine?.joblineid : originalLine?.id, - actual_price: p.act_price, - actual_cost: 0, // p.act_price, - line_desc: p.line_desc, - line_remarks: p.line_remarks, - part_type: p.part_type, - quantity: p.quantity || 1, - applicable_taxes: { - local: false, - state: false, - federal: false - } - }; - }) + billlines: forcedLines.map((p) => ({ + joblineid: p.job_line_id, + actual_price: p.act_price, + actual_cost: 0, // p.act_price, + line_desc: p.line_desc, + line_remarks: p.line_remarks, + part_type: p.part_type, + quantity: p.quantity || 1, + applicable_taxes: { + local: false, + state: false, + federal: false + } + })) } } }); diff --git a/client/src/components/parts-order-modal/parts-order-modal.utils.js b/client/src/components/parts-order-modal/parts-order-modal.utils.js new file mode 100644 index 000000000..1517704ce --- /dev/null +++ b/client/src/components/parts-order-modal/parts-order-modal.utils.js @@ -0,0 +1,23 @@ +export const getPartsOrderJobLineId = ({ line, originalLine, isReturn }) => { + return line?.job_line_id || (isReturn ? originalLine?.joblineid : originalLine?.id); +}; + +export const buildSubmittedPartsOrderLines = ({ submittedLines = [], linesToOrder = [], isReturn }) => { + return submittedLines.map((line, index) => { + const jobLineId = getPartsOrderJobLineId({ + line, + originalLine: linesToOrder?.[index], + isReturn + }); + + return { + ...line, + job_line_id: jobLineId, + ...(isReturn && { cm_received: false }) + }; + }); +}; + +export const getSubmittedPartsOrderJobLineIds = (partsOrderLines = []) => { + return partsOrderLines.map((line) => line.job_line_id).filter(Boolean); +}; diff --git a/client/src/components/parts-order-modal/parts-order-modal.utils.test.js b/client/src/components/parts-order-modal/parts-order-modal.utils.test.js new file mode 100644 index 000000000..1c7309680 --- /dev/null +++ b/client/src/components/parts-order-modal/parts-order-modal.utils.test.js @@ -0,0 +1,32 @@ +import { describe, expect, it } from "vitest"; +import { buildSubmittedPartsOrderLines, getSubmittedPartsOrderJobLineIds } from "./parts-order-modal.utils.js"; + +describe("parts order modal utilities", () => { + it("preserves submitted job line ids after a row is removed", () => { + const submittedLines = [ + { line_desc: "second line", job_line_id: "job-line-2" }, + { line_desc: "third line", job_line_id: "job-line-3" } + ]; + const linesToOrder = [{ id: "job-line-1" }, { id: "job-line-2" }, { id: "job-line-3" }]; + + const result = buildSubmittedPartsOrderLines({ submittedLines, linesToOrder, isReturn: false }); + + expect(result.map((line) => line.job_line_id)).toEqual(["job-line-2", "job-line-3"]); + expect(getSubmittedPartsOrderJobLineIds(result)).toEqual(["job-line-2", "job-line-3"]); + }); + + it("falls back to original return line ids when the form omits hidden metadata", () => { + const submittedLines = [{ line_desc: "return line" }]; + const linesToOrder = [{ joblineid: "return-job-line-1" }]; + + const result = buildSubmittedPartsOrderLines({ submittedLines, linesToOrder, isReturn: true }); + + expect(result).toEqual([ + { + line_desc: "return line", + job_line_id: "return-job-line-1", + cm_received: false + } + ]); + }); +});