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);
+ });
+});
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
+ }
+ ]);
+ });
+});