Compare commits
9 Commits
rrScratch3
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71c6d9fa94 | ||
|
|
6ea1c291e6 | ||
|
|
05d5c96491 | ||
|
|
35a566cbe5 | ||
|
|
f12e40e4c6 | ||
|
|
bb4e671c83 | ||
|
|
d1637d2432 | ||
|
|
1c79628613 | ||
|
|
521a7084b7 |
@@ -138,7 +138,7 @@ export function App({
|
||||
);
|
||||
}
|
||||
|
||||
if (currentEula && !currentUser.eulaIsAccepted) {
|
||||
if (!isPartsEntry && currentEula && !currentUser.eulaIsAccepted) {
|
||||
return <Eula />;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,8 @@ const Eula = ({ currentEula, currentUser, acceptEula }) => {
|
||||
const useremail = currentUser.email;
|
||||
|
||||
try {
|
||||
const { ...otherFormValues } = formValues;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { accepted_terms, ...otherFormValues } = formValues;
|
||||
|
||||
// Trim the values of the fields before submitting
|
||||
const trimmedFormValues = Object.entries(otherFormValues).reduce((acc, [key, value]) => {
|
||||
|
||||
@@ -117,44 +117,46 @@ async function processShopData(shopsToProcess, start, end, skipUpload, ignoreDat
|
||||
imexshopid: shopid,
|
||||
json: JSON.stringify(carfaxObject, null, 2),
|
||||
filename: `${shopid}_${moment().format("DDMMYYYY_HHMMss")}.json`,
|
||||
count: carfaxObject.job.length
|
||||
count: carfaxObject?.job?.length || 0
|
||||
};
|
||||
|
||||
if (skipUpload) {
|
||||
fs.writeFileSync(`./logs/${jsonObj.filename}`, jsonObj.json);
|
||||
uploadToS3(jsonObj, S3_BUCKET_NAME);
|
||||
} else {
|
||||
await uploadViaSFTP(jsonObj);
|
||||
if (jsonObj.count > 0) {
|
||||
await uploadViaSFTP(jsonObj);
|
||||
|
||||
await sendMexicoBillingEmail({
|
||||
subject: `${shopid.replace(/_/g, "").toUpperCase()}_MexicoRPS_${moment().format("MMDDYYYY")} ROs ${jsonObj.count} Error ${errorCode(jsonObj)}`,
|
||||
text: `Errors:\n${JSON.stringify(
|
||||
erroredJobs.map((ej) => ({
|
||||
jobid: ej.job?.id,
|
||||
error: ej.error
|
||||
})),
|
||||
null,
|
||||
2
|
||||
)}\n\nUploaded:\n${JSON.stringify(
|
||||
{
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: shopid,
|
||||
count: jsonObj.count,
|
||||
filename: jsonObj.filename,
|
||||
result: jsonObj.result
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
});
|
||||
await sendMexicoBillingEmail({
|
||||
subject: `${shopid.replace(/_/g, "").toUpperCase()}_MexicoRPS_${moment().format("MMDDYYYY")} ROs ${jsonObj.count} Error ${errorCode(jsonObj)}`,
|
||||
text: `Errors:\n${JSON.stringify(
|
||||
erroredJobs.map((ej) => ({
|
||||
jobid: ej.job?.id,
|
||||
error: ej.error
|
||||
})),
|
||||
null,
|
||||
2
|
||||
)}\n\nUploaded:\n${JSON.stringify(
|
||||
{
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: shopid,
|
||||
count: jsonObj.count,
|
||||
filename: jsonObj.filename,
|
||||
result: jsonObj.result
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
allJSONResults.push({
|
||||
jsonObj.count > 0 && allJSONResults.push({
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: shopid,
|
||||
count: jsonObj.count,
|
||||
filename: jsonObj.filename,
|
||||
result: jsonObj.result
|
||||
result: jsonObj.result || "No Upload Result Available"
|
||||
});
|
||||
|
||||
logger.log("CARFAX-RPS-end-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||
@@ -234,11 +236,10 @@ const CreateRepairOrderTag = (job, errorCallback) => {
|
||||
const ret = {
|
||||
ro_number: crypto.createHash("md5").update(job.id, "utf8").digest("hex"),
|
||||
v_vin: job.v_vin || "",
|
||||
v_year: job.v_model_yr
|
||||
? parseInt(job.v_model_yr.match(/\d/g))
|
||||
? parseInt(job.v_model_yr.match(/\d/g).join(""), 10)
|
||||
: ""
|
||||
: "",
|
||||
v_year: (() => {
|
||||
const y = parseInt(job.v_model_yr);
|
||||
return isNaN(y) ? null : y < 100 ? y + (y >= (new Date().getFullYear() + 1) % 100 ? 1900 : 2000) : y;
|
||||
})(),
|
||||
v_make: job.v_makedesc || "",
|
||||
v_model: job.v_model || "",
|
||||
|
||||
|
||||
@@ -160,40 +160,42 @@ async function processShopData(shopsToProcess, start, end, skipUpload, ignoreDat
|
||||
imexshopid: shopid,
|
||||
json: JSON.stringify(carfaxObject, null, 2),
|
||||
filename: `${shopid}_${moment().format("DDMMYYYY_HHMMss")}.json`,
|
||||
count: carfaxObject.job.length
|
||||
count: carfaxObject?.job?.length || 0
|
||||
};
|
||||
|
||||
if (skipUpload) {
|
||||
fs.writeFileSync(`./logs/${jsonObj.filename}`, jsonObj.json);
|
||||
uploadToS3(jsonObj);
|
||||
} else {
|
||||
await uploadViaSFTP(jsonObj);
|
||||
if (jsonObj.count > 0) {
|
||||
await uploadViaSFTP(jsonObj);
|
||||
|
||||
await sendMexicoBillingEmail({
|
||||
subject: `${shopid.replace(/_/g, "").toUpperCase()}_Mexico${InstanceManager({
|
||||
imex: "IO",
|
||||
rome: "RO"
|
||||
})}_${moment().format("MMDDYYYY")} ROs ${jsonObj.count} Error ${errorCode(jsonObj)}`,
|
||||
text: `Errors:\n${JSON.stringify(
|
||||
erroredJobs.map((ej) => ({
|
||||
ro_number: ej.job?.ro_number,
|
||||
jobid: ej.job?.id,
|
||||
error: ej.error
|
||||
})),
|
||||
null,
|
||||
2
|
||||
)}\n\nUploaded:\n${JSON.stringify(
|
||||
{
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: shopid,
|
||||
count: jsonObj.count,
|
||||
filename: jsonObj.filename,
|
||||
result: jsonObj.result
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
});
|
||||
await sendMexicoBillingEmail({
|
||||
subject: `${shopid.replace(/_/g, "").toUpperCase()}_Mexico${InstanceManager({
|
||||
imex: "IO",
|
||||
rome: "RO"
|
||||
})}_${moment().format("MMDDYYYY")} ROs ${jsonObj.count} Error ${errorCode(jsonObj)}`,
|
||||
text: `Errors:\n${JSON.stringify(
|
||||
erroredJobs.map((ej) => ({
|
||||
ro_number: ej.job?.ro_number,
|
||||
jobid: ej.job?.id,
|
||||
error: ej.error
|
||||
})),
|
||||
null,
|
||||
2
|
||||
)}\n\nUploaded:\n${JSON.stringify(
|
||||
{
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: shopid,
|
||||
count: jsonObj.count,
|
||||
filename: jsonObj.filename,
|
||||
result: jsonObj.result
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
allJSONResults.push({
|
||||
@@ -201,7 +203,7 @@ async function processShopData(shopsToProcess, start, end, skipUpload, ignoreDat
|
||||
imexshopid: shopid,
|
||||
count: jsonObj.count,
|
||||
filename: jsonObj.filename,
|
||||
result: jsonObj.result
|
||||
result: jsonObj.result || "No Upload Result Available"
|
||||
});
|
||||
|
||||
logger.log("CARFAX-end-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||
@@ -286,11 +288,10 @@ const CreateRepairOrderTag = (job, errorCallback) => {
|
||||
const ret = {
|
||||
ro_number: crypto.createHash("md5").update(job.ro_number, "utf8").digest("hex"),
|
||||
v_vin: job.v_vin || "",
|
||||
v_year: job.v_model_yr
|
||||
? parseInt(job.v_model_yr.match(/\d/g))
|
||||
? parseInt(job.v_model_yr.match(/\d/g).join(""), 10)
|
||||
: ""
|
||||
: "",
|
||||
v_year: (() => {
|
||||
const y = parseInt(job.v_model_yr);
|
||||
return isNaN(y) ? null : y < 100 ? y + (y >= (new Date().getFullYear() + 1) % 100 ? 1900 : 2000) : y;
|
||||
})(),
|
||||
v_make: job.v_make_desc || "",
|
||||
v_model: job.v_model_desc || "",
|
||||
|
||||
|
||||
@@ -77,9 +77,8 @@ const generateResetLink = async (email) => {
|
||||
*/
|
||||
const ensureExternalIdUnique = async (externalId) => {
|
||||
const resp = await client.request(CHECK_EXTERNAL_SHOP_ID, { key: externalId });
|
||||
if (resp.bodyshops.length) {
|
||||
throw { status: 400, message: `external_shop_id '${externalId}' is already in use.` };
|
||||
}
|
||||
|
||||
return !!resp.bodyshops.length;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -225,10 +224,25 @@ const patchPartsManagementProvisioning = async (req, res) => {
|
||||
*/
|
||||
const partsManagementProvisioning = async (req, res) => {
|
||||
const { logger } = req;
|
||||
const body = { ...req.body, userEmail: req.body.userEmail?.toLowerCase() };
|
||||
|
||||
// Trim and normalize email early
|
||||
const body = {
|
||||
...req.body,
|
||||
userEmail: req.body.userEmail?.trim().toLowerCase()
|
||||
};
|
||||
|
||||
const trim = (value) => (typeof value === "string" ? value.trim() : value);
|
||||
const trimIfString = (value) =>
|
||||
value !== null && value !== undefined && typeof value === "string" ? value.trim() : value;
|
||||
|
||||
try {
|
||||
// Ensure email is present and trimmed before checking registration
|
||||
if (!body.userEmail) {
|
||||
throw { status: 400, message: "userEmail is required" };
|
||||
}
|
||||
|
||||
await ensureEmailNotRegistered(body.userEmail);
|
||||
|
||||
requireFields(body, [
|
||||
"external_shop_id",
|
||||
"shopname",
|
||||
@@ -241,27 +255,69 @@ const partsManagementProvisioning = async (req, res) => {
|
||||
"phone",
|
||||
"userEmail"
|
||||
]);
|
||||
await ensureExternalIdUnique(body.external_shop_id);
|
||||
|
||||
logger.log("admin-create-shop-user", "debug", body.userEmail, null, {
|
||||
// Trim all top-level string fields
|
||||
const trimmedBody = {
|
||||
...body,
|
||||
external_shop_id: trim(body.external_shop_id),
|
||||
shopname: trim(body.shopname),
|
||||
address1: trim(body.address1),
|
||||
address2: trimIfString(body.address2),
|
||||
city: trim(body.city),
|
||||
state: trim(body.state),
|
||||
zip_post: trim(body.zip_post),
|
||||
country: trim(body.country),
|
||||
email: trim(body.email),
|
||||
phone: trim(body.phone),
|
||||
timezone: trimIfString(body.timezone),
|
||||
logoUrl: trimIfString(body.logoUrl),
|
||||
userPassword: body.userPassword, // passwords should NOT be trimmed (preserves intentional spaces if any, though rare)
|
||||
vendors: Array.isArray(body.vendors)
|
||||
? body.vendors.map((v) => ({
|
||||
name: trim(v.name),
|
||||
street1: trimIfString(v.street1),
|
||||
street2: trimIfString(v.street2),
|
||||
city: trimIfString(v.city),
|
||||
state: trimIfString(v.state),
|
||||
zip: trimIfString(v.zip),
|
||||
country: trimIfString(v.country),
|
||||
email: trimIfString(v.email),
|
||||
cost_center: trimIfString(v.cost_center),
|
||||
phone: trimIfString(v.phone),
|
||||
dmsid: trimIfString(v.dmsid),
|
||||
discount: v.discount ?? 0,
|
||||
due_date: v.due_date ?? null,
|
||||
favorite: v.favorite ?? [],
|
||||
active: v.active ?? true
|
||||
}))
|
||||
: []
|
||||
};
|
||||
|
||||
const duplicateCheck = await ensureExternalIdUnique(trimmedBody.external_shop_id);
|
||||
|
||||
if (duplicateCheck) {
|
||||
throw { status: 400, message: `external_shop_id '${trimmedBody.external_shop_id}' is already in use.` };
|
||||
}
|
||||
|
||||
logger.log("admin-create-shop-user", "debug", trimmedBody.userEmail, null, {
|
||||
request: req.body,
|
||||
ioadmin: true
|
||||
});
|
||||
|
||||
const shopInput = {
|
||||
shopname: body.shopname,
|
||||
address1: body.address1,
|
||||
address2: body.address2 || null,
|
||||
city: body.city,
|
||||
state: body.state,
|
||||
zip_post: body.zip_post,
|
||||
country: body.country,
|
||||
email: body.email,
|
||||
external_shop_id: body.external_shop_id,
|
||||
timezone: body.timezone || DefaultNewShop.timezone,
|
||||
phone: body.phone,
|
||||
shopname: trimmedBody.shopname,
|
||||
address1: trimmedBody.address1,
|
||||
address2: trimmedBody.address2,
|
||||
city: trimmedBody.city,
|
||||
state: trimmedBody.state,
|
||||
zip_post: trimmedBody.zip_post,
|
||||
country: trimmedBody.country,
|
||||
email: trimmedBody.email,
|
||||
external_shop_id: trimmedBody.external_shop_id,
|
||||
timezone: trimmedBody.timezone || DefaultNewShop.timezone,
|
||||
phone: trimmedBody.phone,
|
||||
logo_img_path: {
|
||||
src: body.logoUrl,
|
||||
src: trimmedBody.logoUrl || null, // allow empty logo
|
||||
width: "",
|
||||
height: "",
|
||||
headerMargin: DefaultNewShop.logo_img_path.headerMargin
|
||||
@@ -286,35 +342,37 @@ const partsManagementProvisioning = async (req, res) => {
|
||||
appt_alt_transport: DefaultNewShop.appt_alt_transport,
|
||||
md_jobline_presets: DefaultNewShop.md_jobline_presets,
|
||||
vendors: {
|
||||
data: body.vendors.map((v) => ({
|
||||
data: trimmedBody.vendors.map((v) => ({
|
||||
name: v.name,
|
||||
street1: v.street1 || null,
|
||||
street2: v.street2 || null,
|
||||
city: v.city || null,
|
||||
state: v.state || null,
|
||||
zip: v.zip || null,
|
||||
country: v.country || null,
|
||||
email: v.email || null,
|
||||
discount: v.discount ?? 0,
|
||||
due_date: v.due_date ?? null,
|
||||
cost_center: v.cost_center || null,
|
||||
favorite: v.favorite ?? [],
|
||||
phone: v.phone || null,
|
||||
active: v.active ?? true,
|
||||
dmsid: v.dmsid || null
|
||||
street1: v.street1,
|
||||
street2: v.street2,
|
||||
city: v.city,
|
||||
state: v.state,
|
||||
zip: v.zip,
|
||||
country: v.country,
|
||||
email: v.email,
|
||||
discount: v.discount,
|
||||
due_date: v.due_date,
|
||||
cost_center: v.cost_center,
|
||||
favorite: v.favorite,
|
||||
phone: v.phone,
|
||||
active: v.active,
|
||||
dmsid: v.dmsid
|
||||
}))
|
||||
}
|
||||
};
|
||||
|
||||
const newShopId = await insertBodyshop(shopInput);
|
||||
const userRecord = await createFirebaseUser(body.userEmail, body.userPassword);
|
||||
const userRecord = await createFirebaseUser(trimmedBody.userEmail, trimmedBody.userPassword);
|
||||
let resetLink = null;
|
||||
if (!body.userPassword) resetLink = await generateResetLink(body.userEmail);
|
||||
if (!trimmedBody.userPassword) {
|
||||
resetLink = await generateResetLink(trimmedBody.userEmail);
|
||||
}
|
||||
|
||||
const createdUser = await insertUserAssociation(userRecord.uid, body.userEmail, newShopId);
|
||||
const createdUser = await insertUserAssociation(userRecord.uid, trimmedBody.userEmail, newShopId);
|
||||
|
||||
return res.status(200).json({
|
||||
shop: { id: newShopId, shopname: body.shopname },
|
||||
shop: { id: newShopId, shopname: trimmedBody.shopname },
|
||||
user: {
|
||||
id: createdUser.id,
|
||||
email: createdUser.email,
|
||||
@@ -322,7 +380,7 @@ const partsManagementProvisioning = async (req, res) => {
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
logger.log("admin-create-shop-user-error", "error", body.userEmail, null, {
|
||||
logger.log("admin-create-shop-user-error", "error", body.userEmail || "unknown", null, {
|
||||
message: err.message,
|
||||
detail: err.detail || err
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user