diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 36ec1a0c9..86da3ae37 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -5305,6 +5305,27 @@ + + ro_posting + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + sendmaterialscosting false diff --git a/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx b/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx index 966cadf13..3445c88c1 100644 --- a/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx +++ b/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx @@ -92,6 +92,7 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) { rowKey="center" dataSource={allocationsSummary} locale={{ emptyText: t("dms.labels.refreshallocations") }} + scroll={{ x: true }} summary={() => { const totals = allocationsSummary && diff --git a/client/src/components/job-detail-cards/job-detail-cards.parts.component.jsx b/client/src/components/job-detail-cards/job-detail-cards.parts.component.jsx index 9f3c32820..949d42eae 100644 --- a/client/src/components/job-detail-cards/job-detail-cards.parts.component.jsx +++ b/client/src/components/job-detail-cards/job-detail-cards.parts.component.jsx @@ -90,7 +90,7 @@ export function JobDetailCardsPartsComponent({ loading, data, jobRO }) { .filter(onlyUnique) .map((s) => { return { - text: s || "No Status*", + text: s || t("dashboard.errors.status"), value: [s] }; })) || @@ -103,7 +103,7 @@ export function JobDetailCardsPartsComponent({ loading, data, jobRO }) {
- +
); diff --git a/client/src/components/job-detail-lines/job-lines.component.jsx b/client/src/components/job-detail-lines/job-lines.component.jsx index 2cadcf4e8..02dd2cda6 100644 --- a/client/src/components/job-detail-lines/job-lines.component.jsx +++ b/client/src/components/job-detail-lines/job-lines.component.jsx @@ -318,7 +318,7 @@ export function JobLinesComponent({ .filter(onlyUnique) .map((s) => { return { - text: s || "No Status*", + text: s || t("dashboard.errors.status"), value: [s] }; })) || diff --git a/client/src/components/job-parts-queue-count/job-parts-queue-count.component.jsx b/client/src/components/job-parts-queue-count/job-parts-queue-count.component.jsx index 8fbee411c..c7cc3a115 100644 --- a/client/src/components/job-parts-queue-count/job-parts-queue-count.component.jsx +++ b/client/src/components/job-parts-queue-count/job-parts-queue-count.component.jsx @@ -1,8 +1,9 @@ import { useMemo } from "react"; -import { Col, Row, Tag, Tooltip } from "antd"; +import { Tag, Tooltip } from "antd"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; +import { useTranslation } from "react-i18next"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop @@ -11,65 +12,67 @@ const mapDispatchToProps = () => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export const DEFAULT_COL_LAYOUT = { xs: 24, sm: 24, md: 8, lg: 4, xl: 4, xxl: 4 }; - export default connect(mapStateToProps, mapDispatchToProps)(JobPartsQueueCount); -export function JobPartsQueueCount({ bodyshop, parts, defaultColLayout = DEFAULT_COL_LAYOUT }) { +export function JobPartsQueueCount({ bodyshop, parts }) { + const { t } = useTranslation(); const partsStatus = useMemo(() => { if (!parts) return null; + const statusKeys = ["default_bo", "default_ordered", "default_received", "default_returned"]; return parts.reduce( (acc, val) => { if (val.part_type === "PAS" || val.part_type === "PASL") return acc; acc.total = acc.total + val.count; acc[val.status] = acc[val.status] + val.count; - return acc; }, { total: 0, null: 0, - [bodyshop.md_order_statuses.default_bo]: 0, - [bodyshop.md_order_statuses.default_ordered]: 0, - [bodyshop.md_order_statuses.default_received]: 0, - [bodyshop.md_order_statuses.default_returned]: 0 + ...Object.fromEntries(statusKeys.map((key) => [bodyshop.md_order_statuses[key], 0])) } ); }, [bodyshop, parts]); if (!parts) return null; return ( - - - - {partsStatus.total} - - - - - {partsStatus["null"]} - - - - - {partsStatus[bodyshop.md_order_statuses.default_ordered]} - - - - - {partsStatus[bodyshop.md_order_statuses.default_received]} - - - - - {partsStatus[bodyshop.md_order_statuses.default_returned]} - - - - - {partsStatus[bodyshop.md_order_statuses.default_bo]} - - - +
+ + {partsStatus.total} + + + + {partsStatus["null"]} + + + + + {partsStatus[bodyshop.md_order_statuses.default_bo]} + + + + + {partsStatus[bodyshop.md_order_statuses.default_ordered]} + + + + + {partsStatus[bodyshop.md_order_statuses.default_received]} + + + + + {partsStatus[bodyshop.md_order_statuses.default_returned]} + + +
); } diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx index 49439fa00..bde88d161 100644 --- a/client/src/components/jobs-list/jobs-list.component.jsx +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -166,7 +166,7 @@ export function JobsList({ bodyshop }) { .filter(onlyUnique) .map((s) => { return { - text: s || "No Status*", + text: s || t("dashboard.errors.status"), value: [s] }; }) diff --git a/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx b/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx index b88b713c2..b218b46f4 100644 --- a/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx +++ b/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx @@ -165,7 +165,7 @@ export function JobsReadyList({ bodyshop }) { .filter(onlyUnique) .map((s) => { return { - text: s || "No Status*", + text: s || t("dashboard.errors.status"), value: [s] }; }) diff --git a/client/src/components/parts-queue-card/parts-queue-job-lines.component.jsx b/client/src/components/parts-queue-card/parts-queue-job-lines.component.jsx index 7d108954c..066e937e8 100644 --- a/client/src/components/parts-queue-card/parts-queue-job-lines.component.jsx +++ b/client/src/components/parts-queue-card/parts-queue-job-lines.component.jsx @@ -145,7 +145,7 @@ export function PartsQueueJobLinesComponent({ loading, jobLines }) { .filter(onlyUnique) .map((s) => { return { - text: s || "No Status*", + text: s || t("dashboard.errors.status"), value: [s] }; })) || diff --git a/client/src/components/parts-queue-list/parts-queue.list.component.jsx b/client/src/components/parts-queue-list/parts-queue.list.component.jsx index bb4b72b79..a947eaefa 100644 --- a/client/src/components/parts-queue-list/parts-queue.list.component.jsx +++ b/client/src/components/parts-queue-list/parts-queue.list.component.jsx @@ -171,7 +171,7 @@ export function PartsQueueListComponent({ bodyshop }) { filters: bodyshop.md_ro_statuses.active_statuses.map((s) => { return { - text: s || "No Status*", + text: s || t("dashboard.errors.status"), value: [s] }; }) || [], diff --git a/client/src/components/production-list-columns/production-list-columns.data.jsx b/client/src/components/production-list-columns/production-list-columns.data.jsx index f26733c36..165e15c1d 100644 --- a/client/src/components/production-list-columns/production-list-columns.data.jsx +++ b/client/src/components/production-list-columns/production-list-columns.data.jsx @@ -34,8 +34,9 @@ const getEmployeeName = (employeeId, employees) => { return employee ? `${employee.first_name} ${employee.last_name}` : ""; }; -const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatments }) => { +const productionListColumnsData = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatments }) => { const { Enhanced_Payroll } = treatments; + return [ { title: i18n.t("jobs.actions.viewdetail"), @@ -313,7 +314,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme activeStatuses ?.map((s) => { return { - text: s || "No Status*", + text: s || i18n.t("dashboard.errors.status"), value: [s] }; }) @@ -584,4 +585,4 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme } ]; }; -export default r; +export default productionListColumnsData; diff --git a/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx b/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx index ccae00d4f..6e7a4edfd 100644 --- a/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx +++ b/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx @@ -138,6 +138,15 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) { )} + {bodyshop.pbs_serialnumber && ( + + + + )} {bodyshop.pbs_serialnumber && ( { + filters: bodyshop?.md_ro_statuses?.parts_statuses?.map((s) => { return { text: s, value: [s] }; }), onFilter: (value, record) => value.includes(record.status) diff --git a/client/src/components/tech-lookup-jobs-list/tech-lookup-jobs-list.component.jsx b/client/src/components/tech-lookup-jobs-list/tech-lookup-jobs-list.component.jsx index 0febbb24d..dfba5a133 100644 --- a/client/src/components/tech-lookup-jobs-list/tech-lookup-jobs-list.component.jsx +++ b/client/src/components/tech-lookup-jobs-list/tech-lookup-jobs-list.component.jsx @@ -111,7 +111,7 @@ export function TechLookupJobsList({ bodyshop }) { .filter(onlyUnique) .map((s) => { return { - text: s || "No Status*", + text: s || t("dashboard.errors.status"), value: [s] }; })) || diff --git a/client/src/pages/dms/dms.container.jsx b/client/src/pages/dms/dms.container.jsx index 78f86a0c6..afc67a4a7 100644 --- a/client/src/pages/dms/dms.container.jsx +++ b/client/src/pages/dms/dms.container.jsx @@ -48,7 +48,7 @@ export const socket = SocketIO( export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, insertAuditTrail }) { const { t } = useTranslation(); - const [logLevel, setLogLevel] = useState("DEBUG"); + const [logLevel, setLogLevel] = useState(determineDmsType(bodyshop) === "pbs" ? "INFO" : "DEBUG"); const history = useNavigate(); const [logs, setLogs] = useState([]); const search = queryString.parse(useLocation().search); diff --git a/client/src/pages/tech-assigned-prod-jobs/tech-assigned-prod-jobs.component.jsx b/client/src/pages/tech-assigned-prod-jobs/tech-assigned-prod-jobs.component.jsx index 1849b9b63..5cc1f308d 100644 --- a/client/src/pages/tech-assigned-prod-jobs/tech-assigned-prod-jobs.component.jsx +++ b/client/src/pages/tech-assigned-prod-jobs/tech-assigned-prod-jobs.component.jsx @@ -97,7 +97,7 @@ export function TechAssignedProdJobs({ setTimeTicketTaskContext, technician, bod .filter(onlyUnique) .map((s) => { return { - text: s || "No Status*", + text: s || t("dashboard.errors.status"), value: [s] }; })) || diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index aca3da3e2..342843b64 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -321,6 +321,7 @@ "itc_local": "Local Tax is ITC?", "itc_state": "State Tax is ITC?", "mappingname": "DMS Mapping Name", + "ro_posting": "Create $0 RO?", "sendmaterialscosting": "Materials Cost as % of Sale", "srcco": "Source Company #/Dealer #" }, @@ -996,6 +997,7 @@ "insco": "No Ins. Co.*", "refreshrequired": "You must refresh the dashboard data to see this component.", "status": "No Status*", + "status_normal": "No Status", "updatinglayout": "Error saving updated layout {{message}}" }, "labels": { diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 345712670..124f78f36 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -321,6 +321,7 @@ "itc_local": "", "itc_state": "", "mappingname": "", + "ro_posting": "", "sendmaterialscosting": "", "srcco": "" }, @@ -996,6 +997,7 @@ "insco": "", "refreshrequired": "", "status": "", + "status_normal": "", "updatinglayout": "" }, "labels": { diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index c3f680059..726fa4740 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -321,6 +321,7 @@ "itc_local": "", "itc_state": "", "mappingname": "", + "ro_posting": "", "sendmaterialscosting": "", "srcco": "" }, @@ -996,6 +997,7 @@ "insco": "", "refreshrequired": "", "status": "", + "status_normal": "", "updatinglayout": "" }, "labels": { diff --git a/server/accounting/pbs/pbs-constants.js b/server/accounting/pbs/pbs-constants.js index c8a6d54f3..7c54f4d0e 100644 --- a/server/accounting/pbs/pbs-constants.js +++ b/server/accounting/pbs/pbs-constants.js @@ -6,10 +6,6 @@ const PBS_CREDENTIALS = { }; exports.PBS_CREDENTIALS = PBS_CREDENTIALS; -// const cdkDomain = -// process.env.NODE_ENV === "production" -// ? "https://3pa.dmotorworks.com" -// : "https://uat-3pa.dmotorworks.com"; const pbsDomain = `https://partnerhub.pbsdealers.com/json/reply`; exports.PBS_ENDPOINTS = { @@ -18,5 +14,9 @@ exports.PBS_ENDPOINTS = { VehicleGet: `${pbsDomain}/VehicleGet`, AccountingPostingChange: `${pbsDomain}/AccountingPostingChange`, ContactChange: `${pbsDomain}/ContactChange`, - VehicleChange: `${pbsDomain}/VehicleChange` + VehicleChange: `${pbsDomain}/VehicleChange`, + RepairOrderChange: `${pbsDomain}/RepairOrderChange`, //TODO: Verify that this is correct. Docs have /reply/ in path. + RepairOrderGet: `${pbsDomain}/RepairOrderGet`, + RepairOrderContactVehicleGet: `${pbsDomain}/RepairOrderContactVehicleGet`, + RepairOrderContactVehicleChange: `${pbsDomain}/RepairOrderContactVehicleChange`, }; diff --git a/server/accounting/pbs/pbs-job-export.js b/server/accounting/pbs/pbs-job-export.js index 127c9d5e2..feb3dc064 100644 --- a/server/accounting/pbs/pbs-job-export.js +++ b/server/accounting/pbs/pbs-job-export.js @@ -19,12 +19,11 @@ axios.interceptors.request.use((x) => { ...x.headers[x.method], ...x.headers }; - const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${ - x.url - } | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`; - //console.log(printable); + const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${x.url + } | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`; + //logRequestToFile(printable); - CdkBase.createJsonEvent(socket, "SILLY", `Raw Request: ${printable}`, x.data); + CdkBase.createJsonEvent(socket, "DEBUG", `Raw Request: ${printable}`, x.data); return x; }); @@ -32,23 +31,39 @@ axios.interceptors.request.use((x) => { axios.interceptors.response.use((x) => { const socket = x.config.socket; - const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`; - //console.log(printable); - CdkBase.createJsonEvent(socket, "SILLY", `Raw Response: ${printable}`, x.data); + const printable = `${new Date()} | Response: ${x.status} ${x.statusText} |${JSON.stringify(x.data)}`; + //logRequestToFile(printable); + CdkBase.createJsonEvent(socket, "DEBUG", `Raw Response: ${printable}`, x.data); return x; }); +const fs = require('fs'); +const path = require("path"); +function logRequestToFile(printable) { + try { + const logDir = path.join(process.cwd(), "logs"); + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } + const logFile = path.join(logDir, "pbs-http.log"); + fs.appendFileSync(logFile, `${printable}\n`); + } catch (err) { + console.error("Unexpected error in logRequestToFile:", err); + } +} + + exports.default = async function (socket, { txEnvelope, jobid }) { socket.logEvents = []; socket.recordid = jobid; socket.txEnvelope = txEnvelope; try { - CdkBase.createLogEvent(socket, "DEBUG", `Received Job export request for id ${jobid}`); + CdkBase.createLogEvent(socket, "INFO", `Received Job export request for id ${jobid}`); const JobData = await QueryJobData(socket, jobid); socket.JobData = JobData; - CdkBase.createLogEvent(socket, "DEBUG", `Querying the DMS for the Vehicle Record.`); + CdkBase.createLogEvent(socket, "INFO", `Querying the DMS for the Vehicle Record.`); //Query for the Vehicle record to get the associated customer. socket.DmsVeh = await QueryVehicleFromDms(socket); //Todo: Need to validate the lines and methods below. @@ -69,42 +84,52 @@ exports.default = async function (socket, { txEnvelope, jobid }) { exports.PbsSelectedCustomer = async function PbsSelectedCustomer(socket, selectedCustomerId) { try { - if (socket.JobData.bodyshop.pbs_configuration.disablecontactvehicle === false) { - CdkBase.createLogEvent(socket, "DEBUG", `User selected customer ${selectedCustomerId || "NEW"}`); + socket.selectedCustomerId = selectedCustomerId; + if (socket.JobData.bodyshop.pbs_configuration.disablecontactvehicle !== true) { + CdkBase.createLogEvent(socket, "INFO", `User selected customer ${selectedCustomerId || "NEW"}`); //Upsert the contact information as per Wafaa's Email. CdkBase.createLogEvent( socket, - "DEBUG", - `Upserting contact information to DMS for ${ - socket.JobData.ownr_fn || "" + "INFO", + `Upserting contact information to DMS for ${socket.JobData.ownr_fn || "" } ${socket.JobData.ownr_ln || ""} ${socket.JobData.ownr_co_nm || ""}` ); const ownerRef = await UpsertContactData(socket, selectedCustomerId); - - CdkBase.createLogEvent(socket, "DEBUG", `Upserting vehicle information to DMS for ${socket.JobData.v_vin}`); - await UpsertVehicleData(socket, ownerRef.ReferenceId); + socket.ownerRef = ownerRef; + CdkBase.createLogEvent(socket, "INFO", `Upserting vehicle information to DMS for ${socket.JobData.v_vin}`); + const vehicleRef = await UpsertVehicleData(socket, ownerRef.ReferenceId); + socket.vehicleRef = vehicleRef; } else { CdkBase.createLogEvent( socket, - "DEBUG", - `Contact and Vehicle updates disabled. Skipping to accounting data insert.` + "INFO", + `Contact and Vehicle updates disabled. Querying data and skipping to accounting data insert.` ); + //Must query for records to insert $0 RO. + if (!socket.ownerRef) { + const ownerRef = (await QueryCustomerBycodeFromDms(socket, selectedCustomerId))?.[0]; + socket.ownerRef = ownerRef; + } + const vehicleRef = await GetVehicleData(socket, socket.ownerRef?.ReferenceId || socket.selectedCustomerId); + socket.vehicleRef = vehicleRef; } - CdkBase.createLogEvent(socket, "DEBUG", `Inserting account data.`); - CdkBase.createLogEvent(socket, "DEBUG", `Inserting accounting posting data..`); + CdkBase.createLogEvent(socket, "INFO", `Inserting accounting posting data..`); const insertResponse = await InsertAccountPostingData(socket); if (insertResponse.WasSuccessful) { - CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported.`); - await MarkJobExported(socket, socket.JobData.id); + if (socket.JobData.bodyshop.pbs_configuration.ro_posting) { + await CreateRepairOrderInPBS(socket, socket.ownerRef, socket.vehicleRef) + } + CdkBase.createLogEvent(socket, "INFO", `Marking job as exported.`); + await MarkJobExported(socket, socket.JobData.id); socket.emit("export-success", socket.JobData.id); } else { CdkBase.createLogEvent(socket, "ERROR", `Export was not successful.`); } } catch (error) { - CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkSelectedCustomer. ${error}`); + CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsSelectedCustomer. ${error}`); await InsertFailedExportLog(socket, error); } }; @@ -112,22 +137,22 @@ exports.PbsSelectedCustomer = async function PbsSelectedCustomer(socket, selecte // Was Successful async function CheckForErrors(socket, response) { if (response.WasSuccessful === undefined || response.WasSuccessful === true) { - CdkBase.createLogEvent(socket, "DEBUG", `Successful response from DMS. ${response.Message || ""}`); + CdkBase.createLogEvent(socket, "INFO", `Successful response from DMS. ${response.Message || ""}`); } else { CdkBase.createLogEvent(socket, "ERROR", `Error received from DMS: ${response.Message}`); - CdkBase.createLogEvent(socket, "SILLY", `Error received from DMS: ${JSON.stringify(response)}`); + CdkBase.createLogEvent(socket, "DEBUG", `Error received from DMS: ${JSON.stringify(response)}`); } } exports.CheckForErrors = CheckForErrors; async function QueryJobData(socket, jobid) { - CdkBase.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`); + CdkBase.createLogEvent(socket, "INFO", `Querying job data for id ${jobid}`); const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); const result = await client .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` }) .request(queries.QUERY_JOBS_FOR_PBS_EXPORT, { id: jobid }); - CdkBase.createLogEvent(socket, "SILLY", `Job data query result ${JSON.stringify(result, null, 2)}`); + CdkBase.createLogEvent(socket, "DEBUG", `Job data query result ${JSON.stringify(result, null, 2)}`); return result.jobs_by_pk; } @@ -247,15 +272,15 @@ async function UpsertContactData(socket, selectedCustomerId) { Code: socket.JobData.owner.accountingid, ...(socket.JobData.ownr_co_nm ? { - //LastName: socket.JobData.ownr_ln, - FirstName: socket.JobData.ownr_co_nm, - IsBusiness: true - } + //LastName: socket.JobData.ownr_ln, + FirstName: socket.JobData.ownr_co_nm, + IsBusiness: true + } : { - LastName: socket.JobData.ownr_ln, - FirstName: socket.JobData.ownr_fn, - IsBusiness: false - }), + LastName: socket.JobData.ownr_ln, + FirstName: socket.JobData.ownr_fn, + IsBusiness: false + }), //Salutation: "String", //MiddleName: "String", @@ -332,7 +357,7 @@ async function UpsertVehicleData(socket, ownerRef) { //FleetNumber: "String", //Status: "String", OwnerRef: ownerRef, // "00000000000000000000000000000000", - ModelNumber: socket.JobData.vehicle && socket.JobData.vehicle.v_makecode, + // ModelNumber: socket.JobData.vehicle && socket.JobData.vehicle.v_makecode, Make: socket.JobData.v_make_desc, Model: socket.JobData.v_model_desc, Trim: socket.JobData.vehicle && socket.JobData.vehicle.v_trimcode, @@ -340,7 +365,7 @@ async function UpsertVehicleData(socket, ownerRef) { Year: socket.JobData.v_model_yr, Odometer: socket.JobData.kmout, ExteriorColor: { - Code: socket.JobData.v_color, + // Code: socket.JobData.v_color, Description: socket.JobData.v_color } // InteriorColor: { Code: "String", Description: "String" }, @@ -470,6 +495,57 @@ async function UpsertVehicleData(socket, ownerRef) { } } +async function GetVehicleData(socket, ownerRef) { + try { + const { data: { Vehicles } } = await axios.post( + PBS_ENDPOINTS.VehicleGet, + { + SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, + // "VehicleId": "00000000000000000000000000000000", + // "Year": "String", + // "YearFrom": "String", + // "YearTo": "String", + // "Make": "String", + // "Model": "String", + // "Trim": "String", + // "ModelNumber": "String", + // "StockNumber": "String", + VIN: socket.JobData.v_vin, + // "LicenseNumber": "String", + // "Lot": "String", + // "Status": "String", + // "StatusList": ["String"], + // "OwnerRef": "00000000000000000000000000000000", + // "ModifiedSince": "0001-01-01T00:00:00.0000000Z", + // "ModifiedUntil": "0001-01-01T00:00:00.0000000Z", + // "LastSaleSince": "0001-01-01T00:00:00.0000000Z", + // "VehicleIDList": ["00000000000000000000000000000000"], + // "IncludeInactive": false, + // "IncludeBuildVehicles": false, + // "IncludeBlankLot": false, + // "ShortVIN": "String", + // "ResultLimit": 0, + // "LotAccessDivisions": [0], + // "OdometerTo": 0, + // "OdometerFrom": 0 + } + , + { auth: PBS_CREDENTIALS, socket } + ); + CheckForErrors(socket, Vehicles); + if (Vehicles.length === 1) { + return Vehicles[0]; + + } else { + CdkBase.createLogEvent(socket, "ERROR", `Error in Getting Vehicle Data - ${Vehicles.length} vehicle(s) found`); + } + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`); + throw new Error(error); + } +} + + async function InsertAccountPostingData(socket) { try { const allocations = await CalculateAllocations(socket, socket.JobData.id); @@ -572,7 +648,7 @@ async function InsertAccountPostingData(socket) { } async function MarkJobExported(socket, jobid) { - CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported for id ${jobid}`); + CdkBase.createLogEvent(socket, "INFO", `Marking job as exported for id ${jobid}`); const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); const result = await client .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` }) @@ -618,3 +694,158 @@ async function InsertFailedExportLog(socket, error) { CdkBase.createLogEvent(socket, "ERROR", `Error in InsertFailedExportLog - ${error} - ${JSON.stringify(error2)}`); } } + + +async function CreateRepairOrderInPBS(socket) { + try { + const { RepairOrders } = await RepairOrderGet(socket); + if (RepairOrders.length === 0) { + const InsertedRepairOrder = await RepairOrderChange(socket) + socket.InsertedRepairOrder = InsertedRepairOrder; + CdkBase.createLogEvent(socket, "INFO", `No repair orders found for vehicle. Inserting record.`); + + } else if (RepairOrders.length > 0) { + //Find out if it's a matching RO. + //This logic is used because the integration will simply add another line to an open RO if it exists. + const matchingRo = RepairOrders.find(ro => ro.Memo?.toLowerCase()?.includes(socket.JobData.ro_number.toLowerCase())) + if (!matchingRo) { + CdkBase.createLogEvent(socket, "INFO", `ROs found for vehicle, but none match. Inserting record.`); + const InsertedRepairOrder = await RepairOrderChange(socket) + socket.InsertedRepairOrder = InsertedRepairOrder; + } else { + CdkBase.createLogEvent(socket, "WARN", `Repair order appears to already exist in PBS. ${matchingRo.RepairOrderNumber}`); + } + } + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error in CreateRepairOrderInPBS - ${error} - ${JSON.stringify(error)}`); + } +} + +async function RepairOrderGet(socket) { + try { + const { data: RepairOrderGet } = await axios.post( + PBS_ENDPOINTS.RepairOrderGet, + { + SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, + //"RepairOrderId": "374728766", + //"RepairOrderNumber": "4" || socket.JobData.ro_number, + //"RawRepairOrderNumber": socket.JobData.ro_number, + // "Tag": "String", + //"ContactRef": socket.contactRef, + // "ContactRefList": ["00000000000000000000000000000000"], + "VehicleRef": socket.vehicleRef?.ReferenceId || socket.vehicleRef?.VehicleId, + // "VehicleRefList": ["00000000000000000000000000000000"], + // "Status": "String", + // "CashieredSince": "0001-01-01T00:00:00.0000000Z", + // "CashieredUntil": "0001-01-01T00:00:00.0000000Z", + // "OpenDateSince": "0001-01-01T00:00:00.0000000Z", + // "OpenDateUntil": "0001-01-01T00:00:00.0000000Z", + //"ModifiedSince": "2025-01-01T00:00:00.0000000Z", + // "ModifiedUntil": "0001-01-01T00:00:00.0000000Z", + // "Shop": "String" + }, + { auth: PBS_CREDENTIALS, socket } + ); + CheckForErrors(socket, RepairOrderGet); + return RepairOrderGet; + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error in RepairOrderChange - ${error}`); + throw new Error(error); + } +} + +async function RepairOrderChange(socket) { + try { + const { data: RepairOrderChangeResponse } = await axios.post( + PBS_ENDPOINTS.RepairOrderChange, + { //Additional details at https://partnerhub.pbsdealers.com/json/metadata?op=RepairOrderChange + "RepairOrderInfo": { + //"Id": "string/00000000-0000-0000-0000-000000000000", + //"RepairOrderId": "00000000000000000000000000000000", + SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, + "RepairOrderNumber": "00000000000000000000000000000000", //This helps force a new RO. + "RawRepairOrderNumber": "00000000000000000000000000000000", + // "RepairOrderNumber": socket.JobData.ro_number, //These 2 values are ignored as confirmed by PBS. + // "RawRepairOrderNumber": socket.JobData.ro_number, + "DateOpened": moment(), + // "DateOpenedUTC": "0001-01-01T00:00:00.0000000Z", + // "DateCashiered": "0001-01-01T00:00:00.0000000Z", + // "DateCashieredUTC": "0001-01-01T00:00:00.0000000Z", + "DatePromised": socket.JobData.scheduled_completion, + // "DatePromisedUTC": "0001-01-01T00:00:00.0000000Z", + "DateVehicleCompleted": socket.JobData.actual_completion, + // "DateCustomerNotified": "0001-01-01T00:00:00.0000000Z", + // "CSR": "String", + // "CSRRef": "00000000000000000000000000000000", + // "BookingUser": "String", + // "BookingUserRef": "00000000000000000000000000000000", + "ContactRef": socket.ownerRef?.ReferenceId || socket.ownerRef?.ContactId, + "VehicleRef": socket.vehicleRef?.ReferenceId || socket.vehicleRef?.VehicleId, + "MileageIn": socket.JobData.km_in, + "Tag": "BODYSHOP", + //"Status": "CLOSED", //Values here do not impact the status. Confirmed by PBS support. + Requests: [ + { + // "RepairOrderRequestRef": "b1842ecad62c4279bbc2fef4f6bf6cde", + // "RepairOrderRequestId": 1, + // "CSR": "PBS", + // "CSRRef": "1ce12ac692564e94bda955d529ee911a", + // "Skill": "GEN", + "RequestCode": "MISC", + "RequestDescription": `VEHICLE REPAIRED AT BODYSHOP. PLEASE REFERENCE IMEX SHOP MANAGEMENT SYSTEM. ${socket.txEnvelope.story}`, + "Status": "Completed", + // "TechRef": "00000000000000000000000000000000", + "AllowedHours": 0, + "EstimateLabour": 0, + "EstimateParts": 0, + "ComeBack": false, + "AddedOperation": true, + "PartLines": [], + "PartRequestLines": [], + "LabourLines": [], + "SubletLines": [], + "TimePunches": [], + "Summary": { + "Labour": 0, + "Parts": 0, + "OilGas": 0, + "SubletTow": 0, + "Misc": 0, + "Environment": 0, + "ShopSupplies": 0, + "Freight": 0, + "WarrantyDeductible": 0, + "Discount": 0, + "SubTotal": 0, + "Tax1": 0, + "Tax2": 0, + "InvoiceTotal": 0, + "CustomerDeductible": 0, + "GrandTotal": 0, + "LabourDiscount": 0, + "PartDiscount": 0, + "ServiceFeeTotal": 0, + "OEMDiscount": 0 + }, + "LineType": "RequestLine", + }, + ], + + "Memo": socket.txEnvelope.story, + + }, + "IsAsynchronous": false, + // "UserRequest": "String", + // "UserRef": "00000000000000000000000000000000" + } + + , + { auth: PBS_CREDENTIALS, socket } + ); + CheckForErrors(socket, RepairOrderChangeResponse); + return RepairOrderChangeResponse; + } catch (error) { + CdkBase.createLogEvent(socket, "ERROR", `Error in RepairOrderChange - ${error}`); + throw new Error(error); + } +} \ No newline at end of file diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index d6ebfce64..3e29387bb 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -420,6 +420,8 @@ query QUERY_JOBS_FOR_PBS_EXPORT($id: uuid!) { v_make_desc v_color ca_customer_gst + scheduled_completion + actual_completion vehicle { v_trimcode v_makecode @@ -2201,18 +2203,16 @@ exports.UPDATE_OLD_TRANSITION = `mutation UPDATE_OLD_TRANSITION($jobid: uuid!, $ exports.INSERT_NEW_TRANSITION = ( includeOldTransition -) => `mutation INSERT_NEW_TRANSITION($newTransition: transitions_insert_input!, ${ - includeOldTransition ? `$oldTransitionId: uuid!, $duration: numeric` : "" -}) { +) => `mutation INSERT_NEW_TRANSITION($newTransition: transitions_insert_input!, ${includeOldTransition ? `$oldTransitionId: uuid!, $duration: numeric` : "" + }) { insert_transitions_one(object: $newTransition) { id } - ${ - includeOldTransition - ? `update_transitions(where: {id: {_eq: $oldTransitionId}}, _set: {duration: $duration}) { + ${includeOldTransition + ? `update_transitions(where: {id: {_eq: $oldTransitionId}}, _set: {duration: $duration}) { affected_rows }` - : "" + : "" } }`;