feature/IO-3255-simplified-parts-management - Checkpoint / Meeting changes

This commit is contained in:
Dave Richer
2025-07-03 14:05:35 -04:00
parent 5fa58e5013
commit 94b154a4ac
4 changed files with 70 additions and 105 deletions

View File

@@ -345,13 +345,13 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
payload.features?.allAccess === true
? window.$crisp.push(["set", "session:segments", [["allAccess"]]])
: (() => {
const featureKeys = Object.keys(payload.features).filter(
(key) =>
payload.features[key] === true ||
(typeof payload.features[key] === "string" && !isNaN(Date.parse(payload.features[key])))
);
window.$crisp.push(["set", "session:segments", [["basic", ...featureKeys]]]);
})();
const featureKeys = Object.keys(payload.features).filter(
(key) =>
payload.features[key] === true ||
(typeof payload.features[key] === "string" && !isNaN(Date.parse(payload.features[key])))
);
window.$crisp.push(["set", "session:segments", [["basic", ...featureKeys]]]);
})();
InstanceRenderManager({
executeFunction: true,
@@ -361,8 +361,9 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
}
});
//Set whether it is for parts management only.
//Set whether it is for parts management only.
// TODO: This is a temp fix we do not want to do a check on true
yield put(setPartsManagementOnly(true || payload.features.partsManagementOnly));
} catch (error) {
console.warn("Couldnt find $crisp.", error.message);

View File

@@ -32,12 +32,17 @@ const ensureEmailNotRegistered = async (email) => {
};
/**
* Creates a new Firebase user with the provided email.
* Creates a new Firebase user with the provided email and optional password.
* @param email
* @param password
* @returns {Promise<UserRecord>}
*/
const createFirebaseUser = async (email) => {
return admin.auth().createUser({ email });
const createFirebaseUser = async (email, password = null) => {
const userData = { email };
if (password) {
userData.password = password;
}
return admin.auth().createUser(userData);
};
/**
@@ -158,7 +163,7 @@ const partsManagementProvisioning = async (req, res) => {
const p = { ...req.body, userEmail: req.body.userEmail?.toLowerCase() };
try {
// Validate inputs
// Validate inputs
await ensureEmailNotRegistered(p.userEmail);
requireFields(p, [
"external_shop_id",
@@ -179,7 +184,7 @@ const partsManagementProvisioning = async (req, res) => {
ioadmin: true
});
// Create shop
// Create shop
const shopInput = {
shopname: p.shopname,
address1: p.address1,
@@ -198,6 +203,9 @@ const partsManagementProvisioning = async (req, res) => {
height: "",
headerMargin: DefaultNewShop.logo_img_path.headerMargin
},
features: {
partsManagementOnly: true // This is a parts management only shop
},
md_ro_statuses: DefaultNewShop.md_ro_statuses,
vendors: {
data: p.vendors.map((v) => ({
@@ -221,9 +229,12 @@ const partsManagementProvisioning = async (req, res) => {
};
const newShopId = await insertBodyshop(shopInput);
// Create user + association
const userRecord = await createFirebaseUser(p.userEmail);
const resetLink = await generateResetLink(p.userEmail);
// Create user + association
const userRecord = await createFirebaseUser(p.userEmail, p.userPassword);
let resetLink = null;
if (!p.userPassword) {
resetLink = await generateResetLink(p.userEmail);
}
const createdUser = await insertUserAssociation(userRecord.uid, p.userEmail, newShopId);
return res.status(200).json({
@@ -231,7 +242,7 @@ const partsManagementProvisioning = async (req, res) => {
user: {
id: createdUser.id,
email: createdUser.email,
resetLink
resetLink: resetLink || undefined // Only include resetLink if it exists
}
});
} catch (err) {

View File

@@ -9,11 +9,7 @@ const {
GET_BODYSHOP_STATUS,
GET_VEHICLE_BY_SHOP_VIN,
INSERT_OWNER,
INSERT_JOB_WITH_LINES,
GET_JOB_BY_CLAIM,
UPDATE_JOB_BY_ID,
INSERT_JOBLINES,
DELETE_JOBLINES_BY_JOBID
INSERT_JOB_WITH_LINES
} = require("./partsManagement.queries");
// Defaults
@@ -177,12 +173,28 @@ const extractJobData = (rq) => {
asgn_no: asgn.AssignmentNumber || null,
asgn_type: asgn.AssignmentType || null,
asgn_date: asgn.AssignmentDate || null,
// asgn_created: asgn.CreateDateTime || 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)
// document_id: doc.DocumentID || null,
// bms_version: doc.BMSVer || null,
// reference_info:
// doc.ReferenceInfo?.OtherReferenceInfo?.map((ref) => ({
// name: ref.OtherReferenceName,
// number: ref.OtherRefNum
// })) || [],
// currency_info: doc.CurrencyInfo
// ? {
// code: doc.CurrencyInfo.CurCode,
// base_code: doc.CurrencyInfo.BaseCurCode,
// rate: parseFloat(doc.CurrencyInfo.CurRate || 0),
// rule: doc.CurrencyInfo.CurConvertRule
// }
// : null
};
};
@@ -200,14 +212,12 @@ const extractJobData = (rq) => {
* @returns {object} Owner data for insertion and inline use.
*/
const extractOwnerData = (rq, shopId) => {
// 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;
let ownr_ph1, ownr_ph2, ownr_ea, ownr_alt_ph;
const comms = Array.isArray(ownerOrClaimant.ContactInfo?.Communications)
? ownerOrClaimant.ContactInfo.Communications
@@ -217,6 +227,7 @@ const extractOwnerData = (rq, shopId) => {
if (c.CommQualifier === "CP") ownr_ph1 = c.CommPhone;
if (c.CommQualifier === "WP") ownr_ph2 = c.CommPhone;
if (c.CommQualifier === "EM") ownr_ea = c.CommEmail;
if (c.CommQualifier === "AL") ownr_alt_ph = c.CommPhone;
}
return {
@@ -232,7 +243,11 @@ const extractOwnerData = (rq, shopId) => {
ownr_ctry: address.Country || null,
ownr_ph1,
ownr_ph2,
ownr_ea
ownr_ea,
ownr_alt_ph
// ownr_id_qualifier: ownerOrClaimant.IDInfo?.IDQualifierCode || null // New
// ownr_id_num: ownerOrClaimant.IDInfo?.IDNum || null, // New
// ownr_preferred_contact: ownerOrClaimant.PreferredContactMethod || null // New
};
};
@@ -304,13 +319,19 @@ const extractLossInfo = (rq) => {
loss_date: loss.LossDateTime || null,
loss_type: custom.LossTypeCode || null,
loss_desc: custom.LossTypeDesc || null
// primary_poi: loss.PrimaryPOI?.POICode || null,
// secondary_poi: loss.SecondaryPOI?.POICode || null,
// damage_memo: loss.DamageMemo || null, //(maybe ins_memo)
// total_loss_ind: rq.ClaimInfo?.LossInfo?.TotalLossInd || null // New
};
};
/**
* 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}}
* @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 || {};
@@ -424,7 +445,10 @@ const extractJobLines = (rq) => {
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
notes: line.LineMemo || null,
manual_line: line.ManualLineInd || null
// taxable_ind: line.PartInfo?.TaxableInd || null,
// automated_entry: line.AutomatedEntry || null
};
// TODO: Commented out as not clear if needed for version 1, this only applies to Imex and not rome on the front
@@ -440,18 +464,6 @@ 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.
@@ -474,26 +486,6 @@ 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.
@@ -568,21 +560,6 @@ 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);
@@ -628,36 +605,6 @@ 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);

View File

@@ -54,6 +54,10 @@ paths:
userEmail:
type: string
format: email
userPassword:
type: string
description: Optional password for the new user. If provided, the password is set directly, and no password reset link is sent. Must be at least 6 characters.
nullable: true
logoUrl:
type: string
format: uri
@@ -140,6 +144,8 @@ paths:
resetLink:
type: string
format: uri
nullable: true
description: Password reset link for the user. Only included if userPassword is not provided in the request.
'400':
description: Bad request (missing or invalid fields)
content: