feature/IO-3390-Parts-Management-2 - Add Job status patch route, Bodyshop Patch route, remove PAO from simplified parts filter.

This commit is contained in:
Dave
2025-10-07 12:11:53 -04:00
parent e50bbc3bcc
commit d573335eb0
13 changed files with 1751 additions and 1540 deletions

View File

@@ -1,5 +1,3 @@
const logger = require("../utils/logger");
const GraphQLClient = require("graphql-request").GraphQLClient;
//New bug introduced with Graphql Request.
@@ -14,17 +12,18 @@ const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
});
const rpsClient =
process.env.RPS_GRAPHQL_ENDPOINT && process.env.RPS_HASURA_ADMIN_SECRET ?
new GraphQLClient(process.env.RPS_GRAPHQL_ENDPOINT, {
headers: {
"x-hasura-admin-secret": process.env.RPS_HASURA_ADMIN_SECRET
}
}) : null;
process.env.RPS_GRAPHQL_ENDPOINT && process.env.RPS_HASURA_ADMIN_SECRET
? new GraphQLClient(process.env.RPS_GRAPHQL_ENDPOINT, {
headers: {
"x-hasura-admin-secret": process.env.RPS_HASURA_ADMIN_SECRET
}
})
: null;
if (!rpsClient) {
//System log to disable RPS functions
logger.log(`RPS secrets are not set. Client is not configured.`, "WARN", "redis", "api", {
});
console.log(`RPS secrets are not set. Client is not configured.`, "WARN", "redis", "api", {});
}
const unauthorizedClient = new GraphQLClient(process.env.GRAPHQL_ENDPOINT);

View File

@@ -7,7 +7,8 @@ const {
CREATE_SHOP,
DELETE_VENDORS_BY_SHOP,
DELETE_SHOP,
CREATE_USER
CREATE_USER,
UPDATE_BODYSHOP_BY_ID
} = require("../partsManagement.queries");
/**
@@ -131,6 +132,61 @@ const insertUserAssociation = async (uid, email, shopId) => {
return resp.insert_users_one;
};
/**
* PATCH handler for updating bodyshop fields.
* Allows patching: shopname, address1, address2, city, state, zip_post, country, email, timezone, phone, logo_img_path
* @param req
* @param res
* @returns {Promise<void>}
*/
const patchPartsManagementProvisioning = async (req, res) => {
const { id } = req.params;
const allowedFields = [
"shopname",
"address1",
"address2",
"city",
"state",
"zip_post",
"country",
"email",
"timezone",
"phone",
"logo_img_path"
];
const updateFields = {};
for (const field of allowedFields) {
if (req.body[field] !== undefined) {
updateFields[field] = req.body[field];
}
}
if (Object.keys(updateFields).length === 0) {
return res.status(400).json({ error: "No valid fields provided for update." });
}
// Check that the bodyshop has an external_shop_id before allowing patch
try {
// Fetch the bodyshop by id
const shopResp = await client.request(
`query GetBodyshop($id: uuid!) { bodyshops_by_pk(id: $id) { id external_shop_id } }`,
{ id }
);
if (!shopResp.bodyshops_by_pk?.external_shop_id) {
return res.status(400).json({ error: "Cannot patch: bodyshop does not have an external_shop_id." });
}
} catch (err) {
return res.status(500).json({ error: "Failed to validate bodyshop external_shop_id.", detail: err });
}
try {
const resp = await client.request(UPDATE_BODYSHOP_BY_ID, { id, fields: updateFields });
if (!resp.update_bodyshops_by_pk) {
return res.status(404).json({ error: "Bodyshop not found." });
}
return res.json(resp.update_bodyshops_by_pk);
} catch (err) {
return res.status(500).json({ error: "Failed to update bodyshop.", detail: err });
}
};
/**
* Handles provisioning a new shop for parts management.
* @param req
@@ -259,4 +315,4 @@ const partsManagementProvisioning = async (req, res) => {
}
};
module.exports = partsManagementProvisioning;
module.exports = { partsManagementProvisioning, patchPartsManagementProvisioning };

View File

@@ -298,6 +298,25 @@ const UPDATE_JOBLINE_BY_PK = `
}
`;
const UPDATE_BODYSHOP_BY_ID = `
mutation UpdateBodyshopById($id: uuid!, $fields: bodyshops_set_input!) {
update_bodyshops_by_pk(pk_columns: { id: $id }, _set: $fields) {
id
shopname
address1
address2
city
state
zip_post
country
email
timezone
phone
logo_img_path
}
}
`;
module.exports = {
GET_BODYSHOP_STATUS,
GET_VEHICLE_BY_SHOP_VIN,
@@ -329,5 +348,6 @@ module.exports = {
DELETE_PARTS_ORDERS_BY_JOB_IDS,
UPSERT_JOBLINES,
GET_JOBLINE_IDS_BY_JOBID_UNQSEQ,
UPDATE_JOBLINE_BY_PK
UPDATE_JOBLINE_BY_PK,
UPDATE_BODYSHOP_BY_ID
};

View File

@@ -1,166 +0,0 @@
openapi: 3.0.3
info:
title: Parts Management Provisioning API
description: API endpoint to provision a new shop and user in the Parts Management system.
version: 1.0.0
paths:
/parts-management/provision:
post:
summary: Provision a new parts management shop and user
operationId: partsManagementProvisioning
tags:
- Parts Management
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- external_shop_id
- shopname
- address1
- city
- state
- zip_post
- country
- email
- phone
- userEmail
properties:
external_shop_id:
type: string
description: External shop ID (must be unique)
shopname:
type: string
address1:
type: string
address2:
type: string
nullable: true
city:
type: string
state:
type: string
zip_post:
type: string
country:
type: string
email:
type: string
phone:
type: string
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
nullable: true
timezone:
type: string
nullable: true
vendors:
type: array
items:
type: object
properties:
name:
type: string
street1:
type: string
nullable: true
street2:
type: string
nullable: true
city:
type: string
nullable: true
state:
type: string
nullable: true
zip:
type: string
nullable: true
country:
type: string
nullable: true
email:
type: string
format: email
nullable: true
discount:
type: number
nullable: true
due_date:
type: string
format: date
nullable: true
cost_center:
type: string
nullable: true
favorite:
type: array
items:
type: string
nullable: true
phone:
type: string
nullable: true
active:
type: boolean
nullable: true
dmsid:
type: string
nullable: true
responses:
'200':
description: Shop and user successfully created
content:
application/json:
schema:
type: object
properties:
shop:
type: object
properties:
id:
type: string
format: uuid
shopname:
type: string
user:
type: object
properties:
id:
type: string
email:
type: string
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:
application/json:
schema:
type: object
properties:
error:
type: string
'500':
description: Internal server error
content:
application/json:
schema:
type: object
properties:
error:
type: string

View File

@@ -0,0 +1,40 @@
const client = require("../graphql-client/graphql-client").client;
const { UPDATE_JOB_BY_ID } = require("../integrations/partsManagement/partsManagement.queries");
/**
* PATCH handler to update job status (parts management only)
* @param req
* @param res
* @returns {Promise<void>}
*/
module.exports = async (req, res) => {
const { id } = req.params;
const { status } = req.body;
if (!status) {
return res.status(400).json({ error: "Missing required field: status" });
}
try {
// Fetch job to get shopid
const jobResp = await client.request(`query GetJob($id: uuid!) { jobs_by_pk(id: $id) { id shopid } }`, { id });
const job = jobResp.jobs_by_pk;
if (!job) {
return res.status(404).json({ error: "Job not found" });
}
// Fetch bodyshop to check external_shop_id
const shopResp = await client.request(
`query GetBodyshop($id: uuid!) { bodyshops_by_pk(id: $id) { id external_shop_id } }`,
{ id: job.shopid }
);
if (!shopResp.bodyshops_by_pk || !shopResp.bodyshops_by_pk.external_shop_id) {
return res.status(400).json({ error: "Cannot patch: parent bodyshop does not have an external_shop_id." });
}
// Update job status
const updateResp = await client.request(UPDATE_JOB_BY_ID, { id, job: { status } });
if (!updateResp.update_jobs_by_pk) {
return res.status(404).json({ error: "Job not found after update" });
}
return res.json(updateResp.update_jobs_by_pk);
} catch (err) {
return res.status(500).json({ error: "Failed to update job status.", detail: err });
}
};

View File

@@ -19,11 +19,15 @@ if (typeof VSSTA_INTEGRATION_SECRET === "string" && VSSTA_INTEGRATION_SECRET.len
if (typeof PARTS_MANAGEMENT_INTEGRATION_SECRET === "string" && PARTS_MANAGEMENT_INTEGRATION_SECRET.length > 0) {
const XML_BODY_LIMIT = "10mb"; // Set a limit for XML body size
const partsManagementProvisioning = require("../integrations/partsManagement/endpoints/partsManagementProvisioning");
const {
partsManagementProvisioning,
patchPartsManagementProvisioning
} = require("../integrations/partsManagement/endpoints/partsManagementProvisioning");
const partsManagementDeprovisioning = require("../integrations/partsManagement/endpoints/partsManagementDeprovisioning");
const partsManagementIntegrationMiddleware = require("../middleware/partsManagementIntegrationMiddleware");
const partsManagementVehicleDamageEstimateAddRq = require("../integrations/partsManagement/endpoints/vehicleDamageEstimateAddRq");
const partsManagementVehicleDamageEstimateChqRq = require("../integrations/partsManagement/endpoints/vehicleDamageEstimateChgRq");
const patchJobStatus = require("../job/patchJobStatus");
/**
* Route to handle Vehicle Damage Estimate Add Request
@@ -55,6 +59,20 @@ if (typeof PARTS_MANAGEMENT_INTEGRATION_SECRET === "string" && PARTS_MANAGEMENT_
* Route to handle Parts Management Provisioning
*/
router.post("/parts-management/provision", partsManagementIntegrationMiddleware, partsManagementProvisioning);
/**
* PATCH route to update Parts Management Provisioning info
*/
router.patch(
"/parts-management/provision/:id",
partsManagementIntegrationMiddleware,
patchPartsManagementProvisioning
);
/**
* PATCH route to update job status (parts management only)
*/
router.patch("/parts-management/job/:id/status", partsManagementIntegrationMiddleware, patchJobStatus);
} else {
logger.logger.warn("PARTS_MANAGEMENT_INTEGRATION_SECRET is not set — skipping /parts-management/provision route");
}