feature/IO-3255-simplified-parts-management - Checkpoint
This commit is contained in:
@@ -118,6 +118,7 @@ services:
|
||||
aws --endpoint-url=http://localstack:4566 logs create-log-group --log-group-name development --region ca-central-1
|
||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-large-log --create-bucket-configuration LocationConstraint=ca-central-1
|
||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-job-totals --create-bucket-configuration LocationConstraint=ca-central-1
|
||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket parts-estimates --create-bucket-configuration LocationConstraint=ca-central-1
|
||||
"
|
||||
# Node App: The Main IMEX API
|
||||
node-app:
|
||||
|
||||
@@ -2,6 +2,9 @@ const client = require("../../../graphql-client/graphql-client").client;
|
||||
const { extractPartsTaxRates } = require("./lib/extractPartsTaxRates");
|
||||
const { parseXml, normalizeXmlObject } = require("../partsManagementUtils");
|
||||
const opCodes = require("./lib/opCodes.json");
|
||||
// New imports for S3 XML archival
|
||||
const { uploadFileToS3 } = require("../../../utils/s3");
|
||||
const InstanceMgr = require("../../../utils/instanceMgr").default;
|
||||
|
||||
// GraphQL Queries and Mutations
|
||||
const {
|
||||
@@ -10,10 +13,28 @@ const {
|
||||
INSERT_OWNER,
|
||||
INSERT_JOB_WITH_LINES
|
||||
} = require("../partsManagement.queries");
|
||||
const { v4: uuidv4 } = require("uuid");
|
||||
|
||||
// Defaults
|
||||
const FALLBACK_DEFAULT_JOB_STATUS = "Open";
|
||||
|
||||
const ESTIMATE_XML_BUCKET =
|
||||
process.env?.NODE_ENV === "development"
|
||||
? "parts-estimates" // local/dev shared bucket name
|
||||
: InstanceMgr({
|
||||
imex: `imex-webest-xml`,
|
||||
rome: `rome-webest-xml`
|
||||
});
|
||||
|
||||
const buildEstimateXmlKey = (rq) => {
|
||||
const refClaimNum = rq.RefClaimNum;
|
||||
const shopId = rq.ShopID;
|
||||
|
||||
const ts = new Date().toISOString().replace(/:/g, "-");
|
||||
const safeClaim = (refClaimNum || "no-claim").toString().replace(/[^A-Za-z0-9_-]/g, "_");
|
||||
return `addRequest/${shopId}/${safeClaim}/${ts}-${uuidv4()}.xml`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches the default order status for a bodyshop.
|
||||
* @param {string} shopId - The bodyshop UUID.
|
||||
@@ -514,17 +535,10 @@ const insertOwner = async (ownerInput, logger) => {
|
||||
*/
|
||||
const vehicleDamageEstimateAddRq = async (req, res) => {
|
||||
const { logger } = req;
|
||||
|
||||
const rawXml = typeof req.body === "string" ? req.body : Buffer.isBuffer(req.body) ? req.body.toString("utf8") : "";
|
||||
try {
|
||||
// Parse XML
|
||||
const payload = await parseXml(req.body, logger);
|
||||
const rq = normalizeXmlObject(payload.VehicleDamageEstimateAddRq);
|
||||
if (!rq) {
|
||||
logger.log("parts-missing-root", "error");
|
||||
return res.status(400).send("Missing <VehicleDamageEstimateAddRq>");
|
||||
}
|
||||
|
||||
// Extract job data
|
||||
const {
|
||||
shopId,
|
||||
refClaimNum,
|
||||
@@ -544,36 +558,17 @@ const vehicleDamageEstimateAddRq = async (req, res) => {
|
||||
policy_no,
|
||||
ded_amt,
|
||||
driveable
|
||||
// status,
|
||||
} = extractJobData(rq);
|
||||
|
||||
if (!shopId) {
|
||||
throw { status: 400, message: "Missing <ShopID> in XML" };
|
||||
}
|
||||
|
||||
// Get default status
|
||||
const defaultStatus = await getDefaultJobStatus(shopId, logger);
|
||||
|
||||
// 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 lossInfo = extractLossInfo(rq);
|
||||
const joblinesData = extractJobLines(rq);
|
||||
const insuranceData = extractInsuranceData(rq);
|
||||
|
||||
// Derive clm_total: prefer RepairTotalsInfo SummaryTotals GRAND TOTAL; else sum from lines
|
||||
// const grandTotal = extractGrandTotal(rq);
|
||||
// const computedTotal = grandTotal ?? computeLinesTotal(joblinesData);
|
||||
|
||||
// Find or create relationships
|
||||
const ownerid = await insertOwner(ownerData, logger);
|
||||
const vehicleid = await findExistingVehicle(shopId, vehicleData.v_vin, logger);
|
||||
|
||||
// Build job input
|
||||
const jobInput = {
|
||||
shopid: shopId,
|
||||
driveable,
|
||||
@@ -588,7 +583,7 @@ const vehicleDamageEstimateAddRq = async (req, res) => {
|
||||
parts_tax_rates,
|
||||
clm_no,
|
||||
status: defaultStatus,
|
||||
clm_total: 0, // computedTotal || null,
|
||||
clm_total: 0,
|
||||
policy_no,
|
||||
ded_amt,
|
||||
comment,
|
||||
@@ -598,14 +593,10 @@ const vehicleDamageEstimateAddRq = async (req, res) => {
|
||||
asgn_date,
|
||||
scheduled_in,
|
||||
scheduled_completion,
|
||||
// Inline insurance/loss/contacts
|
||||
...insuranceData,
|
||||
...lossInfo,
|
||||
...ownerData,
|
||||
...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,
|
||||
@@ -616,10 +607,23 @@ const vehicleDamageEstimateAddRq = async (req, res) => {
|
||||
...(vehicleid ? { vehicleid } : { vehicle: { data: vehicleData } }),
|
||||
joblines: { data: joblinesData }
|
||||
};
|
||||
|
||||
// Insert job
|
||||
const { insert_jobs_one: newJob } = await client.request(INSERT_JOB_WITH_LINES, { job: jobInput });
|
||||
|
||||
// Upload AFTER job creation to include job id in filename
|
||||
(async () => {
|
||||
try {
|
||||
const key = buildEstimateXmlKey(rq);
|
||||
await uploadFileToS3({
|
||||
bucketName: ESTIMATE_XML_BUCKET,
|
||||
key,
|
||||
content: rawXml || "",
|
||||
contentType: "application/xml"
|
||||
});
|
||||
logger.log("parts-estimate-xml-uploaded", "info", shopId, newJob.id, { key, bytes: rawXml?.length || 0 });
|
||||
} catch (e) {
|
||||
logger.log("parts-estimate-xml-upload-failed", "warn", shopId, null, { error: e?.message });
|
||||
}
|
||||
})();
|
||||
return res.status(200).json({ success: true, jobId: newJob.id });
|
||||
} catch (err) {
|
||||
logger.log("parts-route-error", "error", null, null, { error: err });
|
||||
|
||||
@@ -5,6 +5,8 @@ const client = require("../../../graphql-client/graphql-client").client;
|
||||
const { parseXml, normalizeXmlObject } = require("../partsManagementUtils");
|
||||
const { extractPartsTaxRates } = require("./lib/extractPartsTaxRates");
|
||||
const opCodes = require("./lib/opCodes.json");
|
||||
const { uploadFileToS3 } = require("../../../utils/s3");
|
||||
const InstanceMgr = require("../../../utils/instanceMgr").default;
|
||||
|
||||
const {
|
||||
GET_JOB_BY_ID,
|
||||
@@ -214,6 +216,22 @@ const extractDeletions = (deletions = {}) => {
|
||||
return Array.from(new Set(allSeqs));
|
||||
};
|
||||
|
||||
// S3 bucket + key builder (mirrors AddRq but with changeRequest prefix)
|
||||
const ESTIMATE_XML_BUCKET =
|
||||
process.env?.NODE_ENV === "development"
|
||||
? "parts-estimates"
|
||||
: InstanceMgr({
|
||||
imex: `imex-webest-xml`,
|
||||
rome: `rome-webest-xml`
|
||||
});
|
||||
|
||||
const buildEstimateXmlKey = (rq) => {
|
||||
const shopId = rq.ShopID;
|
||||
const jobId = rq.JobID;
|
||||
const ts = new Date().toISOString().replace(/:/g, "-");
|
||||
return `changeRequest/${shopId}/${jobId}/${ts}.xml`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles VehicleDamageEstimateChgRq requests.
|
||||
* @param req
|
||||
@@ -222,19 +240,26 @@ const extractDeletions = (deletions = {}) => {
|
||||
*/
|
||||
const partsManagementVehicleDamageEstimateChgRq = async (req, res) => {
|
||||
const { logger } = req;
|
||||
|
||||
const rawXml = typeof req.body === "string" ? req.body : Buffer.isBuffer(req.body) ? req.body.toString("utf8") : "";
|
||||
try {
|
||||
const payload = await parseXml(req.body, logger);
|
||||
const rq = normalizeXmlObject(payload.VehicleDamageEstimateChgRq);
|
||||
if (!rq) return res.status(400).send("Missing <VehicleDamageEstimateChgRq>");
|
||||
|
||||
const shopId = rq.ShopID;
|
||||
const jobId = rq.JobID;
|
||||
const shopId = rq.ShopID;
|
||||
|
||||
if (!shopId || !jobId) return res.status(400).send("Missing ShopID or JobID");
|
||||
// Fire-and-forget archival on valid request
|
||||
(async () => {
|
||||
try {
|
||||
const key = buildEstimateXmlKey(rq);
|
||||
await uploadFileToS3({ bucketName: ESTIMATE_XML_BUCKET, key, content: rawXml || "", contentType: "application/xml" });
|
||||
logger.log("parts-estimate-xml-uploaded", "info", jobId, null, { key, bytes: rawXml?.length || 0 });
|
||||
} catch (e) {
|
||||
logger.log("parts-estimate-xml-upload-failed", "warn", jobId, null, { error: e?.message });
|
||||
}
|
||||
})();
|
||||
|
||||
const job = await findJob(shopId, jobId, logger);
|
||||
|
||||
if (!job) return res.status(404).send("Job not found");
|
||||
|
||||
// --- Get updated lines and their unq_seq ---
|
||||
@@ -290,3 +315,5 @@ const partsManagementVehicleDamageEstimateChgRq = async (req, res) => {
|
||||
};
|
||||
|
||||
module.exports = partsManagementVehicleDamageEstimateChgRq;
|
||||
|
||||
// Remove any duplicate S3 constants that might have been appended previously (none expected now)
|
||||
|
||||
Reference in New Issue
Block a user