From 633d5668f091b78dbff32fd57a58aafb71c9c412 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Mon, 23 Jun 2025 15:08:36 -0700 Subject: [PATCH 01/11] IO-3279 Set usage report to 1 year. --- server/data/usageReport.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/data/usageReport.js b/server/data/usageReport.js index af1d6ec75..ead4b0bba 100644 --- a/server/data/usageReport.js +++ b/server/data/usageReport.js @@ -35,7 +35,7 @@ exports.default = async (req, res) => { //Query the usage data. const queryResults = await client.request(queries.STATUS_UPDATE, { today: moment().startOf("day").subtract(7, "days"), - period: moment().subtract(90, "days").startOf("day") + period: moment().subtract(365, "days").startOf("day") }); //Massage the data. @@ -66,7 +66,7 @@ exports.default = async (req, res) => { Usage Report for ${moment().format("MM/DD/YYYY")} for Rome Online Customers. Notes: - - Days Since Creation: The number of days since the shop was created. Only shops created in the last 90 days are included. + - Days Since Creation: The number of days since the shop was created. Only shops created in the last 365 days are included. - Updated values should be higher than created values. - Counts are inclusive of the last 7 days of data. `, From 7d930045efad0dbf32684ccaaf4034e4760bbef5 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 24 Jun 2025 14:38:13 -0400 Subject: [PATCH 02/11] feature/IO-3255-simplified-parts-management - Checkpoint --- .../partsManagement.queries.js | 40 + ...rtsManagementVehicleDamageEstimateAddRq.js | 751 +++++++++--------- 2 files changed, 436 insertions(+), 355 deletions(-) create mode 100644 server/integrations/partsManagement/partsManagement.queries.js diff --git a/server/integrations/partsManagement/partsManagement.queries.js b/server/integrations/partsManagement/partsManagement.queries.js new file mode 100644 index 000000000..45a88ad58 --- /dev/null +++ b/server/integrations/partsManagement/partsManagement.queries.js @@ -0,0 +1,40 @@ +// GraphQL Queries and Mutations +const GET_BODYSHOP_STATUS = ` + query GetBodyshopStatus($id: uuid!) { + bodyshops_by_pk(id: $id) { + md_order_statuses + } + } +`; + +const GET_VEHICLE_BY_SHOP_VIN = ` + query GetVehicleByShopVin($shopid: uuid!, $v_vin: String!) { + vehicles(where: { shopid: { _eq: $shopid }, v_vin: { _eq: $v_vin } }, limit: 1) { + id + } + } +`; + +const INSERT_OWNER = ` + mutation InsertOwner($owner: owners_insert_input!) { + insert_owners_one(object: $owner) { + id + } + } +`; + +const INSERT_JOB_WITH_LINES = ` + mutation InsertJob($job: jobs_insert_input!) { + insert_jobs_one(object: $job) { + id + joblines { id unq_seq } + } + } +`; + +module.exports = { + GET_BODYSHOP_STATUS, + GET_VEHICLE_BY_SHOP_VIN, + INSERT_OWNER, + INSERT_JOB_WITH_LINES +}; diff --git a/server/integrations/partsManagement/partsManagementVehicleDamageEstimateAddRq.js b/server/integrations/partsManagement/partsManagementVehicleDamageEstimateAddRq.js index 31ca61031..c163a51da 100644 --- a/server/integrations/partsManagement/partsManagementVehicleDamageEstimateAddRq.js +++ b/server/integrations/partsManagement/partsManagementVehicleDamageEstimateAddRq.js @@ -4,357 +4,370 @@ const xml2js = require("xml2js"); const client = require("../../graphql-client/graphql-client").client; +// GraphQL Queries and Mutations +const { + GET_BODYSHOP_STATUS, + GET_VEHICLE_BY_SHOP_VIN, + INSERT_OWNER, + INSERT_JOB_WITH_LINES +} = require("./partsManagement.queries"); + // Defaults -const FALLBACK_DEFAULT_ORDER_STATUS = "OPEN"; // Default status if not found in bodyshop +const FALLBACK_DEFAULT_ORDER_STATUS = "OPEN"; -// GraphQL statements -const INSERT_JOB_WITH_LINES = ` - mutation InsertJob($job: jobs_insert_input!) { - insert_jobs_one(object: $job) { - id - joblines { id unq_seq } - } - } -`; - -const GET_BODYSHOP_STATUS = ` - query GetBodyshopStatus($id: uuid!) { - bodyshops_by_pk(id: $id) { - md_order_statuses - } - } -`; - -const INSERT_OWNER = ` - mutation InsertOwner($owner: owners_insert_input!) { - insert_owners_one(object: $owner) { - id - } - } -`; - -// Do they call the add call first, future ones will be updates, we need to upcycle. Or we need to send a new add request, we treat it as an upsert. +// Known part rate types for tax rates +const KNOWN_PART_RATE_TYPES = [ + "PAA", + "PAC", + "PAG", + "PAL", + "PAM", + "PAN", + "PAO", + "PAP", + "PAR", + "PAS", + "PASL", + "CCC", + "CCD", + "CCF", + "CCM", + "CCDR" +]; /** - * Handles the VehicleDamageEstimateAddRq XML request from parts management. - * @param req - * @param res - * @returns {Promise<*>} + * Parses XML string into a JavaScript object. + * @param {string} xml - The XML string to parse. + * @param {object} logger - The logger instance. + * @returns {Promise} The parsed XML object. + * @throws {Error} If XML parsing fails. */ -const partsManagementVehicleDamageEstimateAddRq = async (req, res) => { - const { logger } = req; - const xml = req.body; - - // ── PARSE XML ──────────────────────────────────────────────────────────────── - let payload; +const parseXml = async (xml, logger) => { try { - payload = await xml2js.parseStringPromise(xml, { + return await xml2js.parseStringPromise(xml, { explicitArray: false, tagNameProcessors: [xml2js.processors.stripPrefix], attrNameProcessors: [xml2js.processors.stripPrefix] - // ignoreAttrs: false, - // xmlns: false }); } catch (err) { logger.log("parts-xml-parse-error", "error", null, null, { error: err }); - return res.status(400).send("Invalid XML"); + throw new Error("Invalid XML"); + } +}; + +/** + * Fetches the default order status for a bodyshop. + * @param {string} shopId - The bodyshop UUID. + * @param {object} logger - The logger instance. + * @returns {Promise} The default status or fallback. + */ +const getDefaultOrderStatus = async (shopId, logger) => { + try { + const { bodyshop_by_pk } = await client.request(GET_BODYSHOP_STATUS, { id: shopId }); + return bodyshop_by_pk?.md_order_statuses?.default_open || FALLBACK_DEFAULT_ORDER_STATUS; + } catch (err) { + logger.log("parts-bodyshop-fetch-failed", "warn", shopId, null, { error: err }); + return FALLBACK_DEFAULT_ORDER_STATUS; + } +}; + +/** + * Extracts and processes parts tax rates from profile info. + * @param {object} profile - The ProfileInfo object from XML. + * @returns {object} The parts tax rates object. + */ +const extractPartsTaxRates = (profile = {}) => { + const rateInfos = Array.isArray(profile.RateInfo) ? profile.RateInfo : [profile.RateInfo || {}]; + const partsTaxRates = {}; + + for (const code of KNOWN_PART_RATE_TYPES) { + const rateInfo = rateInfos.find((r) => (r?.RateType || "").toUpperCase() === code); + if (!rateInfo) { + partsTaxRates[code] = {}; + continue; + } + + const taxInfo = rateInfo.TaxInfo; + const taxTier = taxInfo?.TaxTierInfo; + let percentage = parseFloat(taxTier?.Percentage ?? "NaN"); + + if (isNaN(percentage)) { + const tierRate = Array.isArray(rateInfo.RateTierInfo) + ? rateInfo.RateTierInfo[0]?.Rate + : rateInfo.RateTierInfo?.Rate; + percentage = parseFloat(tierRate ?? "NaN"); + } + + partsTaxRates[code] = isNaN(percentage) + ? {} + : { + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: percentage / 100 + }; } - const rq = payload.VehicleDamageEstimateAddRq; - if (!rq) { - logger.log("parts-missing-root", "error"); - return res.status(400).send("Missing "); - } + return partsTaxRates; +}; + +/** + * Extracts job-related data from the XML request. + * @param {object} rq - The VehicleDamageEstimateAddRq object. + * @returns {object} Extracted job data. + */ +const extractJobData = (rq) => { + const doc = rq.DocumentInfo || {}; + const ev = rq.EventInfo || {}; + const asgn = ev.AssignmentEvent || {}; + const ci = rq.ClaimInfo || {}; + + return { + shopId: rq.ShopID || rq.shopId, + refClaimNum: rq.RefClaimNum, + ciecaid: rq.RqUID || null, + cieca_ttl: parseFloat(rq.Cieca_ttl || 0), + cat_no: doc.VendorCode || null, + category: doc.DocumentType || null, + classType: doc.DocumentStatus || null, + comment: doc.Comment || null, + date_exported: doc.TransmitDateTime || null, + asgn_no: asgn.AssignmentNumber || null, + asgn_type: asgn.AssignmentType || null, + asgn_date: asgn.AssignmentDate || null, + scheduled_in: ev.RepairEvent?.RequestedPickUpDateTime || null, + scheduled_completion: ev.RepairEvent?.TargetCompletionDateTime || null, + clm_no: ci.ClaimNum || null, + status: ci.ClaimStatus || null, + policy_no: ci.PolicyInfo?.PolicyNum || null, + ded_amt: parseFloat(ci.PolicyInfo?.CoverageInfo?.Coverage?.DeductibleInfo?.DeductibleAmt || 0) + }; +}; + +/** + * Extracts owner data from the XML request. + * @param {object} rq - The VehicleDamageEstimateAddRq object. + * @param {string} shopId - The bodyshop UUID. + * @returns {object} Owner data for insertion and inline use. + */ +const extractOwnerData = (rq, shopId) => { + const ownerParty = rq.AdminInfo?.Owner?.Party || {}; + const adr = ownerParty.PersonInfo?.Communications?.Address || {}; + let ownr_ph1, ownr_ph2, ownr_ea; + + (Array.isArray(ownerParty.ContactInfo?.Communications) + ? ownerParty.ContactInfo.Communications + : [ownerParty.ContactInfo?.Communications || {}] + ).forEach((c) => { + if (c.CommQualifier === "CP") ownr_ph1 = c.CommPhone; + if (c.CommQualifier === "WP") ownr_ph2 = c.CommPhone; + if (c.CommQualifier === "EM") ownr_ea = c.CommEmail; + }); + + return { + shopid: shopId, + ownr_fn: ownerParty.PersonInfo?.PersonName?.FirstName || null, + ownr_ln: ownerParty.PersonInfo?.PersonName?.LastName || null, + ownr_co_nm: ownerParty.OrgInfo?.CompanyName || null, + ownr_addr1: adr.Address1 || null, + ownr_addr2: adr.Address2 || null, + ownr_city: adr.City || null, + ownr_st: adr.StateProvince || null, + ownr_zip: adr.PostalCode || null, + ownr_ctry: adr.Country || null, + ownr_ph1, + ownr_ph2, + ownr_ea + }; +}; + +/** + * Extracts estimator data from the XML request. + * @param {object} rq - The VehicleDamageEstimateAddRq object. + * @returns {object} Estimator data. + */ +const extractEstimatorData = (rq) => { + const estParty = rq.AdminInfo?.Estimator?.Party || {}; + const estComms = Array.isArray(estParty.ContactInfo?.Communications) + ? estParty.ContactInfo.Communications + : [estParty.ContactInfo?.Communications || {}]; + + return { + est_co_nm: rq.AdminInfo?.Estimator?.Affiliation || null, + est_ct_fn: estParty.PersonInfo?.PersonName?.FirstName || null, + est_ct_ln: estParty.PersonInfo?.PersonName?.LastName || null, + est_ea: estComms.find((c) => c.CommQualifier === "EM")?.CommEmail || null + }; +}; + +/** + * Extracts adjuster data from the XML request. + * @param {object} rq - The VehicleDamageEstimateAddRq object. + * @returns {object} Adjuster data. + */ +const extractAdjusterData = (rq) => { + const adjParty = rq.AdminInfo?.Adjuster?.Party || {}; + const adjComms = Array.isArray(adjParty.ContactInfo?.Communications) + ? adjParty.ContactInfo.Communications + : [adjParty.ContactInfo?.Communications || {}]; + + return { + agt_ct_fn: adjParty.PersonInfo?.PersonName?.FirstName || null, + agt_ct_ln: adjParty.PersonInfo?.PersonName?.LastName || null, + agt_ct_ph: adjComms.find((c) => c.CommQualifier === "CP")?.CommPhone || null, + agt_ea: adjComms.find((c) => c.CommQualifier === "EM")?.CommEmail || null + }; +}; + +/** + * Extracts repair facility data from the XML request. + * @param {object} rq - The VehicleDamageEstimateAddRq object. + * @returns {object} Repair facility data. + */ +const extractRepairFacilityData = (rq) => { + const rfParty = rq.AdminInfo?.RepairFacility?.Party || {}; + const rfComms = Array.isArray(rfParty.ContactInfo?.Communications) + ? rfParty.ContactInfo.Communications + : [rfParty.ContactInfo?.Communications || {}]; + + return { + servicing_dealer: rfParty.OrgInfo?.CompanyName || null, + servicing_dealer_contact: + rfComms.find((c) => c.CommQualifier === "WP" || c.CommQualifier === "FX")?.CommPhone || null + }; +}; + +/** + * Extracts vehicle data from the XML request. + * @param {object} rq - The VehicleDamageEstimateAddRq object. + * @param {string} shopId - The bodyshop UUID. + * @returns {object} Vehicle data for insertion and inline use. + */ +const extractVehicleData = (rq, shopId) => { + const desc = rq.VehicleInfo?.VehicleDesc || {}; + + return { + shopid: shopId, + v_vin: rq.VehicleInfo?.VINInfo?.VIN?.VINNum || null, + plate_no: rq.VehicleInfo?.License?.LicensePlateNum || null, + plate_st: rq.VehicleInfo?.License?.LicensePlateStateProvince || null, + v_model_yr: desc.ModelYear || null, + v_make_desc: desc.MakeDesc || null, + v_model_desc: desc.ModelName || null, + v_color: rq.VehicleInfo?.Paint?.Exterior?.ColorName || null, + v_bstyle: desc.BodyStyle || null, + v_engine: desc.EngineDesc || null, + v_options: desc.SubModelDesc || null, + v_type: desc.FuelType || null, + v_cond: rq.VehicleInfo?.Condition?.DrivableInd + }; +}; + +/** + * Extracts job lines from the XML request. + * @param {object} rq - The VehicleDamageEstimateAddRq object. + * @returns {object[]} Array of job line objects. + */ +const extractJobLines = (rq) => { + const damageLines = Array.isArray(rq.DamageLineInfo) ? rq.DamageLineInfo : [rq.DamageLineInfo]; + + return damageLines.map((line) => { + const jobLine = { + line_no: parseInt(line.LineNum, 10), + unq_seq: parseInt(line.UniqueSequenceNum, 10), + status: line.LineStatusCode || null, + line_desc: line.LineDesc || null, + part_type: line.PartInfo?.PartType || null, + part_qty: parseFloat(line.PartInfo?.Quantity || 0), + oem_partno: line.PartInfo?.OEMPartNum || null, + db_price: parseFloat(line.PartInfo?.PartPrice || 0), + act_price: parseFloat(line.PartInfo?.PartPrice || 0), + mod_lbr_ty: line.LaborInfo?.LaborType || null, + mod_lb_hrs: parseFloat(line.LaborInfo?.LaborHours || 0), + lbr_op: line.LaborInfo?.LaborOperation || null, + lbr_amt: parseFloat(line.LaborInfo?.LaborAmt || 0), + notes: line.LineMemo || null + }; + + // TODO: Commented out as not clear if needed for version 1, this only applies to Imex and not rome on the front + // end + // if ((jobLine.part_type === "PASL" || jobLine.part_type === "PAS") && jobLine.lbr_op !== "OP11") { + // jobLine.tax_part = true; + // } + // if (line.db_ref === "900510") { + // jobLine.tax_part = true; + // } + + return jobLine; + }); +}; + +/** + * Finds an existing vehicle by shopId and VIN. + * @param {string} shopId - The bodyshop UUID. + * @param {string} v_vin - The vehicle VIN. + * @param {object} logger - The logger instance. + * @returns {Promise} The vehicle ID or null if not found. + */ +const findExistingVehicle = async (shopId, v_vin, logger) => { + if (!v_vin) return null; try { - // ── SHOP & CLAIM IDs ──────────────────────────────────────────────────────── - const shopId = rq.ShopID || rq.shopId; - if (!shopId) throw { status: 400, message: "Missing in XML" }; - const { RefClaimNum } = rq; - - let defaultStatus = FALLBACK_DEFAULT_ORDER_STATUS; - - try { - const { bodyshop_by_pk } = await client.request(GET_BODYSHOP_STATUS, { id: shopId }); - defaultStatus = bodyshop_by_pk?.md_order_statuses?.default_open || defaultStatus; - } catch (err) { - logger.log("parts-bodyshop-fetch-failed", "warn", shopId, null, { error: err }); + const { vehicles } = await client.request(GET_VEHICLE_BY_SHOP_VIN, { shopid: shopId, v_vin }); + if (vehicles?.length > 0) { + logger.log("parts-vehicle-found", "info", vehicles[0].id, null, { shopid: shopId, v_vin }); + return vehicles[0].id; } - // ── DOCUMENT INFO ────────────────────────────────────────────────────────── - const doc = rq.DocumentInfo || {}; - const comment = doc.Comment || null; - const date_exported = doc.TransmitDateTime || null; - // capture CIECA ID & totals - const ciecaid = rq.RqUID || null; - const cieca_ttl = parseFloat(rq.Cieca_ttl || 0); - // map DocumentInfo fields to our category/class fields - const cat_no = doc.VendorCode || null; - const category = doc.DocumentType || null; - const classType = doc.DocumentStatus || null; + } catch (err) { + logger.log("parts-vehicle-fetch-failed", "warn", null, null, { error: err }); + } + return null; +}; - // ── PARTS TAX RATES STRUCTURE ─────────────────────────────────────────────── - // Known rate types that map to your parts_tax_rates keys - // If this has become an issue, default it to an empty object for version 1 - const knownPartRateTypes = [ - "PAA", - "PAC", - "PAG", - "PAL", - "PAM", - "PAN", - "PAO", - "PAP", - "PAR", - "PAS", - "PASL", - "CCC", - "CCD", - "CCF", - "CCM", - "CCDR" - ]; +/** + * Inserts an owner and returns the owner ID. + * @param {object} ownerInput - The owner data to insert. + * @param {object} logger - The logger instance. + * @returns {Promise} The owner ID or null if insertion fails. + */ +const insertOwner = async (ownerInput, logger) => { + try { + const { insert_owners_one } = await client.request(INSERT_OWNER, { owner: ownerInput }); + return insert_owners_one?.id; + } catch (err) { + logger.log("parts-owner-insert-failed", "warn", null, null, { error: err }); + return null; + } +}; - const profile = rq.ProfileInfo || {}; - const rateInfos = Array.isArray(profile.RateInfo) ? profile.RateInfo : [profile.RateInfo || {}]; +/** + * Handles the VehicleDamageEstimateAddRq XML request from parts management. + * @param {object} req - The HTTP request object. + * @param {object} res - The HTTP response object. + * @returns {Promise} + */ +const partsManagementVehicleDamageEstimateAddRq = async (req, res) => { + const { logger } = req; - const parts_tax_rates = {}; - - for (const code of knownPartRateTypes) { - const rateInfo = rateInfos.find((r) => (r?.RateType || "").toUpperCase() === code); - if (!rateInfo) { - parts_tax_rates[code] = {}; - continue; - } - - const taxInfo = rateInfo.TaxInfo; - const taxTier = taxInfo?.TaxTierInfo; - - // Try to find Percentage first - let percentage = parseFloat(taxTier?.Percentage ?? "NaN"); - if (isNaN(percentage)) { - // fallback to RateTierInfo.Rate if that's where it might be - const tierRate = Array.isArray(rateInfo.RateTierInfo) - ? rateInfo.RateTierInfo[0]?.Rate - : rateInfo.RateTierInfo?.Rate; - - percentage = parseFloat(tierRate ?? "NaN"); - } - - // Still no tax rate? fallback to null object - if (isNaN(percentage)) { - parts_tax_rates[code] = {}; - continue; - } - - parts_tax_rates[code] = { - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: percentage / 100 - }; + try { + // Parse XML + const payload = await parseXml(req.body, logger); + const rq = payload.VehicleDamageEstimateAddRq; + if (!rq) { + logger.log("parts-missing-root", "error"); + return res.status(400).send("Missing "); } - // ── EVENT INFO ────────────────────────────────────────────────────────────── - const ev = rq.EventInfo || {}; - const asgn = ev.AssignmentEvent || {}; - const asgn_no = asgn.AssignmentNumber || null; - const asgn_type = asgn.AssignmentType || null; - const asgn_date = asgn.AssignmentDate || null; - const scheduled_in = ev.RepairEvent?.RequestedPickUpDateTime || null; - const scheduled_completion = ev.RepairEvent?.TargetCompletionDateTime || null; - - // ── CLAIM & POLICY ────────────────────────────────────────────────────────── - const ci = rq.ClaimInfo || {}; - const clm_no = ci.ClaimNum || null; - const status = ci.ClaimStatus || null; - const policy_no = ci.PolicyInfo?.PolicyNum || null; - const ded_amt = parseFloat(ci.PolicyInfo?.CoverageInfo?.Coverage?.DeductibleInfo?.DeductibleAmt || 0); - - // ── OWNER ──────────────────────────────────────────────────────────────────── - const ownerParty = rq.AdminInfo?.Owner?.Party || {}; - const ownr_fn = ownerParty.PersonInfo?.PersonName?.FirstName || null; - const ownr_ln = ownerParty.PersonInfo?.PersonName?.LastName || null; - const ownr_co_nm = ownerParty.OrgInfo?.CompanyName || null; - const adr = ownerParty.PersonInfo?.Communications?.Address || {}; - const ownr_addr1 = adr.Address1 || null; - const ownr_addr2 = adr.Address2 || null; - const ownr_city = adr.City || null; - const ownr_st = adr.StateProvince || null; - const ownr_zip = adr.PostalCode || null; - const ownr_ctry = adr.Country || null; - let ownr_ph1; - let ownr_ph2; - let ownr_fax; - let ownr_ea; - (Array.isArray(ownerParty.ContactInfo?.Communications) - ? ownerParty.ContactInfo.Communications - : [ownerParty.ContactInfo?.Communications || {}] - ).forEach((c) => { - if (c.CommQualifier === "CP") ownr_ph1 = c.CommPhone; - if (c.CommQualifier === "WP") ownr_ph2 = c.CommPhone; - if (c.CommQualifier === "FX") ownr_fax = c.CommPhone; - if (c.CommQualifier === "EM") ownr_ea = c.CommEmail; - }); - - // Estimator → map to est_… fields - const estParty = rq.AdminInfo?.Estimator?.Party || {}; - // grab raw first/last - const est_fn = estParty.PersonInfo?.PersonName?.FirstName || null; - const est_ln = estParty.PersonInfo?.PersonName?.LastName || null; - // now alias into the GraphQL names - const est_ct_fn = est_fn; - const est_ct_ln = est_ln; - - const est_co_nm = rq.AdminInfo?.Estimator?.Affiliation || null; - - const estComms = Array.isArray(estParty.ContactInfo?.Communications) - ? estParty.ContactInfo.Communications - : [estParty.ContactInfo?.Communications || {}]; - const est_ea = estComms.find((c) => c.CommQualifier === "EM")?.CommEmail || null; - - // ── ADJUSTER ──────────────────────────────────────────────────────────────── - const adjParty = rq.AdminInfo?.Adjuster?.Party || {}; - const agt_ct_fn = adjParty.PersonInfo?.PersonName?.FirstName || null; - const agt_ct_ln = adjParty.PersonInfo?.PersonName?.LastName || null; - const agt_ct_ph = - (Array.isArray(adjParty.ContactInfo?.Communications) - ? adjParty.ContactInfo.Communications - : [adjParty.ContactInfo?.Communications || {}] - ).find((c) => c.CommQualifier === "CP")?.CommPhone || null; - const agt_ea = - (Array.isArray(adjParty.ContactInfo?.Communications) - ? adjParty.ContactInfo.Communications - : [adjParty.ContactInfo?.Communications || {}] - ).find((c) => c.CommQualifier === "EM")?.CommEmail || null; - - // ── REPAIR FACILITY ───────────────────────────────────────────────────────── - const rfParty = rq.AdminInfo?.RepairFacility?.Party || {}; - const servicing_dealer = rfParty.OrgInfo?.CompanyName || null; - const servicing_dealer_contact = - (Array.isArray(rfParty.ContactInfo?.Communications) - ? rfParty.ContactInfo.Communications - : [rfParty.ContactInfo?.Communications || {}] - ).find((c) => c.CommQualifier === "WP" || c.CommQualifier === "FX")?.CommPhone || null; - - // ── VEHICLE (one-to-one) ───────────────────────────────────────────────────── - const vin = rq.VehicleInfo?.VINInfo?.VINNum || null; - const plate_no = rq.VehicleInfo?.License?.LicensePlateNum || null; - const plate_st = rq.VehicleInfo?.License?.LicensePlateStateProvince || null; - const desc = rq.VehicleInfo?.VehicleDesc || {}; - const v_model_yr = desc.ModelYear || null; - const v_make_desc = desc.MakeDesc || null; - const v_model_desc = desc.ModelName || null; - const body_style = desc.BodyStyle || null; - const engine_desc = desc.EngineDesc || null; - const v_options = desc.SubModelDesc || null; - const v_type = desc.FuelType || null; - const v_cond = rq.VehicleInfo?.Condition?.DrivableInd; - - const vehicleData = { - shopid: shopId, - v_vin: vin, - plate_no, - plate_st, - v_model_yr, - v_make_desc, - v_model_desc, - v_color: rq.VehicleInfo?.Paint?.Exterior?.ColorName || null, - v_bstyle: body_style, - v_engine: engine_desc, - // prod_dt: production_date, - v_options, - v_type, - v_cond - }; - - // ── DAMAGE LINES → joblinesData ──────────────────────────────────────────── - const damageLines = Array.isArray(rq.DamageLineInfo) ? rq.DamageLineInfo : [rq.DamageLineInfo]; - const joblinesData = damageLines.map((line) => { - const jobLine = { - line_no: parseInt(line.LineNum, 10), - unq_seq: parseInt(line.UniqueSequenceNum, 10), - status: line.LineStatusCode || null, - line_desc: line.LineDesc || null, - - // parts - part_type: line.PartInfo?.PartType || null, - part_qty: parseFloat(line.PartInfo?.Quantity || 0), - oem_partno: line.PartInfo?.OEMPartNum || null, - db_price: parseFloat(line.PartInfo?.PartPrice || 0), - act_price: parseFloat(line.PartInfo?.PartPrice || 0), - - // labor - mod_lbr_ty: line.LaborInfo?.LaborType || null, - mod_lb_hrs: parseFloat(line.LaborInfo?.LaborHours || 0), - lbr_op: line.LaborInfo?.LaborOperation || null, - lbr_amt: parseFloat(line.LaborInfo?.LaborAmt || 0), - - notes: line.LineMemo || null - }; - - // TODO: Commented out as not clear if needed for version 1, this only applies to Imex and not rome on the front - // end - - // if ((jobLine.part_type === "PASL" || jobLine.part_type === "PAS") && jobLine.lbr_op !== "OP11") { - // jobLine.tax_part = true; - // } - // if (line.db_ref === "900510") { - // jobLine.tax_part = true; - // } - - return jobLine; - }); - - const ownerInput = { - shopid: shopId, - ownr_fn, - ownr_ln, - ownr_co_nm, - ownr_addr1, - ownr_addr2, - ownr_city, - ownr_st, - ownr_zip, - ownr_ctry, - ownr_ph1, - ownr_ph2, - ownr_ea - }; - - let ownerid = null; - - try { - const { insert_owners_one } = await client.request(INSERT_OWNER, { owner: ownerInput }); - ownerid = insert_owners_one?.id; - } catch (err) { - logger.log("parts-owner-insert-failed", "warn", null, null, { error: err }); - } - - // ── BUILD & INSERT THE JOB ────────────────────────────────────────────────── - const jobInput = { - shopid: shopId, - ownerid, - ro_number: RefClaimNum, - - // IDs & CIECA metadata + // Extract job data + const { + shopId, + refClaimNum, ciecaid, cieca_ttl, cat_no, category, - class: classType, - - // tax - parts_tax_rates, - - // claim & policy - clm_no, - status: status || defaultStatus, - clm_total: cieca_ttl, - policy_no, - ded_amt, - - // document & events + classType, comment, date_exported, asgn_no, @@ -362,45 +375,73 @@ const partsManagementVehicleDamageEstimateAddRq = async (req, res) => { asgn_date, scheduled_in, scheduled_completion, + clm_no, + status, + policy_no, + ded_amt + } = extractJobData(rq); - // owner - ownr_fn, - ownr_ln, - ownr_co_nm, - ownr_addr1, - ownr_addr2, - ownr_city, - ownr_st, - ownr_zip, - ownr_ctry, - ownr_ph1, - ownr_ph2, - ownr_fax, - ownr_ea, + if (!shopId) { + throw { status: 400, message: "Missing in XML" }; + } - // estimator - est_co_nm, - est_ct_fn, - est_ct_ln, - est_ea, + // Get default status + const defaultStatus = await getDefaultOrderStatus(shopId, logger); - // adjuster - agt_ct_fn, - agt_ct_ln, - agt_ct_ph, - agt_ea, + // Extract additional data + const parts_tax_rates = extractPartsTaxRates(rq.ProfileInfo); + const ownerData = extractOwnerData(rq, shopId); + const estimatorData = extractEstimatorData(rq); + const adjusterData = extractAdjusterData(rq); + const repairFacilityData = extractRepairFacilityData(rq); + const vehicleData = extractVehicleData(rq, shopId); + const joblinesData = extractJobLines(rq); - // repair facility - servicing_dealer, - servicing_dealer_contact, + // Find or create relationships + const ownerid = await insertOwner(ownerData, logger); + const vehicleid = await findExistingVehicle(shopId, vehicleData.v_vin, logger); - // nested relationships - vehicle: { data: vehicleData }, + // Build job input + const jobInput = { + shopid: shopId, + ownerid, + ro_number: refClaimNum, + ciecaid, + cieca_ttl, + cat_no, + category, + class: classType, + parts_tax_rates, + clm_no, + status: status || defaultStatus, + clm_total: cieca_ttl, + policy_no, + ded_amt, + comment, + date_exported, + asgn_no, + asgn_type, + asgn_date, + scheduled_in, + scheduled_completion, + ...ownerData, // Inline owner data + ...estimatorData, + ...adjusterData, + ...repairFacilityData, + // Inline vehicle data + v_vin: vehicleData.v_vin, + v_model_yr: vehicleData.v_model_yr, + v_model_desc: vehicleData.v_model_desc, + v_make_desc: vehicleData.v_make_desc, + v_color: vehicleData.v_color, + plate_no: vehicleData.plate_no, + plate_st: vehicleData.plate_st, + ...(vehicleid ? { vehicleid } : { vehicle: { data: vehicleData } }), joblines: { data: joblinesData } }; + // Insert job const { insert_jobs_one: newJob } = await client.request(INSERT_JOB_WITH_LINES, { job: jobInput }); - logger.log("parts-job-created", "info", newJob.id, null); return res.status(200).json({ success: true, jobId: newJob.id }); From 25ea2a80a3dbe58e53df926874c385b07c9ecff7 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 24 Jun 2025 14:42:06 -0400 Subject: [PATCH 03/11] hotfix/IO-3280-Image-Selection-Bug - Fix Bug in image selection dialog --- .../chat-media-selector/chat-media-selector.component.jsx | 2 +- .../chat-media-selector/chat-media-selector.styles.scss | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/client/src/components/chat-media-selector/chat-media-selector.component.jsx b/client/src/components/chat-media-selector/chat-media-selector.component.jsx index 95a917e7e..75de2879a 100644 --- a/client/src/components/chat-media-selector/chat-media-selector.component.jsx +++ b/client/src/components/chat-media-selector/chat-media-selector.component.jsx @@ -110,7 +110,7 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c trigger="click" open={open} onOpenChange={handleVisibleChange} - overlayClassName="media-selector-popover" + classNames={{ root: "media-selector-popover" }} > s.isSelected).length}> diff --git a/client/src/components/chat-media-selector/chat-media-selector.styles.scss b/client/src/components/chat-media-selector/chat-media-selector.styles.scss index 58e2d06b4..76442a592 100644 --- a/client/src/components/chat-media-selector/chat-media-selector.styles.scss +++ b/client/src/components/chat-media-selector/chat-media-selector.styles.scss @@ -1,5 +1,6 @@ .media-selector-popover { .ant-popover-inner-content { + position: relative; max-width: 640px; max-height: 480px; overflow-y: auto; @@ -36,11 +37,6 @@ border-radius: 4px; margin: 4px; cursor: pointer; - transition: transform 0.2s; - - &:hover { - transform: scale(1.05); - } } /* Grid layout for gallery components */ From 01185b307360597f7b09b8f20dc54895ab41019b Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 24 Jun 2025 16:22:07 -0400 Subject: [PATCH 04/11] feature/IO-3182-Phone-Number-Consent - Checkpoint --- ...rtsManagementVehicleDamageEstimateAddRq.js | 99 +++++++++++++++++-- 1 file changed, 93 insertions(+), 6 deletions(-) diff --git a/server/integrations/partsManagement/partsManagementVehicleDamageEstimateAddRq.js b/server/integrations/partsManagement/partsManagementVehicleDamageEstimateAddRq.js index c163a51da..ba9ac2aa2 100644 --- a/server/integrations/partsManagement/partsManagementVehicleDamageEstimateAddRq.js +++ b/server/integrations/partsManagement/partsManagementVehicleDamageEstimateAddRq.js @@ -238,6 +238,75 @@ const extractRepairFacilityData = (rq) => { }; }; +/** + * Extracts loss information from the XML request. + * @param rq + * @returns {{loss_dt: (*|null), reported_dt: (*|null), loss_type_code: (*|null), loss_type_desc: (*|null)}} + */ +const extractLossInfo = (rq) => { + const loss = rq.ClaimInfo?.LossInfo?.Facts || {}; + const custom = rq.ClaimInfo?.CustomElement || {}; + return { + loss_date: loss.LossDateTime || null, + loss_type: custom.LossTypeCode || null, + loss_desc: custom.LossTypeDesc || null + }; +}; + +/** + * Extracts insurance data from the XML request. + * @param rq + * @returns {{insd_ln: (*|null), insd_fn: (string|null), insd_title: (*|null), insd_co_nm: (*|string|null), insd_addr1: (*|null), insd_addr2: (*|null), insd_city: (*|null), insd_st: (*|null), insd_zip: (*|null), insd_ctry: (*|null), insd_ph1, insd_ph1x, insd_ph2, insd_ph2x, insd_fax, insd_faxx, insd_ea}} + */ +const extractInsuranceData = (rq) => { + const insuredParty = rq.AdminInfo?.Insured?.Party || {}; + const insuredPerson = insuredParty.PersonInfo || {}; + const insuredComms = Array.isArray(insuredParty.ContactInfo?.Communications) + ? insuredParty.ContactInfo.Communications + : [insuredParty.ContactInfo?.Communications || {}]; + const insuredAddress = insuredPerson.Communications?.Address || {}; + + const insurerParty = rq.AdminInfo?.InsuranceCompany?.Party || {}; + + let insd_ph1, insd_ph1x, insd_ph2, insd_ph2x, insd_fax, insd_faxx, insd_ea; + + for (const c of insuredComms) { + if (c.CommQualifier === "CP") { + insd_ph1 = c.CommPhone; + insd_ph1x = c.CommPhoneExt; + } + if (c.CommQualifier === "WP") { + insd_ph2 = c.CommPhone; + insd_ph2x = c.CommPhoneExt; + } + if (c.CommQualifier === "FX") { + insd_fax = c.CommPhone; + insd_faxx = c.CommPhoneExt; + } + if (c.CommQualifier === "EM") insd_ea = c.CommEmail; + } + + return { + insd_ln: insuredPerson.PersonName?.LastName || null, + insd_fn: insuredPerson.PersonName?.FirstName || null, + insd_title: insuredPerson.PersonName?.Title || null, + insd_co_nm: insurerParty.OrgInfo?.CompanyName || insuredParty.OrgInfo?.CompanyName || null, + insd_addr1: insuredAddress.Address1 || null, + insd_addr2: insuredAddress.Address2 || null, + insd_city: insuredAddress.City || null, + insd_st: insuredAddress.StateProvince || null, + insd_zip: insuredAddress.PostalCode || null, + insd_ctry: insuredAddress.Country || null, + insd_ph1, + insd_ph1x, + insd_ph2, + insd_ph2x, + insd_fax, + insd_faxx, + insd_ea + }; +}; + /** * Extracts vehicle data from the XML request. * @param {object} rq - The VehicleDamageEstimateAddRq object. @@ -246,7 +315,8 @@ const extractRepairFacilityData = (rq) => { */ const extractVehicleData = (rq, shopId) => { const desc = rq.VehicleInfo?.VehicleDesc || {}; - + const exterior = rq.VehicleInfo?.Paint?.Exterior || {}; + const interior = rq.VehicleInfo?.Paint?.Interior || {}; return { shopid: shopId, v_vin: rq.VehicleInfo?.VINInfo?.VIN?.VINNum || null, @@ -255,12 +325,25 @@ const extractVehicleData = (rq, shopId) => { v_model_yr: desc.ModelYear || null, v_make_desc: desc.MakeDesc || null, v_model_desc: desc.ModelName || null, - v_color: rq.VehicleInfo?.Paint?.Exterior?.ColorName || null, + v_color: exterior.Color?.ColorName || null, v_bstyle: desc.BodyStyle || null, v_engine: desc.EngineDesc || null, v_options: desc.SubModelDesc || null, v_type: desc.FuelType || null, - v_cond: rq.VehicleInfo?.Condition?.DrivableInd + v_cond: rq.VehicleInfo?.Condition?.DrivableInd, + v_trimcode: desc.TrimCode || null, + v_tone: exterior.Tone || null, + v_stage: exterior.RefinishStage || rq.VehicleInfo?.Paint?.RefinishStage || null, + v_prod_dt: desc.ProductionDate || null, + v_paint_codes: Array.isArray(exterior.PaintCodeInfo) + ? exterior.PaintCodeInfo.map((p) => p.PaintCode).join(",") + : exterior.PaintCode || null, + v_mldgcode: desc.MldgCode || null, + v_makecode: desc.MakeCode || null, + trim_color: interior.ColorName || desc.TrimColor || null, + db_v_code: desc.DatabaseCode || null, + v_model_num: desc.ModelNum || null, + v_odo: desc.OdometerInfo?.OdometerReading || null }; }; @@ -395,7 +478,9 @@ const partsManagementVehicleDamageEstimateAddRq = async (req, res) => { const adjusterData = extractAdjusterData(rq); const repairFacilityData = extractRepairFacilityData(rq); const vehicleData = extractVehicleData(rq, shopId); + const lossInfo = extractLossInfo(rq); const joblinesData = extractJobLines(rq); + const insuranceData = extractInsuranceData(rq); // Find or create relationships const ownerid = await insertOwner(ownerData, logger); @@ -424,10 +509,12 @@ const partsManagementVehicleDamageEstimateAddRq = async (req, res) => { asgn_date, scheduled_in, scheduled_completion, + ...insuranceData, // Inline insurance data + ...lossInfo, // Inline loss information ...ownerData, // Inline owner data - ...estimatorData, - ...adjusterData, - ...repairFacilityData, + ...estimatorData, // Inline estimator data + ...adjusterData, // Inline adjuster data + ...repairFacilityData, // Inline repair facility data // Inline vehicle data v_vin: vehicleData.v_vin, v_model_yr: vehicleData.v_model_yr, From c1e1dff7d2533e23521e5ed80ea91d4509ffdd46 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 25 Jun 2025 08:51:41 -0700 Subject: [PATCH 05/11] IO-3281 resolve imgproxy download failures. --- ...s-documents-imgproxy-gallery.component.jsx | 8 +++- package-lock.json | 12 +++++- package.json | 3 +- server/media/imgproxy-media.js | 42 +++++++------------ 4 files changed, 36 insertions(+), 29 deletions(-) diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx index f99485dc8..8ada3616f 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx @@ -98,7 +98,13 @@ function JobsDocumentsImgproxyComponent({ jobId={jobId} totalSize={totalSize} billId={billId} - callbackAfterUpload={billsCallback || fetchThumbnails || refetch} + callbackAfterUpload={ + billsCallback || + function () { + isFunction(refetch) && refetch(); + isFunction(fetchThumbnails) && fetchThumbnails(); + } + } ignoreSizeLimit={ignoreSizeLimit} /> diff --git a/package-lock.json b/package-lock.json index 82ec89bb2..79cc67430 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,7 +63,8 @@ "winston": "^3.17.0", "winston-cloudwatch": "^6.3.0", "xml2js": "^0.6.2", - "xmlbuilder2": "^3.1.1" + "xmlbuilder2": "^3.1.1", + "yazl": "^3.3.1" }, "devDependencies": { "@eslint/js": "^9.28.0", @@ -13057,6 +13058,15 @@ "node": ">=8" } }, + "node_modules/yazl": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-3.3.1.tgz", + "integrity": "sha512-BbETDVWG+VcMUle37k5Fqp//7SDOK2/1+T7X8TD96M3D9G8jK5VLUdQVdVjGi8im7FGkazX7kk5hkU8X4L5Bng==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "^1.0.0" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index b57c9f6b2..7b9279bbf 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,8 @@ "winston": "^3.17.0", "winston-cloudwatch": "^6.3.0", "xml2js": "^0.6.2", - "xmlbuilder2": "^3.1.1" + "xmlbuilder2": "^3.1.1", + "yazl": "^3.3.1" }, "devDependencies": { "@eslint/js": "^9.28.0", diff --git a/server/media/imgproxy-media.js b/server/media/imgproxy-media.js index e30aee90e..c6ea1a9ce 100644 --- a/server/media/imgproxy-media.js +++ b/server/media/imgproxy-media.js @@ -20,6 +20,7 @@ const { GET_DOCUMENTS_BY_IDS, DELETE_MEDIA_DOCUMENTS } = require("../graphql-client/queries"); +const yazl = require("yazl"); const imgproxyBaseUrl = process.env.IMGPROXY_BASE_URL; // `https://u4gzpp5wm437dnm75qa42tvza40fguqr.lambda-url.ca-central-1.on.aws` //Direct Lambda function access to bypass CDN. const imgproxySalt = process.env.IMGPROXY_SALT; @@ -174,55 +175,45 @@ const downloadFiles = async (req, res) => { try { logger.log("imgproxy-download", "DEBUG", req.user?.email, jobId, { billid, jobId, documentids }); - //Delayed as the key structure may change slightly from what it is currently and will require evaluating mobile components. const client = req.userGraphQLClient; - - //Query for the keys of the document IDs const data = await client.request(GET_DOCUMENTS_BY_IDS, { documentIds: documentids }); - //Using the Keys, get all the S3 links, zip them, and send back to the client. const s3client = new S3Client({ region: InstanceRegion() }); - const archiveStream = archiver("zip"); - - archiveStream.on("error", (error) => { - console.error("Archival encountered an error:", error); - throw new Error(error); - }); - + const zipfile = new yazl.ZipFile(); const passThrough = new stream.PassThrough(); - archiveStream.pipe(passThrough); + // Pipe the zipfile output to the passThrough stream + zipfile.outputStream.pipe(passThrough); - for (const key of data.documents.map((d) => d.key)) { + // Add each file to the zip as a stream + for (const doc of data.documents) { + const key = doc.key; const response = await s3client.send( new GetObjectCommand({ Bucket: imgproxyDestinationBucket, Key: key }) ); - - archiveStream.append(response.Body, { name: path.basename(key) }); + // response.Body is a readable stream + zipfile.addReadStream(response.Body, path.basename(key)); } - await archiveStream.finalize(); + // Finalize the zip after all files are added + zipfile.end(); const archiveKey = `archives/${jobId || "na"}/archive-${new Date().toISOString()}.zip`; + // Upload the zip stream to S3 const parallelUploads3 = new Upload({ client: s3client, - queueSize: 4, // optional concurrency configuration - leavePartsOnError: false, // optional manually handle dropped parts + queueSize: 4, + leavePartsOnError: false, params: { Bucket: imgproxyDestinationBucket, Key: archiveKey, Body: passThrough } }); - // Disabled progress logging for upload, uncomment if needed - // parallelUploads3.on("httpUploadProgress", (progress) => { - // console.log(progress); - // }); - await parallelUploads3.done(); - //Generate the presigned URL to download it. + // Generate the presigned URL to download it. const presignedUrl = await getSignedUrl( s3client, new GetObjectCommand({ Bucket: imgproxyDestinationBucket, Key: archiveKey }), @@ -230,9 +221,8 @@ const downloadFiles = async (req, res) => { ); return res.json({ success: true, url: presignedUrl }); - //Iterate over them, build the link based on the media type, and return the array. } catch (error) { - logger.log("imgproxy-thumbnails-error", "ERROR", req.user?.email, jobId, { + logger.log("imgproxy-download-error", "ERROR", req.user?.email, jobId, { jobId, billid, message: error.message, From 27d28e7ffcc141d2da8d72df8fe3cb943ec884e3 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 25 Jun 2025 09:42:45 -0700 Subject: [PATCH 06/11] IO-3281 Adjust zip to stream. --- ...nt-imgproxy-gallery.download.component.jsx | 31 ++++++++++--------- .../shop-employees-form.component.jsx | 2 +- server/media/imgproxy-media.js | 31 +++++-------------- 3 files changed, 26 insertions(+), 38 deletions(-) diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx index 8644115fd..9e49d0d97 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx @@ -56,22 +56,25 @@ export function JobsDocumentsImgproxyDownloadButton({ bodyshop, galleryImages, i const handleDownload = async () => { logImEXEvent("jobs_documents_download"); setLoading(true); - const zipUrl = await axios({ - url: "/media/imgproxy/download", - method: "POST", - data: { jobId, documentids: imagesToDownload.map((_) => _.id) } - }); + try { + const response = await axios({ + url: "/media/imgproxy/download", + method: "POST", + responseType: "blob", + data: { jobId, documentids: imagesToDownload.map((_) => _.id) }, + onDownloadProgress: downloadProgress + }); - const theDownloadedZip = await cleanAxios({ - url: zipUrl.data.url, - method: "GET", - responseType: "arraybuffer", - onDownloadProgress: downloadProgress - }); - setLoading(false); - setDownload(null); + setLoading(false); + setDownload(null); - standardMediaDownload(theDownloadedZip.data); + // Use the response data (Blob) to trigger download + standardMediaDownload(response.data); + } catch (error) { + setLoading(false); + setDownload(null); + // handle error (optional) + } }; return ( diff --git a/client/src/components/shop-employees/shop-employees-form.component.jsx b/client/src/components/shop-employees/shop-employees-form.component.jsx index 563ad835b..a44e26d4a 100644 --- a/client/src/components/shop-employees/shop-employees-form.component.jsx +++ b/client/src/components/shop-employees/shop-employees-form.component.jsx @@ -383,7 +383,7 @@ export function ShopEmployeesFormComponent({ bodyshop }) { title={() => } columns={columns} rowKey={"id"} - dataSource={data ? data.employees_by_pk.employee_vacations : []} + dataSource={data?.employees_by_pk?.employee_vacations ?? []} /> ); diff --git a/server/media/imgproxy-media.js b/server/media/imgproxy-media.js index c6ea1a9ce..b11f4b539 100644 --- a/server/media/imgproxy-media.js +++ b/server/media/imgproxy-media.js @@ -180,10 +180,14 @@ const downloadFiles = async (req, res) => { const s3client = new S3Client({ region: InstanceRegion() }); const zipfile = new yazl.ZipFile(); - const passThrough = new stream.PassThrough(); - // Pipe the zipfile output to the passThrough stream - zipfile.outputStream.pipe(passThrough); + // Set response headers for zip download + const filename = `archive-${jobId || "na"}-${new Date().toISOString().replace(/[:.]/g, "-")}.zip`; + res.setHeader("Content-Type", "application/zip"); + res.setHeader("Content-Disposition", `attachment; filename="${filename}"`); + + // Pipe the zipfile output directly to the response + zipfile.outputStream.pipe(res); // Add each file to the zip as a stream for (const doc of data.documents) { @@ -200,27 +204,8 @@ const downloadFiles = async (req, res) => { // Finalize the zip after all files are added zipfile.end(); + // No need to send a JSON response, as the zip is streamed directly - const archiveKey = `archives/${jobId || "na"}/archive-${new Date().toISOString()}.zip`; - - // Upload the zip stream to S3 - const parallelUploads3 = new Upload({ - client: s3client, - queueSize: 4, - leavePartsOnError: false, - params: { Bucket: imgproxyDestinationBucket, Key: archiveKey, Body: passThrough } - }); - - await parallelUploads3.done(); - - // Generate the presigned URL to download it. - const presignedUrl = await getSignedUrl( - s3client, - new GetObjectCommand({ Bucket: imgproxyDestinationBucket, Key: archiveKey }), - { expiresIn: 360 } - ); - - return res.json({ success: true, url: presignedUrl }); } catch (error) { logger.log("imgproxy-download-error", "ERROR", req.user?.email, jobId, { jobId, From f2a2653eae004ecac7c52e8688db812dc624ec44 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 25 Jun 2025 15:36:03 -0700 Subject: [PATCH 07/11] IO-3281 Prevent broken stream reseting HTTP headers. --- ...cument-imgproxy-gallery.download.component.jsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx index 9e49d0d97..65140d6b0 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx @@ -46,11 +46,16 @@ export function JobsDocumentsImgproxyDownloadButton({ bodyshop, galleryImages, i } function standardMediaDownload(bufferData) { - const a = document.createElement("a"); - const url = window.URL.createObjectURL(new Blob([bufferData])); - a.href = url; - a.download = `${identifier || "documents"}.zip`; - a.click(); + try { + const a = document.createElement("a"); + const url = window.URL.createObjectURL(new Blob([bufferData])); + a.href = url; + a.download = `${identifier || "documents"}.zip`; + a.click(); + } catch (error) { + setLoading(false); + setDownload(null); + } } const handleDownload = async () => { From 0c80abb3cab3238601dc7a6865f55bfa4185f600 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 25 Jun 2025 15:48:06 -0700 Subject: [PATCH 08/11] IO-3281 missed file in previous commit. --- server/media/imgproxy-media.js | 91 +++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 35 deletions(-) diff --git a/server/media/imgproxy-media.js b/server/media/imgproxy-media.js index b11f4b539..55d88f938 100644 --- a/server/media/imgproxy-media.js +++ b/server/media/imgproxy-media.js @@ -169,43 +169,14 @@ const getThumbnailUrls = async (req, res) => { * @returns {Promise<*>} */ const downloadFiles = async (req, res) => { - //Given a series of document IDs or keys, generate a file (or a link) to download all images in bulk const { jobId, billid, documentids } = req.body; + logger.log("imgproxy-download", "DEBUG", req.user?.email, jobId, { billid, jobId, documentids }); + + const client = req.userGraphQLClient; + let data; try { - logger.log("imgproxy-download", "DEBUG", req.user?.email, jobId, { billid, jobId, documentids }); - - const client = req.userGraphQLClient; - const data = await client.request(GET_DOCUMENTS_BY_IDS, { documentIds: documentids }); - - const s3client = new S3Client({ region: InstanceRegion() }); - const zipfile = new yazl.ZipFile(); - - // Set response headers for zip download - const filename = `archive-${jobId || "na"}-${new Date().toISOString().replace(/[:.]/g, "-")}.zip`; - res.setHeader("Content-Type", "application/zip"); - res.setHeader("Content-Disposition", `attachment; filename="${filename}"`); - - // Pipe the zipfile output directly to the response - zipfile.outputStream.pipe(res); - - // Add each file to the zip as a stream - for (const doc of data.documents) { - const key = doc.key; - const response = await s3client.send( - new GetObjectCommand({ - Bucket: imgproxyDestinationBucket, - Key: key - }) - ); - // response.Body is a readable stream - zipfile.addReadStream(response.Body, path.basename(key)); - } - - // Finalize the zip after all files are added - zipfile.end(); - // No need to send a JSON response, as the zip is streamed directly - + data = await client.request(GET_DOCUMENTS_BY_IDS, { documentIds: documentids }); } catch (error) { logger.log("imgproxy-download-error", "ERROR", req.user?.email, jobId, { jobId, @@ -213,8 +184,58 @@ const downloadFiles = async (req, res) => { message: error.message, stack: error.stack }); + return res.status(400).json({ message: error.message }); + } - return res.status(400).json({ message: error.message, stack: error.stack }); + const s3client = new S3Client({ region: InstanceRegion() }); + const zipfile = new yazl.ZipFile(); + + const filename = `archive-${jobId || "na"}-${new Date().toISOString().replace(/[:.]/g, "-")}.zip`; + res.setHeader("Content-Type", "application/zip"); + res.setHeader("Content-Disposition", `attachment; filename="${filename}"`); + + // Handle zipfile stream errors + zipfile.outputStream.on("error", (err) => { + logger.log("imgproxy-download-zipstream-error", "ERROR", req.user?.email, jobId, { message: err.message, stack: err.stack }); + // Cannot send another response here, just destroy the connection + res.destroy(err); + }); + + zipfile.outputStream.pipe(res); + + try { + for (const doc of data.documents) { + const key = doc.key; + let response; + try { + response = await s3client.send( + new GetObjectCommand({ + Bucket: imgproxyDestinationBucket, + Key: key + }) + ); + } catch (err) { + logger.log("imgproxy-download-s3-error", "ERROR", req.user?.email, jobId, { key, message: err.message, stack: err.stack }); + // Optionally, skip this file or add a placeholder file in the zip + continue; + } + // Attach error handler to S3 stream + response.Body.on("error", (err) => { + logger.log("imgproxy-download-s3stream-error", "ERROR", req.user?.email, jobId, { key, message: err.message, stack: err.stack }); + res.destroy(err); + }); + zipfile.addReadStream(response.Body, path.basename(key)); + } + zipfile.end(); + } catch (error) { + logger.log("imgproxy-download-error", "ERROR", req.user?.email, jobId, { + jobId, + billid, + message: error.message, + stack: error.stack + }); + // Cannot send another response here, just destroy the connection + res.destroy(error); } }; From bd0c4ceae20a39a33fea01addc865b8b5bdd63fa Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 25 Jun 2025 16:32:47 -0700 Subject: [PATCH 09/11] IO-3281 Resolve key issue for downloads. --- server/media/imgproxy-media.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/server/media/imgproxy-media.js b/server/media/imgproxy-media.js index 55d88f938..46a8dd840 100644 --- a/server/media/imgproxy-media.js +++ b/server/media/imgproxy-media.js @@ -103,13 +103,7 @@ const getThumbnailUrls = async (req, res) => { /////< base 64 URL encoded to image path> //When working with documents from Cloudinary, the URL does not include the extension. - let key; - - if (/\.[^/.]+$/.test(document.key)) { - key = document.key; - } else { - key = `${document.key}.${document.extension.toLowerCase()}`; - } + let key = keyStandardize(document) // Build the S3 path to the object. const fullS3Path = `s3://${imgproxyDestinationBucket}/${key}`; const base64UrlEncodedKeyString = base64UrlEncode(fullS3Path); @@ -205,7 +199,7 @@ const downloadFiles = async (req, res) => { try { for (const doc of data.documents) { - const key = doc.key; + let key = keyStandardize(doc) let response; try { response = await s3client.send( @@ -388,6 +382,15 @@ const moveFiles = async (req, res) => { } }; +const keyStandardize = (doc) => { + if (/\.[^/.]+$/.test(doc.key)) { + return doc.key; + } else { + return `${doc.key}.${doc.extension.toLowerCase()}`; + } +}; + + module.exports = { generateSignedUploadUrls, getThumbnailUrls, From 8de92403ee08e12ccf54e898c957e1b5ed866bb1 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 27 Jun 2025 13:01:18 -0400 Subject: [PATCH 10/11] feature/IO-3255-simplified-parts-management - Checkpoint --- client/package-lock.json | 128 ++--- client/package.json | 8 +- .../simplified-parts.page.component.jsx | 3 +- package-lock.json | 444 +++++++++--------- package.json | 18 +- ...rtsManagementVehicleDamageEstimateAddRq.js | 95 +++- 6 files changed, 373 insertions(+), 323 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index b357aab33..d9f55d38a 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -21,11 +21,11 @@ "@jsreport/browser-client": "^3.1.0", "@reduxjs/toolkit": "^2.8.2", "@sentry/cli": "^2.46.0", - "@sentry/react": "^9.31.0", + "@sentry/react": "^9.32.0", "@sentry/vite-plugin": "^3.5.0", "@splitsoftware/splitio-react": "^2.3.1", "@tanem/react-nprogress": "^5.0.53", - "antd": "^5.26.1", + "antd": "^5.26.2", "apollo-link-logger": "^2.0.1", "apollo-link-sentry": "^4.3.0", "autosize": "^6.0.1", @@ -101,7 +101,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@vitejs/plugin-react": "^4.6.0", - "browserslist": "^4.25.0", + "browserslist": "^4.25.1", "browserslist-to-esbuild": "^2.1.1", "chalk": "^5.4.1", "eslint": "^8.57.1", @@ -116,7 +116,7 @@ "redux-logger": "^3.0.6", "source-map-explorer": "^2.5.3", "vite": "^6.3.5", - "vite-plugin-babel": "^1.3.1", + "vite-plugin-babel": "^1.3.2", "vite-plugin-eslint": "^1.8.1", "vite-plugin-node-polyfills": "^0.23.0", "vite-plugin-pwa": "^1.0.0", @@ -3746,9 +3746,9 @@ } }, "node_modules/@rc-component/trigger": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.2.6.tgz", - "integrity": "sha512-/9zuTnWwhQ3S3WT1T8BubuFTT46kvnXgaERR9f4BTKyn61/wpf/BvbImzYBubzJibU707FxwbKszLlHjcLiv1Q==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.2.7.tgz", + "integrity": "sha512-Qggj4Z0AA2i5dJhzlfFSmg1Qrziu8dsdHOihROL5Kl18seO2Eh/ZaTYt2c8a/CyGaTChnFry7BEYew1+/fhSbA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.2", @@ -4466,50 +4466,50 @@ "license": "MIT" }, "node_modules/@sentry-internal/browser-utils": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.31.0.tgz", - "integrity": "sha512-rviu/jUmeQbY4rSO8l4pubOtRIhFtH5Gu/ryRNMTlpJRdomp4uxddqthHUDH5g6xCXZsMTyJEIdx0aTqbgr/GQ==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.32.0.tgz", + "integrity": "sha512-mVWdruSWXF+2WgS24jwLhWFyC/nDQbKXseLR8paU9LGSnVtlBlQseIx1GrANbJrhBxiEWSft4WiuxU34wPsbXg==", "license": "MIT", "dependencies": { - "@sentry/core": "9.31.0" + "@sentry/core": "9.32.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/feedback": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.31.0.tgz", - "integrity": "sha512-Ygi/8UZ7p2B4DhXQjZDtOc45vNUHkfk2XETBTBGkByEQkE8vygzSiKhgRcnVpzwq+8xKFMRy+PxvpcCo+PNQew==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.32.0.tgz", + "integrity": "sha512-OaXaovXqlhN1sG2wtJMhxMEjyeuK7RwY57o96LgKE0bWM//Fs9WWCOkGa+7l8TOf0+0ib7gfhJZlpN0hlqOgRw==", "license": "MIT", "dependencies": { - "@sentry/core": "9.31.0" + "@sentry/core": "9.32.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/replay": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.31.0.tgz", - "integrity": "sha512-V5rvcO/xSj8JMw4ZnZT2cBYC+UOuIiZ2Flj4EoIurxMrTgowE1uMXUBA32EBfuB5/vQSJXB6W5uAudhk7LjBPQ==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.32.0.tgz", + "integrity": "sha512-mOHUKjUtHbEwshikrCQPM1ZqWAMUEcpEGashnXQp3KQivvbTxrExiNnt6XK5TjJyGvsI3A907Bp/HvEzgneYgQ==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "9.31.0", - "@sentry/core": "9.31.0" + "@sentry-internal/browser-utils": "9.32.0", + "@sentry/core": "9.32.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/replay-canvas": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.31.0.tgz", - "integrity": "sha512-VGqfvQCIuXQZeecrBf8bd4sj8lYGzUA/2CffTAkad1nB1Onyz0Kzo54qLWemivCxA3ufHf6DCpNA3Loa/0ywFQ==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.32.0.tgz", + "integrity": "sha512-tu+coeTRpJxknmWPMJC2jqmIM5IsVoRn9gEDdkSrcPbgx/GwgE03fSJVBJL1tOEA8yRNIhZPMR86ORE7/7n2ow==", "license": "MIT", "dependencies": { - "@sentry-internal/replay": "9.31.0", - "@sentry/core": "9.31.0" + "@sentry-internal/replay": "9.32.0", + "@sentry/core": "9.32.0" }, "engines": { "node": ">=18" @@ -4525,16 +4525,16 @@ } }, "node_modules/@sentry/browser": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.31.0.tgz", - "integrity": "sha512-DzG72JJTqHzE0Qo2fHeHm3xgFs97InaSQStmTMxOA59yPqvAXbweNPcsgCNu1q76+jZyaJcoy1qOwahnLuEVDg==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.32.0.tgz", + "integrity": "sha512-BzPogpH87n+sC9VPfXaXkiKJtagLpIB87LGg1hSBURpwGx6Rt2ORmaVYgwwuuFZX8Hia727IIM7pbcbNfrXGRQ==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "9.31.0", - "@sentry-internal/feedback": "9.31.0", - "@sentry-internal/replay": "9.31.0", - "@sentry-internal/replay-canvas": "9.31.0", - "@sentry/core": "9.31.0" + "@sentry-internal/browser-utils": "9.32.0", + "@sentry-internal/feedback": "9.32.0", + "@sentry-internal/replay": "9.32.0", + "@sentry-internal/replay-canvas": "9.32.0", + "@sentry/core": "9.32.0" }, "engines": { "node": ">=18" @@ -4911,22 +4911,22 @@ } }, "node_modules/@sentry/core": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.31.0.tgz", - "integrity": "sha512-6JeoPGvBgT9m2YFIf2CrW+KrrOYzUqb9+Xwr/Dw25kPjVKy+WJjWqK8DKCNLgkBA22OCmSOmHuRwFR0YxGVdZQ==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.32.0.tgz", + "integrity": "sha512-1wAXMMmeY4Ny2MJBCuri3b4LMVPjqXdgbVgTxxipGW+gzPsjv+8+LCSnJAR/cRBr8JoXV+qGC2tE06rI1XDj3A==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/@sentry/react": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.31.0.tgz", - "integrity": "sha512-cZT/AwRiawRED7pB4Ug6ZRbcWd92HQxOPc12KKe5ZUQFEc9jUqH6HqwzQUSMzkg86NrE9Hc6XXga+JZ3Q1Lzow==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.32.0.tgz", + "integrity": "sha512-4d13sA/e9oEEK9cB6DZxVNDLTw9Q2x0WzhKtit6jhFKv1ItQ61Uu+euBJLfy3yCzFGl7PJbfJViMt2bhqjkTuA==", "license": "MIT", "dependencies": { - "@sentry/browser": "9.31.0", - "@sentry/core": "9.31.0", + "@sentry/browser": "9.32.0", + "@sentry/core": "9.32.0", "hoist-non-react-statics": "^3.3.2" }, "engines": { @@ -6120,9 +6120,9 @@ } }, "node_modules/antd": { - "version": "5.26.1", - "resolved": "https://registry.npmjs.org/antd/-/antd-5.26.1.tgz", - "integrity": "sha512-CiLGZ2Ftld+fuoj+U3OL8uouuqUppqFJnW4O/4bOgSWzM9XsJGibpNtUa9QArhrZ5ndfnzlPP/4RVXUK/xfSvQ==", + "version": "5.26.2", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.26.2.tgz", + "integrity": "sha512-C8dBgwSzXfUS5ousUN+mfcaGFhEOd9wuyhvmw0lQnU9gukpRoFe1B0UKzvr6Z50QgapIl+s03nYlQJUghKqVjQ==", "license": "MIT", "dependencies": { "@ant-design/colors": "^7.2.1", @@ -6136,7 +6136,7 @@ "@rc-component/mutate-observer": "^1.1.0", "@rc-component/qrcode": "~1.0.0", "@rc-component/tour": "~1.15.1", - "@rc-component/trigger": "^2.2.6", + "@rc-component/trigger": "^2.2.7", "classnames": "^2.5.1", "copy-to-clipboard": "^3.3.3", "dayjs": "^1.11.11", @@ -6164,7 +6164,7 @@ "rc-slider": "~11.1.8", "rc-steps": "~6.0.1", "rc-switch": "~4.1.0", - "rc-table": "~7.51.0", + "rc-table": "~7.51.1", "rc-tabs": "~15.6.1", "rc-textarea": "~1.10.0", "rc-tooltip": "~6.4.0", @@ -6998,9 +6998,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", - "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "funding": [ { "type": "opencollective", @@ -7017,8 +7017,8 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001718", - "electron-to-chromium": "^1.5.160", + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -7197,9 +7197,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001721", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", - "integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==", + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", "funding": [ { "type": "opencollective", @@ -8361,9 +8361,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.165", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz", - "integrity": "sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==", + "version": "1.5.176", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.176.tgz", + "integrity": "sha512-2nDK9orkm7M9ZZkjO3PjbEd3VUulQLyg5T9O3enJdFvUg46Hzd4DUvTvAuEgbdHYXyFsiG4A5sO9IzToMH1cDg==", "license": "ISC" }, "node_modules/elliptic": { @@ -14138,9 +14138,9 @@ } }, "node_modules/rc-table": { - "version": "7.51.0", - "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.51.0.tgz", - "integrity": "sha512-7ZlvW6lB0IDKaSFInD6OfJsCepSJJtfsQv2PZLtzEeZd/PLzQnKliXPaoZqkqDdLdJ3jxE2x4sane4DjxcAg+g==", + "version": "7.51.1", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.51.1.tgz", + "integrity": "sha512-5iq15mTHhvC42TlBLRCoCBLoCmGlbRZAlyF21FonFnS/DIC8DeRqnmdyVREwt2CFbPceM0zSNdEeVfiGaqYsKw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -17625,14 +17625,14 @@ "license": "MIT" }, "node_modules/vite-plugin-babel": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/vite-plugin-babel/-/vite-plugin-babel-1.3.1.tgz", - "integrity": "sha512-ikAdgkYQS6ytr6KGmfIbEDES0gBMtw0tUtiwIe8/LEk/ndISFy6IR2MjQUMksirCtrWnqCrixbGKTw2ezOIMrA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/vite-plugin-babel/-/vite-plugin-babel-1.3.2.tgz", + "integrity": "sha512-mEld4OVyuNs5+ISN+U5XyTnNcDwln/s2oER2m0PQ32YYPqPR25E3mfnhAA/RkZJxPuwFkprKWV405aZArE6kzA==", "dev": true, "license": "MIT", "peerDependencies": { "@babel/core": "^7.0.0", - "vite": "^2.7.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" + "vite": "^2.7.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "node_modules/vite-plugin-ejs": { diff --git a/client/package.json b/client/package.json index 67de920bf..14524131a 100644 --- a/client/package.json +++ b/client/package.json @@ -20,11 +20,11 @@ "@jsreport/browser-client": "^3.1.0", "@reduxjs/toolkit": "^2.8.2", "@sentry/cli": "^2.46.0", - "@sentry/react": "^9.31.0", + "@sentry/react": "^9.32.0", "@sentry/vite-plugin": "^3.5.0", "@splitsoftware/splitio-react": "^2.3.1", "@tanem/react-nprogress": "^5.0.53", - "antd": "^5.26.1", + "antd": "^5.26.2", "apollo-link-logger": "^2.0.1", "apollo-link-sentry": "^4.3.0", "autosize": "^6.0.1", @@ -141,7 +141,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@vitejs/plugin-react": "^4.6.0", - "browserslist": "^4.25.0", + "browserslist": "^4.25.1", "browserslist-to-esbuild": "^2.1.1", "chalk": "^5.4.1", "eslint": "^8.57.1", @@ -156,7 +156,7 @@ "redux-logger": "^3.0.6", "source-map-explorer": "^2.5.3", "vite": "^6.3.5", - "vite-plugin-babel": "^1.3.1", + "vite-plugin-babel": "^1.3.2", "vite-plugin-eslint": "^1.8.1", "vite-plugin-node-polyfills": "^0.23.0", "vite-plugin-pwa": "^1.0.0", diff --git a/client/src/pages/simplified-parts/simplified-parts.page.component.jsx b/client/src/pages/simplified-parts/simplified-parts.page.component.jsx index aa906834f..9603fd624 100644 --- a/client/src/pages/simplified-parts/simplified-parts.page.component.jsx +++ b/client/src/pages/simplified-parts/simplified-parts.page.component.jsx @@ -14,11 +14,11 @@ import PrintCenterModalContainer from "../../components/print-center-modal/print import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component.jsx"; import UpdateAlert from "../../components/update-alert/update-alert.component.jsx"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; -import { useSocket } from "../../contexts/SocketIO/useSocket.js"; import { addAlerts } from "../../redux/application/application.actions.js"; import { selectAlerts } from "../../redux/application/application.selectors.js"; import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors.js"; import InstanceRenderManager from "../../utils/instanceRenderMgr.js"; + const SimplifiedPartsJobsPage = lazy(() => import("../simplified-parts-jobs/simplified-parts-jobs.page.jsx")); const SimplifiedPartsJobsDetailPage = lazy( () => import("../simplified-parts-jobs-detail/simplified-parts-jobs-detail.container.jsx") @@ -49,7 +49,6 @@ const mapDispatchToProps = (dispatch) => ({ export function SimplifiedPartsPage({ conflict, bodyshop, alerts, setAlerts }) { const { t } = useTranslation(); - const { socket, clientId } = useSocket(); const notification = useNotification(); // State to track displayed alerts diff --git a/package-lock.json b/package-lock.json index ac831e2ce..9014fd22d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,14 +9,14 @@ "version": "0.2.0", "license": "UNLICENSED", "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "^3.832.0", - "@aws-sdk/client-elasticache": "^3.830.0", - "@aws-sdk/client-s3": "^3.832.0", - "@aws-sdk/client-secrets-manager": "^3.830.0", - "@aws-sdk/client-ses": "^3.830.0", - "@aws-sdk/credential-provider-node": "^3.830.0", - "@aws-sdk/lib-storage": "^3.832.0", - "@aws-sdk/s3-request-presigner": "^3.832.0", + "@aws-sdk/client-cloudwatch-logs": "^3.835.0", + "@aws-sdk/client-elasticache": "^3.835.0", + "@aws-sdk/client-s3": "^3.837.0", + "@aws-sdk/client-secrets-manager": "^3.835.0", + "@aws-sdk/client-ses": "^3.835.0", + "@aws-sdk/credential-provider-node": "^3.835.0", + "@aws-sdk/lib-storage": "^3.837.0", + "@aws-sdk/s3-request-presigner": "^3.837.0", "@opensearch-project/opensearch": "^2.13.0", "@socket.io/admin-ui": "^0.5.1", "@socket.io/redis-adapter": "^8.3.0", @@ -73,7 +73,7 @@ "globals": "^15.15.0", "mock-require": "^3.0.3", "p-limit": "^3.1.0", - "prettier": "^3.6.0", + "prettier": "^3.6.1", "supertest": "^7.1.1", "vitest": "^3.2.4" }, @@ -285,24 +285,24 @@ } }, "node_modules/@aws-sdk/client-cloudwatch-logs": { - "version": "3.832.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.832.0.tgz", - "integrity": "sha512-Xpu1HKMafA9j7j/ttvKYcv0a4XRFCOBQFjKvsZTrKeGZoVzoEP+IKBfy8+pGI3zLZEm5N6J5d1ifXbZ+9F0icA==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.835.0.tgz", + "integrity": "sha512-lR08TngWAszUUEW1utaPfLLbDJF5BQVBDclvZF0ke1a4C0o3nU2HyoWy/A7fQJEOXGfiegABdqtbi9w3UHjibA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.826.0", - "@aws-sdk/credential-provider-node": "3.830.0", + "@aws-sdk/core": "3.835.0", + "@aws-sdk/credential-provider-node": "3.835.0", "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.828.0", + "@aws-sdk/middleware-user-agent": "3.835.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.828.0", "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.828.0", + "@aws-sdk/util-user-agent-node": "3.835.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.5.3", "@smithy/eventstream-serde-browser": "^4.0.4", @@ -312,24 +312,24 @@ "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.11", - "@smithy/middleware-retry": "^4.1.12", + "@smithy/middleware-endpoint": "^4.1.12", + "@smithy/middleware-retry": "^4.1.13", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", + "@smithy/smithy-client": "^4.4.4", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.19", - "@smithy/util-defaults-mode-node": "^4.0.19", + "@smithy/util-defaults-mode-browser": "^4.0.20", + "@smithy/util-defaults-mode-node": "^4.0.20", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.5", + "@smithy/util-retry": "^4.0.6", "@smithy/util-utf8": "^4.0.0", "@types/uuid": "^9.0.1", "tslib": "^2.6.2", @@ -353,48 +353,48 @@ } }, "node_modules/@aws-sdk/client-elasticache": { - "version": "3.830.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-elasticache/-/client-elasticache-3.830.0.tgz", - "integrity": "sha512-ln7OISYRUasEL54B0+UEeJLISd3vG2zkdRCCIEVUzh7SOGiHADgCaQAk6WFiGAy4F9uGUWiI5qDkvddTBZT3tw==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-elasticache/-/client-elasticache-3.835.0.tgz", + "integrity": "sha512-R3tSQ0VXOtfe4cMFXmtxbfXF6UyBEb99eY4+KBSO9V79wQw5iQGX/jrruWRgSo9r6sxz68kXqkGuxgKOg+D8VQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.826.0", - "@aws-sdk/credential-provider-node": "3.830.0", + "@aws-sdk/core": "3.835.0", + "@aws-sdk/credential-provider-node": "3.835.0", "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.828.0", + "@aws-sdk/middleware-user-agent": "3.835.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.828.0", "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.828.0", + "@aws-sdk/util-user-agent-node": "3.835.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.5.3", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.11", - "@smithy/middleware-retry": "^4.1.12", + "@smithy/middleware-endpoint": "^4.1.12", + "@smithy/middleware-retry": "^4.1.13", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", + "@smithy/smithy-client": "^4.4.4", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.19", - "@smithy/util-defaults-mode-node": "^4.0.19", + "@smithy/util-defaults-mode-browser": "^4.0.20", + "@smithy/util-defaults-mode-node": "^4.0.20", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.5", + "@smithy/util-retry": "^4.0.6", "@smithy/util-utf8": "^4.0.0", "@smithy/util-waiter": "^4.0.5", "tslib": "^2.6.2" @@ -404,32 +404,32 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.832.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.832.0.tgz", - "integrity": "sha512-S+md1zCe71SEuaRDuLHq4mzhYYkVxR1ENa8NwrgInfYoC4xo8/pESoR6i0ZZpcLs0Jw4EyVInWYs4GgDHW70qQ==", + "version": "3.837.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.837.0.tgz", + "integrity": "sha512-sBjPPG30HIfNwpzWuajCDf7agb4YAxPFFpsp3kwgptJF8PEi0HzQg64bskquMzjqLC2tXsn5rKtDVpQOvs29MQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.826.0", - "@aws-sdk/credential-provider-node": "3.830.0", + "@aws-sdk/core": "3.835.0", + "@aws-sdk/credential-provider-node": "3.835.0", "@aws-sdk/middleware-bucket-endpoint": "3.830.0", "@aws-sdk/middleware-expect-continue": "3.821.0", - "@aws-sdk/middleware-flexible-checksums": "3.826.0", + "@aws-sdk/middleware-flexible-checksums": "3.835.0", "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-location-constraint": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-sdk-s3": "3.826.0", + "@aws-sdk/middleware-sdk-s3": "3.835.0", "@aws-sdk/middleware-ssec": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.828.0", + "@aws-sdk/middleware-user-agent": "3.835.0", "@aws-sdk/region-config-resolver": "3.821.0", - "@aws-sdk/signature-v4-multi-region": "3.826.0", + "@aws-sdk/signature-v4-multi-region": "3.835.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.828.0", "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.828.0", + "@aws-sdk/util-user-agent-node": "3.835.0", "@aws-sdk/xml-builder": "3.821.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.5.3", @@ -443,24 +443,24 @@ "@smithy/invalid-dependency": "^4.0.4", "@smithy/md5-js": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.11", - "@smithy/middleware-retry": "^4.1.12", + "@smithy/middleware-endpoint": "^4.1.12", + "@smithy/middleware-retry": "^4.1.13", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", + "@smithy/smithy-client": "^4.4.4", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.19", - "@smithy/util-defaults-mode-node": "^4.0.19", + "@smithy/util-defaults-mode-browser": "^4.0.20", + "@smithy/util-defaults-mode-node": "^4.0.20", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.5", + "@smithy/util-retry": "^4.0.6", "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", "@smithy/util-waiter": "^4.0.5", @@ -486,48 +486,48 @@ } }, "node_modules/@aws-sdk/client-secrets-manager": { - "version": "3.830.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.830.0.tgz", - "integrity": "sha512-St2EK5i91vwv9LmDUmWevZYl+Y/TYRP/AHm7gxZm1LkEf1VEjkSizUMm91JOnH6y+0Clok9mqe6jZ/XossMXlw==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.835.0.tgz", + "integrity": "sha512-w8xIFhxP54kRdmTuRjxOAgNU7MCSgVieXx5pUxMD6B92dpqDTjnVFgTDX8fpUFZSrSwe5dOCiHEDKZsV20YNaQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.826.0", - "@aws-sdk/credential-provider-node": "3.830.0", + "@aws-sdk/core": "3.835.0", + "@aws-sdk/credential-provider-node": "3.835.0", "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.828.0", + "@aws-sdk/middleware-user-agent": "3.835.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.828.0", "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.828.0", + "@aws-sdk/util-user-agent-node": "3.835.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.5.3", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.11", - "@smithy/middleware-retry": "^4.1.12", + "@smithy/middleware-endpoint": "^4.1.12", + "@smithy/middleware-retry": "^4.1.13", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", + "@smithy/smithy-client": "^4.4.4", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.19", - "@smithy/util-defaults-mode-node": "^4.0.19", + "@smithy/util-defaults-mode-browser": "^4.0.20", + "@smithy/util-defaults-mode-node": "^4.0.20", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.5", + "@smithy/util-retry": "^4.0.6", "@smithy/util-utf8": "^4.0.0", "@types/uuid": "^9.0.1", "tslib": "^2.6.2", @@ -551,48 +551,48 @@ } }, "node_modules/@aws-sdk/client-ses": { - "version": "3.830.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.830.0.tgz", - "integrity": "sha512-Y2XaJkqHJ7qM4cpCw3YS96fMZgT44mP3HLP+9dU0ct29L+iwf3zhigJGQzakieMdJfuTFZe7Vi6s1RbcWv5v5w==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.835.0.tgz", + "integrity": "sha512-Eugl8TqnvNWs2i38XVXwiLKWIreRdiOrIaf2lCPgowKZqw6yvLG6+Yc3yABzZZ5bnUZdDHt1pYfIMUbSXLGdAw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.826.0", - "@aws-sdk/credential-provider-node": "3.830.0", + "@aws-sdk/core": "3.835.0", + "@aws-sdk/credential-provider-node": "3.835.0", "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.828.0", + "@aws-sdk/middleware-user-agent": "3.835.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.828.0", "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.828.0", + "@aws-sdk/util-user-agent-node": "3.835.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.5.3", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.11", - "@smithy/middleware-retry": "^4.1.12", + "@smithy/middleware-endpoint": "^4.1.12", + "@smithy/middleware-retry": "^4.1.13", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", + "@smithy/smithy-client": "^4.4.4", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.19", - "@smithy/util-defaults-mode-node": "^4.0.19", + "@smithy/util-defaults-mode-browser": "^4.0.20", + "@smithy/util-defaults-mode-node": "^4.0.20", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.5", + "@smithy/util-retry": "^4.0.6", "@smithy/util-utf8": "^4.0.0", "@smithy/util-waiter": "^4.0.5", "tslib": "^2.6.2" @@ -602,47 +602,47 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.830.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.830.0.tgz", - "integrity": "sha512-5zCEpfI+zwX2SIa258L+TItNbBoAvQQ6w74qdFM6YJufQ1F9tvwjTX8T+eSTT9nsFIvfYnUaGalWwJVfmJUgVQ==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.835.0.tgz", + "integrity": "sha512-4J19IcBKU5vL8yw/YWEvbwEGcmCli0rpRyxG53v0K5/3weVPxVBbKfkWcjWVQ4qdxNz2uInfbTde4BRBFxWllQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.826.0", + "@aws-sdk/core": "3.835.0", "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.828.0", + "@aws-sdk/middleware-user-agent": "3.835.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.828.0", "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.828.0", + "@aws-sdk/util-user-agent-node": "3.835.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.5.3", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.11", - "@smithy/middleware-retry": "^4.1.12", + "@smithy/middleware-endpoint": "^4.1.12", + "@smithy/middleware-retry": "^4.1.13", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", + "@smithy/smithy-client": "^4.4.4", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.19", - "@smithy/util-defaults-mode-node": "^4.0.19", + "@smithy/util-defaults-mode-browser": "^4.0.20", + "@smithy/util-defaults-mode-node": "^4.0.20", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.5", + "@smithy/util-retry": "^4.0.6", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, @@ -651,9 +651,9 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.826.0.tgz", - "integrity": "sha512-BGbQYzWj3ps+dblq33FY5tz/SsgJCcXX0zjQlSC07tYvU1jHTUvsefphyig+fY38xZ4wdKjbTop+KUmXUYrOXw==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.835.0.tgz", + "integrity": "sha512-7mnf4xbaLI8rkDa+w6fUU48dG6yDuOgLXEPe4Ut3SbMp1ceJBPMozNHbCwkiyHk3HpxZYf8eVy0wXhJMrxZq5w==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.821.0", @@ -663,7 +663,7 @@ "@smithy/property-provider": "^4.0.4", "@smithy/protocol-http": "^5.1.2", "@smithy/signature-v4": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", + "@smithy/smithy-client": "^4.4.4", "@smithy/types": "^4.3.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", @@ -677,12 +677,12 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.826.0.tgz", - "integrity": "sha512-DK3pQY8+iKK3MGDdC3uOZQ2psU01obaKlTYhEwNu4VWzgwQL4Vi3sWj4xSWGEK41vqZxiRLq6fOq7ysRI+qEZA==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.835.0.tgz", + "integrity": "sha512-U9LFWe7+ephNyekpUbzT7o6SmJTmn6xkrPkE0D7pbLojnPVi/8SZKyjtgQGIsAv+2kFkOCqMOIYUKd/0pE7uew==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.826.0", + "@aws-sdk/core": "3.835.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", @@ -693,18 +693,18 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.826.0.tgz", - "integrity": "sha512-N+IVZBh+yx/9GbMZTKO/gErBi/FYZQtcFRItoLbY+6WU+0cSWyZYfkoeOxHmQV3iX9k65oljERIWUmL9x6OSQg==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.835.0.tgz", + "integrity": "sha512-jCdNEsQklil7frDm/BuVKl4ubVoQHRbV6fnkOjmxAJz0/v7cR8JP0jBGlqKKzh3ROh5/vo1/5VUZbCTLpc9dSg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.826.0", + "@aws-sdk/core": "3.835.0", "@aws-sdk/types": "3.821.0", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/node-http-handler": "^4.0.6", "@smithy/property-provider": "^4.0.4", "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", + "@smithy/smithy-client": "^4.4.4", "@smithy/types": "^4.3.1", "@smithy/util-stream": "^4.2.2", "tslib": "^2.6.2" @@ -714,18 +714,18 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.830.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.830.0.tgz", - "integrity": "sha512-zeQenzvh8JRY5nULd8izdjVGoCM1tgsVVsrLSwDkHxZTTW0hW/bmOmXfvdaE0wDdomXW7m2CkQDSmP7XdvNXZg==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.835.0.tgz", + "integrity": "sha512-nqF6rYRAnJedmvDfrfKygzyeADcduDvtvn7GlbQQbXKeR2l7KnCdhuxHa0FALLvspkHiBx7NtInmvnd5IMuWsw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.826.0", - "@aws-sdk/credential-provider-env": "3.826.0", - "@aws-sdk/credential-provider-http": "3.826.0", - "@aws-sdk/credential-provider-process": "3.826.0", - "@aws-sdk/credential-provider-sso": "3.830.0", - "@aws-sdk/credential-provider-web-identity": "3.830.0", - "@aws-sdk/nested-clients": "3.830.0", + "@aws-sdk/core": "3.835.0", + "@aws-sdk/credential-provider-env": "3.835.0", + "@aws-sdk/credential-provider-http": "3.835.0", + "@aws-sdk/credential-provider-process": "3.835.0", + "@aws-sdk/credential-provider-sso": "3.835.0", + "@aws-sdk/credential-provider-web-identity": "3.835.0", + "@aws-sdk/nested-clients": "3.835.0", "@aws-sdk/types": "3.821.0", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/property-provider": "^4.0.4", @@ -738,17 +738,17 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.830.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.830.0.tgz", - "integrity": "sha512-X/2LrTgwtK1pkWrvofxQBI8VTi6QVLtSMpsKKPPnJQ0vgqC0e4czSIs3ZxiEsOkCBaQ2usXSiKyh0ccsQ6k2OA==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.835.0.tgz", + "integrity": "sha512-77B8elyZlaEd7vDYyCnYtVLuagIBwuJ0AQ98/36JMGrYX7TT8UVAhiDAfVe0NdUOMORvDNFfzL06VBm7wittYw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.826.0", - "@aws-sdk/credential-provider-http": "3.826.0", - "@aws-sdk/credential-provider-ini": "3.830.0", - "@aws-sdk/credential-provider-process": "3.826.0", - "@aws-sdk/credential-provider-sso": "3.830.0", - "@aws-sdk/credential-provider-web-identity": "3.830.0", + "@aws-sdk/credential-provider-env": "3.835.0", + "@aws-sdk/credential-provider-http": "3.835.0", + "@aws-sdk/credential-provider-ini": "3.835.0", + "@aws-sdk/credential-provider-process": "3.835.0", + "@aws-sdk/credential-provider-sso": "3.835.0", + "@aws-sdk/credential-provider-web-identity": "3.835.0", "@aws-sdk/types": "3.821.0", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/property-provider": "^4.0.4", @@ -761,12 +761,12 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.826.0.tgz", - "integrity": "sha512-kURrc4amu3NLtw1yZw7EoLNEVhmOMRUTs+chaNcmS+ERm3yK0nKjaJzmKahmwlTQTSl3wJ8jjK7x962VPo+zWw==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.835.0.tgz", + "integrity": "sha512-qXkTt5pAhSi2Mp9GdgceZZFo/cFYrA735efqi/Re/nf0lpqBp8mRM8xv+iAaPHV4Q10q0DlkbEidT1DhxdT/+w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.826.0", + "@aws-sdk/core": "3.835.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", @@ -778,14 +778,14 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.830.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.830.0.tgz", - "integrity": "sha512-+VdRpZmfekzpySqZikAKx6l5ndnLGluioIgUG4ZznrButgFD/iogzFtGmBDFB3ZLViX1l4pMXru0zFwJEZT21Q==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.835.0.tgz", + "integrity": "sha512-jAiEMryaPFXayYGszrc7NcgZA/zrrE3QvvvUBh/Udasg+9Qp5ZELdJCm/p98twNyY9n5i6Ex6VgvdxZ7+iEheQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.830.0", - "@aws-sdk/core": "3.826.0", - "@aws-sdk/token-providers": "3.830.0", + "@aws-sdk/client-sso": "3.835.0", + "@aws-sdk/core": "3.835.0", + "@aws-sdk/token-providers": "3.835.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", @@ -797,13 +797,13 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.830.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.830.0.tgz", - "integrity": "sha512-hPYrKsZeeOdLROJ59T6Y8yZ0iwC/60L3qhZXjapBFjbqBtMaQiMTI645K6xVXBioA6vxXq7B4aLOhYqk6Fy/Ww==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.835.0.tgz", + "integrity": "sha512-zfleEFXDLlcJ7cyfS4xSyCRpd8SVlYZfH3rp0pg2vPYKbnmXVE0r+gPIYXl4L+Yz4A2tizYl63nKCNdtbxadog==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.826.0", - "@aws-sdk/nested-clients": "3.830.0", + "@aws-sdk/core": "3.835.0", + "@aws-sdk/nested-clients": "3.835.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", @@ -814,14 +814,14 @@ } }, "node_modules/@aws-sdk/lib-storage": { - "version": "3.832.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.832.0.tgz", - "integrity": "sha512-NM+q0WD8TCreo+tvKy0AZytHQQC19zXVG8iapDhafLNs1W72zAQ659pTfVzsC00Zvwtsp0LI/b2FWTjkjTAAdA==", + "version": "3.837.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.837.0.tgz", + "integrity": "sha512-V7NkOw8bX1HdRTWSy+pMCpHQgSaUh/l1fQIx63anu4TORe18pkLkos0x5YnPJ+o2ksbCVtokDuOG1jGdVM0NPg==", "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.11", - "@smithy/smithy-client": "^4.4.3", + "@smithy/middleware-endpoint": "^4.1.12", + "@smithy/smithy-client": "^4.4.4", "buffer": "5.6.0", "events": "3.3.0", "stream-browserify": "3.0.0", @@ -831,7 +831,7 @@ "node": ">=18.0.0" }, "peerDependencies": { - "@aws-sdk/client-s3": "^3.832.0" + "@aws-sdk/client-s3": "^3.837.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { @@ -868,15 +868,15 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.826.0.tgz", - "integrity": "sha512-Fz9w8CFYPfSlHEB6feSsi06hdS+s+FB8k5pO4L7IV0tUa78mlhxF/VNlAJaVWYyOkZXl4HPH2K48aapACSQOXw==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.835.0.tgz", + "integrity": "sha512-9ezorQYlr5cQY28zWAReFhNKUTaXsi3TMvXIagMRrSeWtQ7R6TCYnt91xzHRCmFR2kp3zLI+dfoeH+wF3iCKUw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "3.826.0", + "@aws-sdk/core": "3.835.0", "@aws-sdk/types": "3.821.0", "@smithy/is-array-buffer": "^4.0.0", "@smithy/node-config-provider": "^4.1.3", @@ -950,19 +950,19 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.826.0.tgz", - "integrity": "sha512-8F0qWaYKfvD/de1AKccXuigM+gb/IZSncCqxdnFWqd+TFzo9qI9Hh+TpUhWOMYSgxsMsYQ8ipmLzlD/lDhjrmA==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.835.0.tgz", + "integrity": "sha512-oPebxpVf9smInHhevHh3APFZagGU+4RPwXEWv9YtYapFvsMq+8QXFvOfxfVZ/mwpe0JVG7EiJzL9/9Kobmts8Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.826.0", + "@aws-sdk/core": "3.835.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-arn-parser": "3.804.0", "@smithy/core": "^3.5.3", "@smithy/node-config-provider": "^4.1.3", "@smithy/protocol-http": "^5.1.2", "@smithy/signature-v4": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", + "@smithy/smithy-client": "^4.4.4", "@smithy/types": "^4.3.1", "@smithy/util-config-provider": "^4.0.0", "@smithy/util-middleware": "^4.0.4", @@ -989,12 +989,12 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.828.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.828.0.tgz", - "integrity": "sha512-nixvI/SETXRdmrVab4D9LvXT3lrXkwAWGWk2GVvQvzlqN1/M/RfClj+o37Sn4FqRkGH9o9g7Fqb1YqZ4mqDAtA==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.835.0.tgz", + "integrity": "sha512-2gmAYygeE/gzhyF2XlkcbMLYFTbNfV61n+iCFa/ZofJHXYE+RxSyl5g4kujLEs7bVZHmjQZJXhprVSkGccq3/w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.826.0", + "@aws-sdk/core": "3.835.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.828.0", "@smithy/core": "^3.5.3", @@ -1007,47 +1007,47 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.830.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.830.0.tgz", - "integrity": "sha512-5N5YTlBr1vtxf7+t+UaIQ625KEAmm7fY9o1e3MgGOi/paBoI0+axr3ud24qLIy0NSzFlAHEaxUSWxcERNjIoZw==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.835.0.tgz", + "integrity": "sha512-UtmOO0U5QkicjCEv+B32qqRAnS7o2ZkZhC+i3ccH1h3fsfaBshpuuNBwOYAzRCRBeKW5fw3ANFrV/+2FTp4jWg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.826.0", + "@aws-sdk/core": "3.835.0", "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.828.0", + "@aws-sdk/middleware-user-agent": "3.835.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.828.0", "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.828.0", + "@aws-sdk/util-user-agent-node": "3.835.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.5.3", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.11", - "@smithy/middleware-retry": "^4.1.12", + "@smithy/middleware-endpoint": "^4.1.12", + "@smithy/middleware-retry": "^4.1.13", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", + "@smithy/smithy-client": "^4.4.4", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.19", - "@smithy/util-defaults-mode-node": "^4.0.19", + "@smithy/util-defaults-mode-browser": "^4.0.20", + "@smithy/util-defaults-mode-node": "^4.0.20", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.5", + "@smithy/util-retry": "^4.0.6", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, @@ -1073,17 +1073,17 @@ } }, "node_modules/@aws-sdk/s3-request-presigner": { - "version": "3.832.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.832.0.tgz", - "integrity": "sha512-zXuwfaAYu99LUF7/6iBr3UlKCMaMImBwfmLXJQlvtE3ebrERXQuISME9Vjd2oG+hJ6XcX6RJqkeIvZBytMzvRw==", + "version": "3.837.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.837.0.tgz", + "integrity": "sha512-h/D/cqeciBPGFSHIHRQm0q/CDvToV4rUoPef3tWzYtfoKzqfYaqRO175FnDv/4XgOYpdoqv6q36bx8KueVQ62w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/signature-v4-multi-region": "3.826.0", + "@aws-sdk/signature-v4-multi-region": "3.835.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-format-url": "3.821.0", - "@smithy/middleware-endpoint": "^4.1.11", + "@smithy/middleware-endpoint": "^4.1.12", "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", + "@smithy/smithy-client": "^4.4.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, @@ -1092,12 +1092,12 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.826.0.tgz", - "integrity": "sha512-3fEi/zy6tpMzomYosksGtu7jZqGFcdBXoL7YRsG7OEeQzBbOW9B+fVaQZ4jnsViSjzA/yKydLahMrfPnt+iaxg==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.835.0.tgz", + "integrity": "sha512-rEtJH4dIwJYlXXe5rIH+uTCQmd2VIjuaoHlDY3Dr4nxF6po6U7vKsLfybIU2tgflGVqoqYQnXsfW/kj/Rh+/ow==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.826.0", + "@aws-sdk/middleware-sdk-s3": "3.835.0", "@aws-sdk/types": "3.821.0", "@smithy/protocol-http": "^5.1.2", "@smithy/signature-v4": "^5.1.2", @@ -1109,13 +1109,13 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.830.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.830.0.tgz", - "integrity": "sha512-aJ4guFwj92nV9D+EgJPaCFKK0I3y2uMchiDfh69Zqnmwfxxxfxat6F79VA7PS0BdbjRfhLbn+Ghjftnomu2c1g==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.835.0.tgz", + "integrity": "sha512-zN1P3BE+Rv7w7q/CDA8VCQox6SE9QTn0vDtQ47AHA3eXZQQgYzBqgoLgJxR9rKKBIRGZqInJa/VRskLL95VliQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.826.0", - "@aws-sdk/nested-clients": "3.830.0", + "@aws-sdk/core": "3.835.0", + "@aws-sdk/nested-clients": "3.835.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", @@ -1206,12 +1206,12 @@ } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.828.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.828.0.tgz", - "integrity": "sha512-LdN6fTBzTlQmc8O8f1wiZN0qF3yBWVGis7NwpWK7FUEzP9bEZRxYfIkV9oV9zpt6iNRze1SedK3JQVB/udxBoA==", + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.835.0.tgz", + "integrity": "sha512-gY63QZ4W5w9JYHYuqvUxiVGpn7IbCt1ODPQB0ZZwGGr3WRmK+yyZxCtFjbYhEQDQLgTWpf8YgVxgQLv2ps0PJg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.828.0", + "@aws-sdk/middleware-user-agent": "3.835.0", "@aws-sdk/types": "3.821.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/types": "^4.3.1", @@ -2916,9 +2916,9 @@ } }, "node_modules/@smithy/core": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.3.tgz", - "integrity": "sha512-xa5byV9fEguZNofCclv6v9ra0FYh5FATQW/da7FQUVTic94DfrN/NvmKZjrMyzbpqfot9ZjBaO8U1UeTbmSLuA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.6.0.tgz", + "integrity": "sha512-Pgvfb+TQ4wUNLyHzvgCP4aYZMh16y7GcfF59oirRHcgGgkH1e/s9C0nv/v3WP+Quymyr5je71HeFQCwh+44XLg==", "license": "Apache-2.0", "dependencies": { "@smithy/middleware-serde": "^4.0.8", @@ -3135,12 +3135,12 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.11.tgz", - "integrity": "sha512-zDogwtRLzKl58lVS8wPcARevFZNBOOqnmzWWxVe9XiaXU2CADFjvJ9XfNibgkOWs08sxLuSr81NrpY4mgp9OwQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.13.tgz", + "integrity": "sha512-xg3EHV/Q5ZdAO5b0UiIMj3RIOCobuS40pBBODguUDVdko6YK6QIzCVRrHTogVuEKglBWqWenRnZ71iZnLL3ZAQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.5.3", + "@smithy/core": "^3.6.0", "@smithy/middleware-serde": "^4.0.8", "@smithy/node-config-provider": "^4.1.3", "@smithy/shared-ini-file-loader": "^4.0.4", @@ -3154,18 +3154,18 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.12.tgz", - "integrity": "sha512-wvIH70c4e91NtRxdaLZF+mbLZ/HcC6yg7ySKUiufL6ESp6zJUSnJucZ309AvG9nqCFHSRB5I6T3Ez1Q9wCh0Ww==", + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.14.tgz", + "integrity": "sha512-eoXaLlDGpKvdmvt+YBfRXE7HmIEtFF+DJCbTPwuLunP0YUnrydl+C4tS+vEM0+nyxXrX3PSUFqC+lP1+EHB1Tw==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^4.1.3", "@smithy/protocol-http": "^5.1.2", - "@smithy/service-error-classification": "^4.0.5", - "@smithy/smithy-client": "^4.4.3", + "@smithy/service-error-classification": "^4.0.6", + "@smithy/smithy-client": "^4.4.5", "@smithy/types": "^4.3.1", "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.5", + "@smithy/util-retry": "^4.0.6", "tslib": "^2.6.2", "uuid": "^9.0.1" }, @@ -3298,9 +3298,9 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.5.tgz", - "integrity": "sha512-LvcfhrnCBvCmTee81pRlh1F39yTS/+kYleVeLCwNtkY8wtGg8V/ca9rbZZvYIl8OjlMtL6KIjaiL/lgVqHD2nA==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.6.tgz", + "integrity": "sha512-RRoTDL//7xi4tn5FrN2NzH17jbgmnKidUqd4KvquT0954/i6CXXkh1884jBiunq24g9cGtPBEXlU40W6EpNOOg==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.3.1" @@ -3342,13 +3342,13 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.3.tgz", - "integrity": "sha512-xxzNYgA0HD6ETCe5QJubsxP0hQH3QK3kbpJz3QrosBCuIWyEXLR/CO5hFb2OeawEKUxMNhz3a1nuJNN2np2RMA==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.5.tgz", + "integrity": "sha512-+lynZjGuUFJaMdDYSTMnP/uPBBXXukVfrJlP+1U/Dp5SFTEI++w6NMga8DjOENxecOF71V9Z2DllaVDYRnGlkg==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.5.3", - "@smithy/middleware-endpoint": "^4.1.11", + "@smithy/core": "^3.6.0", + "@smithy/middleware-endpoint": "^4.1.13", "@smithy/middleware-stack": "^4.0.4", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", @@ -3449,13 +3449,13 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.19", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.19.tgz", - "integrity": "sha512-mvLMh87xSmQrV5XqnUYEPoiFFeEGYeAKIDDKdhE2ahqitm8OHM3aSvhqL6rrK6wm1brIk90JhxDf5lf2hbrLbQ==", + "version": "4.0.21", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.21.tgz", + "integrity": "sha512-wM0jhTytgXu3wzJoIqpbBAG5U6BwiubZ6QKzSbP7/VbmF1v96xlAbX2Am/mz0Zep0NLvLh84JT0tuZnk3wmYQA==", "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.3", + "@smithy/smithy-client": "^4.4.5", "@smithy/types": "^4.3.1", "bowser": "^2.11.0", "tslib": "^2.6.2" @@ -3465,16 +3465,16 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.19", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.19.tgz", - "integrity": "sha512-8tYnx+LUfj6m+zkUUIrIQJxPM1xVxfRBvoGHua7R/i6qAxOMjqR6CpEpDwKoIs1o0+hOjGvkKE23CafKL0vJ9w==", + "version": "4.0.21", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.21.tgz", + "integrity": "sha512-/F34zkoU0GzpUgLJydHY8Rxu9lBn8xQC/s/0M0U9lLBkYbA1htaAFjWYJzpzsbXPuri5D1H8gjp2jBum05qBrA==", "license": "Apache-2.0", "dependencies": { "@smithy/config-resolver": "^4.1.4", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/node-config-provider": "^4.1.3", "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.3", + "@smithy/smithy-client": "^4.4.5", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, @@ -3522,12 +3522,12 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.5.tgz", - "integrity": "sha512-V7MSjVDTlEt/plmOFBn1762Dyu5uqMrV2Pl2X0dYk4XvWfdWJNe9Bs5Bzb56wkCuiWjSfClVMGcsuKrGj7S/yg==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.6.tgz", + "integrity": "sha512-+YekoF2CaSMv6zKrA6iI/N9yva3Gzn4L6n35Luydweu5MMPYpiGZlWqehPHDHyNbnyaYlz/WJyYAZnC+loBDZg==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.0.5", + "@smithy/service-error-classification": "^4.0.6", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, @@ -9826,9 +9826,9 @@ } }, "node_modules/prettier": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.0.tgz", - "integrity": "sha512-ujSB9uXHJKzM/2GBuE0hBOUgC77CN3Bnpqa+g80bkv3T3A93wL/xlzDATHhnhkzifz/UE2SNOvmbTz5hSkDlHw==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.1.tgz", + "integrity": "sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==", "dev": true, "license": "MIT", "bin": { diff --git a/package.json b/package.json index 199ae1f76..5155066ff 100644 --- a/package.json +++ b/package.json @@ -16,14 +16,14 @@ "job-totals-fixtures:local": "docker exec node-app /usr/bin/node /app/download-job-totals-fixtures.js" }, "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "^3.832.0", - "@aws-sdk/client-elasticache": "^3.830.0", - "@aws-sdk/client-s3": "^3.832.0", - "@aws-sdk/client-secrets-manager": "^3.830.0", - "@aws-sdk/client-ses": "^3.830.0", - "@aws-sdk/credential-provider-node": "^3.830.0", - "@aws-sdk/lib-storage": "^3.832.0", - "@aws-sdk/s3-request-presigner": "^3.832.0", + "@aws-sdk/client-cloudwatch-logs": "^3.835.0", + "@aws-sdk/client-elasticache": "^3.835.0", + "@aws-sdk/client-s3": "^3.837.0", + "@aws-sdk/client-secrets-manager": "^3.835.0", + "@aws-sdk/client-ses": "^3.835.0", + "@aws-sdk/credential-provider-node": "^3.835.0", + "@aws-sdk/lib-storage": "^3.837.0", + "@aws-sdk/s3-request-presigner": "^3.837.0", "@opensearch-project/opensearch": "^2.13.0", "@socket.io/admin-ui": "^0.5.1", "@socket.io/redis-adapter": "^8.3.0", @@ -80,7 +80,7 @@ "globals": "^15.15.0", "mock-require": "^3.0.3", "p-limit": "^3.1.0", - "prettier": "^3.6.0", + "prettier": "^3.6.1", "supertest": "^7.1.1", "vitest": "^3.2.4" } diff --git a/server/integrations/partsManagement/partsManagementVehicleDamageEstimateAddRq.js b/server/integrations/partsManagement/partsManagementVehicleDamageEstimateAddRq.js index ba9ac2aa2..1ab8e4e64 100644 --- a/server/integrations/partsManagement/partsManagementVehicleDamageEstimateAddRq.js +++ b/server/integrations/partsManagement/partsManagementVehicleDamageEstimateAddRq.js @@ -55,6 +55,34 @@ const parseXml = async (xml, logger) => { } }; +/** + * Recursively strip `xml2js`-style { _: 'value', $: { ... } } nodes into plain strings. + * @param {*} obj - Parsed XML object + * @returns {*} Normalized object + */ +const normalizeXmlObject = (obj) => { + if (Array.isArray(obj)) { + return obj.map(normalizeXmlObject); + } + + if (typeof obj === "object" && obj !== null) { + if (Object.keys(obj).length === 2 && "_" in obj && "$" in obj) { + return normalizeXmlObject(obj._); // unwrap {_:"value",$:{...}} to just "value" + } + if (Object.keys(obj).length === 1 && "_" in obj) { + return normalizeXmlObject(obj._); // unwrap {_:"value"} + } + + const normalized = {}; + for (const key in obj) { + normalized[key] = normalizeXmlObject(obj[key]); + } + return normalized; + } + + return obj; +}; + /** * Fetches the default order status for a bodyshop. * @param {string} shopId - The bodyshop UUID. @@ -81,7 +109,17 @@ const extractPartsTaxRates = (profile = {}) => { const partsTaxRates = {}; for (const code of KNOWN_PART_RATE_TYPES) { - const rateInfo = rateInfos.find((r) => (r?.RateType || "").toUpperCase() === code); + const rateInfo = rateInfos.find((r) => { + const rateType = + typeof r?.RateType === "string" + ? r.RateType + : typeof r?.RateType === "object" && r?.RateType._ // xml2js sometimes uses _ for text content + ? r.RateType._ + : ""; + + return rateType.toUpperCase() === code; + }); + if (!rateInfo) { partsTaxRates[code] = {}; continue; @@ -111,7 +149,6 @@ const extractPartsTaxRates = (profile = {}) => { return partsTaxRates; }; - /** * Extracts job-related data from the XML request. * @param {object} rq - The VehicleDamageEstimateAddRq object. @@ -151,31 +188,44 @@ const extractJobData = (rq) => { * @param {string} shopId - The bodyshop UUID. * @returns {object} Owner data for insertion and inline use. */ +/** + * Extracts owner data from the XML request. + * Falls back to Claimant if Owner is missing. + * @param {object} rq - The VehicleDamageEstimateAddRq object. + * @param {string} shopId - The bodyshop UUID. + * @returns {object} Owner data for insertion and inline use. + */ const extractOwnerData = (rq, shopId) => { - const ownerParty = rq.AdminInfo?.Owner?.Party || {}; - const adr = ownerParty.PersonInfo?.Communications?.Address || {}; + // Prefer Owner, fallback to Claimant + const ownerOrClaimant = rq.AdminInfo?.Owner?.Party || rq.AdminInfo?.Claimant?.Party || {}; + + const personInfo = ownerOrClaimant.PersonInfo || {}; + const personName = personInfo.PersonName || {}; + const address = personInfo.Communications?.Address || {}; + let ownr_ph1, ownr_ph2, ownr_ea; - (Array.isArray(ownerParty.ContactInfo?.Communications) - ? ownerParty.ContactInfo.Communications - : [ownerParty.ContactInfo?.Communications || {}] - ).forEach((c) => { + const comms = Array.isArray(ownerOrClaimant.ContactInfo?.Communications) + ? ownerOrClaimant.ContactInfo.Communications + : [ownerOrClaimant.ContactInfo?.Communications || {}]; + + for (const c of comms) { if (c.CommQualifier === "CP") ownr_ph1 = c.CommPhone; if (c.CommQualifier === "WP") ownr_ph2 = c.CommPhone; if (c.CommQualifier === "EM") ownr_ea = c.CommEmail; - }); + } return { shopid: shopId, - ownr_fn: ownerParty.PersonInfo?.PersonName?.FirstName || null, - ownr_ln: ownerParty.PersonInfo?.PersonName?.LastName || null, - ownr_co_nm: ownerParty.OrgInfo?.CompanyName || null, - ownr_addr1: adr.Address1 || null, - ownr_addr2: adr.Address2 || null, - ownr_city: adr.City || null, - ownr_st: adr.StateProvince || null, - ownr_zip: adr.PostalCode || null, - ownr_ctry: adr.Country || null, + ownr_fn: personName.FirstName || null, + ownr_ln: personName.LastName || null, + ownr_co_nm: ownerOrClaimant.OrgInfo?.CompanyName || null, + ownr_addr1: address.Address1 || null, + ownr_addr2: address.Address2 || null, + ownr_city: address.City || null, + ownr_st: address.StateProvince || null, + ownr_zip: address.PostalCode || null, + ownr_ctry: address.Country || null, ownr_ph1, ownr_ph2, ownr_ea @@ -341,9 +391,9 @@ const extractVehicleData = (rq, shopId) => { v_mldgcode: desc.MldgCode || null, v_makecode: desc.MakeCode || null, trim_color: interior.ColorName || desc.TrimColor || null, - db_v_code: desc.DatabaseCode || null, - v_model_num: desc.ModelNum || null, - v_odo: desc.OdometerInfo?.OdometerReading || null + db_v_code: desc.DatabaseCode || null + // v_model_num: desc.ModelNum || null + // v_odo: desc.OdometerInfo?.OdometerReading || null }; }; @@ -436,7 +486,7 @@ const partsManagementVehicleDamageEstimateAddRq = async (req, res) => { try { // Parse XML const payload = await parseXml(req.body, logger); - const rq = payload.VehicleDamageEstimateAddRq; + const rq = normalizeXmlObject(payload.VehicleDamageEstimateAddRq); if (!rq) { logger.log("parts-missing-root", "error"); return res.status(400).send("Missing "); @@ -534,6 +584,7 @@ const partsManagementVehicleDamageEstimateAddRq = async (req, res) => { return res.status(200).json({ success: true, jobId: newJob.id }); } catch (err) { logger.log("parts-route-error", "error", null, null, { error: err }); + console.dir({ err }); return res.status(err.status || 500).json({ error: err.message || "Internal error" }); } }; From 646c42b8c76900a4ff178956c47cdb67960f2388 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 27 Jun 2025 13:48:49 -0400 Subject: [PATCH 11/11] feature/IO-3255-simplified-parts-management - Checkpoint --- .../partsManagement.queries.js | 69 ++++++++++++++- ...rtsManagementVehicleDamageEstimateAddRq.js | 83 ++++++++++++++++++- 2 files changed, 150 insertions(+), 2 deletions(-) diff --git a/server/integrations/partsManagement/partsManagement.queries.js b/server/integrations/partsManagement/partsManagement.queries.js index 45a88ad58..e0ba6088c 100644 --- a/server/integrations/partsManagement/partsManagement.queries.js +++ b/server/integrations/partsManagement/partsManagement.queries.js @@ -32,9 +32,76 @@ const INSERT_JOB_WITH_LINES = ` } `; +const GET_JOB_BY_CLAIM = ` + query GetJobByClaim($shopid: uuid!, $clm_no: String!) { + jobs( + where: { shopid: { _eq: $shopid }, clm_no: { _eq: $clm_no } } + order_by: { created_at: desc } + limit: 1 + ) { + id + } + } +`; + +const UPDATE_JOB_BY_ID = ` + mutation UpdateJobById($id: uuid!, $job: jobs_set_input!) { + update_jobs_by_pk(pk_columns: { id: $id }, _set: $job) { + id + } + } +`; + +const UPSERT_JOBLINES = ` + mutation UpsertJoblines($joblines: [joblines_insert_input!]!) { + insert_joblines( + objects: $joblines + on_conflict: { + constraint: joblines_jobid_line_no_unq_seq_key + update_columns: [ + status + line_desc + part_type + part_qty + oem_partno + db_price + act_price + mod_lbr_ty + mod_lb_hrs + lbr_op + lbr_amt + notes + ] + } + ) { + affected_rows + } + } +`; +const DELETE_JOBLINES_BY_JOBID = ` + mutation DeleteJoblinesByJobId($jobid: uuid!) { + delete_joblines(where: { jobid: { _eq: $jobid } }) { + affected_rows + } + } +`; + +const INSERT_JOBLINES = ` + mutation InsertJoblines($joblines: [joblines_insert_input!]!) { + insert_joblines(objects: $joblines) { + affected_rows + } + } +`; + module.exports = { GET_BODYSHOP_STATUS, GET_VEHICLE_BY_SHOP_VIN, INSERT_OWNER, - INSERT_JOB_WITH_LINES + INSERT_JOB_WITH_LINES, + GET_JOB_BY_CLAIM, + UPDATE_JOB_BY_ID, + DELETE_JOBLINES_BY_JOBID, + UPSERT_JOBLINES, + INSERT_JOBLINES }; diff --git a/server/integrations/partsManagement/partsManagementVehicleDamageEstimateAddRq.js b/server/integrations/partsManagement/partsManagementVehicleDamageEstimateAddRq.js index 1ab8e4e64..ea3803725 100644 --- a/server/integrations/partsManagement/partsManagementVehicleDamageEstimateAddRq.js +++ b/server/integrations/partsManagement/partsManagementVehicleDamageEstimateAddRq.js @@ -9,7 +9,11 @@ const { GET_BODYSHOP_STATUS, GET_VEHICLE_BY_SHOP_VIN, INSERT_OWNER, - INSERT_JOB_WITH_LINES + INSERT_JOB_WITH_LINES, + GET_JOB_BY_CLAIM, + UPDATE_JOB_BY_ID, + INSERT_JOBLINES, + DELETE_JOBLINES_BY_JOBID } = require("./partsManagement.queries"); // Defaults @@ -436,6 +440,18 @@ const extractJobLines = (rq) => { }); }; +/** + * Checks if the request is a supplement or a document portion delta. + * TODO: This is a temporary check, should be replaced with a proper field in the XML. + * @param rq + * @returns {boolean} + */ +const isSupplement = (rq) => { + const docStatus = rq.DocumentInfo?.DocumentStatus; + const historyType = rq.RepairTotalsHistory?.HistoryTotalType; + return docStatus === "S" || historyType === "DocumentPortionDelta"; +}; + /** * Finds an existing vehicle by shopId and VIN. * @param {string} shopId - The bodyshop UUID. @@ -458,6 +474,26 @@ const findExistingVehicle = async (shopId, v_vin, logger) => { return null; }; +/** + * Finds an existing job by shopid and claim number. + * @param shopid + * @param clm_no + * @param logger + * @returns {Promise<*|null>} + */ +const findExistingJob = async (shopid, clm_no, logger) => { + try { + const { jobs } = await client.request(GET_JOB_BY_CLAIM, { + shopid, + clm_no + }); + return jobs?.[0] || null; + } catch (err) { + logger.log("parts-job-fetch-failed", "warn", null, null, { error: err }); + return null; + } +}; + /** * Inserts an owner and returns the owner ID. * @param {object} ownerInput - The owner data to insert. @@ -532,6 +568,21 @@ const partsManagementVehicleDamageEstimateAddRq = async (req, res) => { const joblinesData = extractJobLines(rq); const insuranceData = extractInsuranceData(rq); + // Uncomment for debugging + // console.dir( + // { + // joblinesData, + // lossInfo, + // insuranceData, + // vehicleData, + // ownerData, + // adjusterData, + // repairFacilityData, + // estimatorData + // }, + // { depth: null } + // ); + // Find or create relationships const ownerid = await insertOwner(ownerData, logger); const vehicleid = await findExistingVehicle(shopId, vehicleData.v_vin, logger); @@ -577,6 +628,36 @@ const partsManagementVehicleDamageEstimateAddRq = async (req, res) => { joblines: { data: joblinesData } }; + // Check if this is a supplement or document portion delta. + if (isSupplement(rq)) { + console.log("----------------------IS SUPPLEMENT----------------------"); + const existingJob = await findExistingJob(shopId, clm_no, logger); + if (existingJob) { + const { joblines, ...jobWithoutLines } = jobInput; + + await client.request(UPDATE_JOB_BY_ID, { + id: existingJob.id, + job: jobWithoutLines + }); + + await client.request(DELETE_JOBLINES_BY_JOBID, { + jobid: existingJob.id + }); + + if (joblines?.data?.length) { + const joblinesWithJobId = joblines.data.map((line) => ({ + ...line, + jobid: existingJob.id + })); + + await client.request(INSERT_JOBLINES, { joblines: joblinesWithJobId }); + } + + logger.log("parts-job-updated", "info", existingJob.id); + return res.status(200).json({ success: true, jobId: existingJob.id }); + } + } + // Insert job const { insert_jobs_one: newJob } = await client.request(INSERT_JOB_WITH_LINES, { job: jobInput }); logger.log("parts-job-created", "info", newJob.id, null);