From b6dc7a4d925310b548e5e31baa51d6df936fe295 Mon Sep 17 00:00:00 2001 From: Dave Date: Mon, 25 Aug 2025 11:30:18 -0400 Subject: [PATCH 1/2] feature/IO-3255-simplified-parts-management - Notes are appended if they are not duplicates --- .../endpoints/vehicleDamageEstimateChgRq.js | 54 ++++++++++++--- .../partsManagement.queries.js | 65 ++++--------------- 2 files changed, 56 insertions(+), 63 deletions(-) diff --git a/server/integrations/partsManagement/endpoints/vehicleDamageEstimateChgRq.js b/server/integrations/partsManagement/endpoints/vehicleDamageEstimateChgRq.js index f2aafbd8d..947e0f3c7 100644 --- a/server/integrations/partsManagement/endpoints/vehicleDamageEstimateChgRq.js +++ b/server/integrations/partsManagement/endpoints/vehicleDamageEstimateChgRq.js @@ -9,7 +9,8 @@ const { GET_JOB_BY_CLAIM, UPDATE_JOB_BY_ID, SOFT_DELETE_JOBLINES_BY_IDS, - INSERT_JOBLINES + INSERT_JOBLINES, + GET_JOBLINES_NOTES_BY_JOBID_UNQSEQ } = require("../partsManagement.queries"); /** @@ -60,8 +61,9 @@ const extractUpdatedJobData = (rq) => { * - 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 + * Accepts currentJobLineNotes map for notes merging. */ -const extractUpdatedJobLines = (addsChgs = {}, jobId) => { +const extractUpdatedJobLines = (addsChgs = {}, jobId, currentJobLineNotes = {}) => { const linesIn = Array.isArray(addsChgs.DamageLineInfo) ? addsChgs.DamageLineInfo : [addsChgs.DamageLineInfo || {}]; const coerceManual = (val) => @@ -83,12 +85,33 @@ const extractUpdatedJobLines = (addsChgs = {}, jobId) => { unq_seq: parseInt(line.UniqueSequenceNum || 0, 10), status: line.LineStatusCode || null, line_desc: line.LineDesc || null, - notes: line.LineMemo || null, + // notes will be set below manual_line: line.ManualLineInd !== undefined ? coerceManual(line.ManualLineInd) : null }; const lineOut = { ...base }; + // --- Notes merge logic --- + const unqSeq = lineOut.unq_seq; + const currentNotes = currentJobLineNotes?.[unqSeq] || null; + const newNotes = line.LineMemo || null; + if (newNotes && currentNotes) { + if (currentNotes === newNotes) { + lineOut.notes = currentNotes; + } else if (currentNotes.includes(newNotes)) { + lineOut.notes = currentNotes; + } else { + lineOut.notes = `${currentNotes} | ${newNotes}`; + } + } else if (newNotes) { + lineOut.notes = newNotes; + } else if (currentNotes) { + lineOut.notes = currentNotes; + } else { + lineOut.notes = null; + } + // --- End notes merge logic --- + const hasPart = Object.keys(partInfo).length > 0; const hasSublet = Object.keys(subletInfo).length > 0; @@ -198,17 +221,30 @@ const partsManagementVehicleDamageEstimateChgRq = async (req, res) => { const job = await findJob(shopId, claimNum, logger); if (!job) return res.status(404).send("Job not found"); + // --- Get updated lines and their unq_seq --- + const linesIn = Array.isArray(rq.AddsChgs?.DamageLineInfo) + ? rq.AddsChgs.DamageLineInfo + : [rq.AddsChgs?.DamageLineInfo || {}]; + const updatedSeqs = Array.from( + new Set((linesIn || []).map((l) => parseInt(l?.UniqueSequenceNum || 0, 10)).filter((v) => Number.isInteger(v))) + ); + let currentJobLineNotes = {}; + if (updatedSeqs.length > 0) { + const resp = await client.request(GET_JOBLINES_NOTES_BY_JOBID_UNQSEQ, { jobid: job.id, unqSeqs: updatedSeqs }); + if (resp?.joblines) { + for (const jl of resp.joblines) { + currentJobLineNotes[jl.unq_seq] = jl.notes; + } + } + } + // --- End fetch current notes --- + const updatedJobData = extractUpdatedJobData(rq); - const updatedLines = extractUpdatedJobLines(rq.AddsChgs, job.id); + const updatedLines = extractUpdatedJobLines(rq.AddsChgs, job.id, currentJobLineNotes); const deletedLineIds = extractDeletions(rq.Deletions); await client.request(UPDATE_JOB_BY_ID, { id: job.id, job: updatedJobData }); - // Build a set of unq_seq that will be updated (replaced). We delete them first to avoid duplicates. - const updatedSeqs = Array.from( - new Set((updatedLines || []).map((l) => l?.unq_seq).filter((v) => Number.isInteger(v))) - ); - if (deletedLineIds?.length || updatedSeqs?.length) { const allToDelete = Array.from(new Set([...(deletedLineIds || []), ...(updatedSeqs || [])])); if (allToDelete.length) { diff --git a/server/integrations/partsManagement/partsManagement.queries.js b/server/integrations/partsManagement/partsManagement.queries.js index 3e682c4f8..e4baf8f28 100644 --- a/server/integrations/partsManagement/partsManagement.queries.js +++ b/server/integrations/partsManagement/partsManagement.queries.js @@ -52,56 +52,6 @@ const UPDATE_JOB_BY_ID = ` } `; -const UPSERT_JOBLINES = ` -mutation UpsertJoblines($joblines: [joblines_insert_input!]!) { - insert_joblines( - objects: $joblines - on_conflict: { - constraint: joblines_pkey - update_columns: [ - jobid - status - line_desc - part_type - part_qty - oem_partno - db_price - act_price - mod_lbr_ty - mod_lb_hrs - lbr_op - lbr_amt - notes - manual_line - ] - } - ) { - affected_rows - } -} -`; - -const DELETE_JOBLINES_BY_JOBID = ` - mutation DeleteJoblinesByJobId($jobid: uuid!) { - delete_joblines(where: { jobid: { _eq: $jobid } }) { - affected_rows - } - } -`; - -const DELETE_JOBLINES_BY_IDS = ` - mutation DeleteJoblinesByIds($jobid: uuid!, $unqSeqs: [Int!]!) { - delete_joblines( - where: { - jobid: { _eq: $jobid }, - unq_seq: { _in: $unqSeqs } - } - ) { - affected_rows - } - } -`; - // Soft delete joblines by marking removed=true instead of hard-deleting const SOFT_DELETE_JOBLINES_BY_IDS = ` mutation SoftDeleteJoblinesByIds($jobid: uuid!, $unqSeqs: [Int!]!) { @@ -245,6 +195,15 @@ const DELETE_AUDIT_TRAIL_BY_SHOP = ` } `; +const GET_JOBLINES_NOTES_BY_JOBID_UNQSEQ = ` + query GetJoblinesNotesByJobIdUnqSeq($jobid: uuid!, $unqSeqs: [Int!]!) { + joblines(where: { jobid: { _eq: $jobid }, unq_seq: { _in: $unqSeqs }, removed: { _neq: true } }) { + unq_seq + notes + } + } +`; + module.exports = { GET_BODYSHOP_STATUS, GET_VEHICLE_BY_SHOP_VIN, @@ -252,9 +211,6 @@ module.exports = { INSERT_JOB_WITH_LINES, GET_JOB_BY_CLAIM, UPDATE_JOB_BY_ID, - UPSERT_JOBLINES, - DELETE_JOBLINES_BY_JOBID, - DELETE_JOBLINES_BY_IDS, SOFT_DELETE_JOBLINES_BY_IDS, INSERT_JOBLINES, CHECK_EXTERNAL_SHOP_ID, @@ -271,5 +227,6 @@ module.exports = { GET_JOBS_BY_SHOP, DELETE_JOBLINES_BY_JOB_IDS, DELETE_JOBS_BY_IDS, - DELETE_AUDIT_TRAIL_BY_SHOP + DELETE_AUDIT_TRAIL_BY_SHOP, + GET_JOBLINES_NOTES_BY_JOBID_UNQSEQ }; From e2f17583780ba63b34c8d72fc83157519be97bbf Mon Sep 17 00:00:00 2001 From: Dave Date: Mon, 25 Aug 2025 11:33:12 -0400 Subject: [PATCH 2/2] feature/IO-3255-simplified-parts-management - Simplified Parts print center stacks on top of each other, not beside each other --- .../print-center-jobs-parts.component.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/components/print-center-jobs/print-center-jobs-parts.component.jsx b/client/src/components/print-center-jobs/print-center-jobs-parts.component.jsx index ffacc7913..5650ab919 100644 --- a/client/src/components/print-center-jobs/print-center-jobs-parts.component.jsx +++ b/client/src/components/print-center-jobs/print-center-jobs-parts.component.jsx @@ -37,7 +37,7 @@ export function PrintCenterJobsPartsComponent({ printCenterModal, bodyshop, tech .filter( (temp) => (!temp.regions || - (temp.regions && temp.regions[bodyshop.region_config]) || + temp.regions?.[bodyshop.region_config] || (temp.regions && bodyshop.region_config.includes(Object.keys(temp.regions)) === true)) && (!temp.dms || temp.dms === false) ) @@ -46,7 +46,7 @@ export function PrintCenterJobsPartsComponent({ printCenterModal, bodyshop, tech .filter( (temp) => !temp.regions || - (temp.regions && temp.regions[bodyshop.region_config]) || + temp.regions?.[bodyshop.region_config] || (temp.regions && bodyshop.region_config.includes(Object.keys(temp.regions)) === true) ); @@ -82,7 +82,7 @@ export function PrintCenterJobsPartsComponent({ printCenterModal, bodyshop, tech variables: { id: jobId } }, { - to: job && job.ownr_ea, + to: job?.ownr_ea, subject: cards.find((c) => c.key === key)?.subject }, "e", @@ -129,7 +129,7 @@ export function PrintCenterJobsPartsComponent({ printCenterModal, bodyshop, tech const columns = `repeat(${actions.length}, 1fr)`; return ( - +