diff --git a/client/src/components/job-detail-lines/job-lines-part-price-change.component.jsx b/client/src/components/job-detail-lines/job-lines-part-price-change.component.jsx
index 9cb7b3021..152ca7c56 100644
--- a/client/src/components/job-detail-lines/job-lines-part-price-change.component.jsx
+++ b/client/src/components/job-detail-lines/job-lines-part-price-change.component.jsx
@@ -12,13 +12,15 @@ import InstanceRenderManager from "../../utils/instanceRenderMgr";
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
+import { selectIsPartsEntry } from "../../redux/application/application.selectors.js";
const mapStateToProps = createStructuredSelector({
- technician: selectTechnician
+ technician: selectTechnician,
+ isPartsEntry: selectIsPartsEntry
});
const mapDispatchToProps = () => ({});
-export function JobLinesPartPriceChange({ job, line, refetch, technician }) {
+export function JobLinesPartPriceChange({ job, line, refetch, technician, isPartsEntry }) {
const [loading, setLoading] = useState(false);
const [updatePartPrice] = useMutation(UPDATE_LINE_PPC);
const notification = useNotification();
@@ -64,6 +66,7 @@ export function JobLinesPartPriceChange({ job, line, refetch, technician }) {
const popcontent =
!technician &&
+ !isPartsEntry &&
InstanceRenderManager({
imex: null,
rome: (
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 80759099e..3961fa625 100644
--- a/client/src/components/job-detail-lines/job-lines.component.jsx
+++ b/client/src/components/job-detail-lines/job-lines.component.jsx
@@ -481,48 +481,50 @@ export function JobLinesComponent({
{Enhanced_Payroll.treatment === "on" && (
)}
-
+ )}
0 ? false : true) || jobRO || technician}
@@ -578,7 +580,8 @@ export function JobLinesComponent({
{t("joblines.actions.new")}
)}
- {InstanceRenderManager({ rome: })}
+ {!isPartsEntry &&
+ InstanceRenderManager({ rome: })}
- {job.ins_co_nm}
+ {!isPartsEntry && {job.ins_co_nm}}
{job.clm_no}
{job.po_number}
-
- {job.clm_total}
- /
- {job.owner_owing}
-
-
+ {!isPartsEntry && (
+
+ {job.clm_total}
+ /
+ {job.owner_owing}
+
+ )}
{!isPartsEntry && (
<>
diff --git a/client/src/components/simplified-parts-jobs-list/simplified-parts-jobs-list.component.jsx b/client/src/components/simplified-parts-jobs-list/simplified-parts-jobs-list.component.jsx
index 6b77d5d48..fe4a69c09 100644
--- a/client/src/components/simplified-parts-jobs-list/simplified-parts-jobs-list.component.jsx
+++ b/client/src/components/simplified-parts-jobs-list/simplified-parts-jobs-list.component.jsx
@@ -9,7 +9,6 @@ import { connect } from "react-redux";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
-import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { pageLimit } from "../../utils/config";
import { alphaSort, statusSort } from "../../utils/sorters";
import useLocalStorage from "../../utils/useLocalStorage";
@@ -144,26 +143,6 @@ export function SimplifiedPartsJobsListComponent({
sortOrder: sortcolumn === "clm_no" && sortorder,
render: (text, record) => `${record.clm_no || ""}${record.po_number ? ` (PO: ${record.po_number})` : ""}`
},
- {
- title: t("jobs.fields.ins_co_nm"),
- dataIndex: "ins_co_nm",
- key: "ins_co_nm",
- ellipsis: true
- },
- {
- title: t("jobs.fields.clm_total"),
- dataIndex: "clm_total",
- key: "clm_total",
- sorter: search?.search ? (a, b) => a.clm_total - b.clm_total : true,
- sortOrder: sortcolumn === "clm_total" && sortorder,
- render: (text, record) => {
- return record.clm_total ? (
- {record.clm_total}
- ) : (
- t("general.labels.unknown")
- );
- }
- },
{
title: t("jobs.fields.partsstatus"),
dataIndex: "partsstatus",
diff --git a/server/integrations/partsManagement/endpoints/lib/opCodes.json b/server/integrations/partsManagement/endpoints/lib/opCodes.json
new file mode 100644
index 000000000..e12857d7e
--- /dev/null
+++ b/server/integrations/partsManagement/endpoints/lib/opCodes.json
@@ -0,0 +1,197 @@
+{
+ "OP0": {
+ "desc": "REMOVE / REPLACE PARTIAL",
+ "opcode": "OP11",
+ "partcode": "PAA"
+ },
+ "OP1": {
+ "desc": "REFINISH / REPAIR",
+ "opcode": "OP1",
+ "partcode": "PAE"
+ },
+ "OP2": {
+ "desc": "REMOVE / INSTALL",
+ "opcode": "OP2",
+ "partcode": "PAE"
+ },
+ "OP3": {
+ "desc": "ADDITIONAL LABOR",
+ "opcode": "OP9",
+ "partcode": "PAE"
+ },
+ "OP4": {
+ "desc": "ALIGNMENT",
+ "opcode": "OP4",
+ "partcode": "PAS"
+ },
+ "OP5": {
+ "desc": "OVERHAUL",
+ "opcode": "OP5",
+ "partcode": "PAE"
+ },
+ "OP6": {
+ "desc": "REFINISH",
+ "opcode": "OP6",
+ "partcode": "PAE"
+ },
+ "OP7": {
+ "desc": "INSPECT",
+ "opcode": "OP7",
+ "partcode": "PAE"
+ },
+ "OP8": {
+ "desc": "CHECK / ADJUST",
+ "opcode": "OP8",
+ "partcode": "PAE"
+ },
+ "OP9": {
+ "desc": "REPAIR",
+ "opcode": "OP9",
+ "partcode": "PAE"
+ },
+ "OP10": {
+ "desc": "REPAIR , PARTIAL",
+ "opcode": "OP9",
+ "partcode": "PAE"
+ },
+ "OP11": {
+ "desc": "REMOVE / REPLACE",
+ "opcode": "OP11",
+ "partcode": "PAN"
+ },
+ "OP12": {
+ "desc": "REMOVE / REPLACE PARTIAL",
+ "opcode": "OP11",
+ "partcode": "PAN"
+ },
+ "OP13": {
+ "desc": "ADDITIONAL COSTS",
+ "opcode": "OP13",
+ "partcode": "PAE"
+ },
+ "OP14": {
+ "desc": "ADDITIONAL OPERATIONS",
+ "opcode": "OP14",
+ "partcode": "PAE"
+ },
+ "OP15": {
+ "desc": "BLEND",
+ "opcode": "OP15",
+ "partcode": "PAE"
+ },
+ "OP16": {
+ "desc": "SUBLET",
+ "opcode": "OP16",
+ "partcode": "PAS"
+ },
+ "OP17": {
+ "desc": "POLICY LIMIT ADJUSTMENT",
+ "opcode": "OP9",
+ "partcode": "PAE"
+ },
+ "OP18": {
+ "desc": "APPEAR ALLOWANCE",
+ "opcode": "OP7",
+ "partcode": "PAE"
+ },
+ "OP20": {
+ "desc": "REMOVE AND REINSTALL",
+ "opcode": "OP20",
+ "partcode": "PAE"
+ },
+ "OP24": {
+ "desc": "CHIPGUARD",
+ "opcode": "OP6",
+ "partcode": "PAE"
+ },
+ "OP25": {
+ "desc": "TWO TONE",
+ "opcode": "OP6",
+ "partcode": "PAE"
+ },
+ "OP26": {
+ "desc": "PAINTLESS DENT REPAIR",
+ "opcode": "OP16",
+ "partcode": "PAE"
+ },
+ "OP100": {
+ "desc": "REPLACE PRE-PRICED",
+ "opcode": "OP11",
+ "partcode": "PAA"
+ },
+ "OP101": {
+ "desc": "REMOVE/REPLACE RECYCLED PART",
+ "opcode": "OP11",
+ "partcode": "PAL"
+ },
+ "OP103": {
+ "desc": "REMOVE / REPLACE PARTIAL",
+ "opcode": "OP11",
+ "partcode": "PAA"
+ },
+ "OP104": {
+ "desc": "REMOVE / REPLACE PARTIAL LABOUR",
+ "opcode": "OP11",
+ "partcode": "PAA"
+ },
+ "OP105": {
+ "desc": "!!ADJUST MANUALLY!!",
+ "opcode": "OP99",
+ "partcode": "PAE"
+ },
+ "OP106": {
+ "desc": "REPAIR , PARTIAL",
+ "opcode": "OP9",
+ "partcode": "PAE"
+ },
+ "OP107": {
+ "desc": "CHIPGUARD",
+ "opcode": "OP6",
+ "partcode": "PAE"
+ },
+ "OP108": {
+ "desc": "MULTI TONE",
+ "opcode": "OP6",
+ "partcode": "PAE"
+ },
+ "OP109": {
+ "desc": "REPLACE PRE-PRICED",
+ "opcode": "OP11",
+ "partcode": "PAA"
+ },
+ "OP110": {
+ "desc": "REFINISH / REPAIR",
+ "opcode": "OP1",
+ "partcode": "PAE"
+ },
+ "OP111": {
+ "desc": "REMOVE / REPLACE",
+ "opcode": "OP11",
+ "partcode": "PAN"
+ },
+ "OP112": {
+ "desc": "REMOVE / REPLACE",
+ "opcode": "OP11",
+ "partcode": "PAA"
+ },
+ "OP113": {
+ "desc": "REPLACE PRE-PRICED",
+ "opcode": "OP11",
+ "partcode": "PAA"
+ },
+ "OP114": {
+ "desc": "REPLACE PRE-PRICED",
+ "opcode": "OP11",
+ "partcode": "PAA"
+ },
+ "OP120": {
+ "desc": "REPAIR , PARTIAL",
+ "opcode": "OP9",
+ "partcode": "PAE"
+ },
+ "OP260": {
+ "desc": "SUBLET",
+ "opcode": "OP16",
+ "partcode": "PAE"
+ }
+}
diff --git a/server/integrations/partsManagement/endpoints/partsManagementDeprovisioning.js b/server/integrations/partsManagement/endpoints/partsManagementDeprovisioning.js
index 6261b3a27..449023a86 100644
--- a/server/integrations/partsManagement/endpoints/partsManagementDeprovisioning.js
+++ b/server/integrations/partsManagement/endpoints/partsManagementDeprovisioning.js
@@ -129,15 +129,17 @@ const partsManagementDeprovisioning = async (req, res) => {
const deletedUsers = [];
for (const user of associatedUsers) {
const countResp = await client.request(GET_USER_ASSOCIATIONS_COUNT, { userEmail: user.email });
- const assocCount = countResp.associations_aggregate.aggregate.count;
- if (assocCount === 0) {
- await client.request(DELETE_USER, { email: user.email });
- await deleteFirebaseUser(user.authId);
- deletedUsers.push(user.email);
- }
+ // Determine which users now have zero associations and should be deleted (defer deletion until end)
+ const emailsToAuthId = associatedUsers.reduce((acc, u) => {
+ acc[u.email] = u.authId;
+ return acc;
+ }, {});
+ const emailsToDelete = [];
+ await client.request(DELETE_USER, { email: user.email });
+ await deleteFirebaseUser(user.authId);
+ deletedUsers.push(user.email);
}
-
- // Get all job ids for this shop, then delete joblines and jobs (joblines first)
+ emailsToDelete.push(user.email);
const jobIds = await getJobIdsForShop(body.shopId);
const joblinesDeleted = await deleteJoblinesForJobs(jobIds);
const jobsDeleted = await deleteJobsByIds(jobIds);
@@ -174,6 +176,16 @@ const partsManagementDeprovisioning = async (req, res) => {
deletedJobsCount: jobsDeleted,
deletedAuditTrailCount: auditDeleted
});
+ // Now delete users that have no remaining associations and their Firebase accounts
+ const deletedUsers = [];
+ for (const email of emailsToDelete) {
+ await client.request(DELETE_USER, { email });
+ const authId = emailsToAuthId[email];
+ if (authId) {
+ await deleteFirebaseUser(authId);
+ }
+ deletedUsers.push(email);
+ }
} catch (err) {
logger.log("admin-delete-shop-error", "error", null, null, {
message: err.message,
diff --git a/server/integrations/partsManagement/partsManagement.queries.js b/server/integrations/partsManagement/partsManagement.queries.js
index afcc9a43c..129ca4419 100644
--- a/server/integrations/partsManagement/partsManagement.queries.js
+++ b/server/integrations/partsManagement/partsManagement.queries.js
@@ -216,6 +216,36 @@ const GET_JOBLINES_NOTES_BY_JOBID_UNQSEQ = `
}
`;
+// Clear task links to parts orders for all jobs in a shop to avoid FK violations when deleting parts orders
+const CLEAR_TASKS_PARTSORDER_LINKS_BY_JOBIDS = `
+ mutation ClearTasksPartsOrderLinks($jobIds: [uuid!]!) {
+ update_tasks(
+ where: { parts_order: { jobid: { _in: $jobIds } } },
+ _set: { partsorderid: null }
+ ) {
+ affected_rows
+ }
+ }
+`;
+
+// Delete parts order lines where the parent order belongs to any of the provided job IDs
+const DELETE_PARTS_ORDER_LINES_BY_JOB_IDS = `
+ mutation DeletePartsOrderLinesByJobIds($jobIds: [uuid!]!) {
+ delete_parts_order_lines(where: { parts_order: { jobid: { _in: $jobIds } } }) {
+ affected_rows
+ }
+ }
+`;
+
+// Delete parts orders for the given job IDs
+const DELETE_PARTS_ORDERS_BY_JOB_IDS = `
+ mutation DeletePartsOrdersByJobIds($jobIds: [uuid!]!) {
+ delete_parts_orders(where: { jobid: { _in: $jobIds } }) {
+ affected_rows
+ }
+ }
+`;
+
module.exports = {
GET_BODYSHOP_STATUS,
GET_VEHICLE_BY_SHOP_VIN,
@@ -241,5 +271,9 @@ module.exports = {
DELETE_JOBS_BY_IDS,
DELETE_AUDIT_TRAIL_BY_SHOP,
GET_JOBLINES_NOTES_BY_JOBID_UNQSEQ,
- GET_JOB_BY_ID
+ GET_JOB_BY_ID,
+ // newly added exports
+ CLEAR_TASKS_PARTSORDER_LINKS_BY_JOBIDS,
+ DELETE_PARTS_ORDER_LINES_BY_JOB_IDS,
+ DELETE_PARTS_ORDERS_BY_JOB_IDS
};