From f6d6b548be0e5f5e8e457ea2f78f042b401a6976 Mon Sep 17 00:00:00 2001 From: Dave Date: Tue, 19 Aug 2025 11:17:29 -0400 Subject: [PATCH] feature/IO-3255-simplified-parts-management Deprovision route can now remove jobs, joblines, and audit trail --- .../partsManagementDeprovisioning.js | 138 +++++++++--------- .../partsManagement.queries.js | 100 ++++++++++++- 2 files changed, 167 insertions(+), 71 deletions(-) diff --git a/server/integrations/partsManagement/endpoints/partsManagementDeprovisioning.js b/server/integrations/partsManagement/endpoints/partsManagementDeprovisioning.js index 084afef7d..122a35d17 100644 --- a/server/integrations/partsManagement/endpoints/partsManagementDeprovisioning.js +++ b/server/integrations/partsManagement/endpoints/partsManagementDeprovisioning.js @@ -1,71 +1,20 @@ const admin = require("firebase-admin"); const client = require("../../../graphql-client/graphql-client").client; -const { DELETE_SHOP } = require("../partsManagement.queries"); - -// Define corrected DELETE_VENDORS_BY_SHOP locally -const DELETE_VENDORS_BY_SHOP = ` - mutation DELETE_VENDORS_BY_SHOP($shopId: uuid!) { - delete_vendors(where: {bodyshopid: {_eq: $shopId}}) { - affected_rows - } - } -`; - -// New queries for deprovisioning -const GET_BODYSHOP = ` - query GetBodyshop($id: uuid!) { - bodyshops_by_pk(id: $id) { - external_shop_id - shopname - } - } -`; - -const GET_ASSOCIATED_USERS = ` - query GetAssociatedUsers($shopId: uuid!) { - associations(where: {shopid: {_eq: $shopId}}) { - user { - authid - email - } - } - } -`; - -const DELETE_ASSOCIATIONS_BY_SHOP = ` - mutation DeleteAssociationsByShop($shopId: uuid!) { - delete_associations(where: {shopid: {_eq: $shopId}}) { - affected_rows - } - } -`; - -const GET_USER_ASSOCIATIONS_COUNT = ` - query GetUserAssociationsCount($userEmail: String!) { - associations_aggregate(where: {useremail: {_eq: $userEmail}}) { - aggregate { - count - } - } - } -`; - -const DELETE_USER = ` - mutation DeleteUser($email: String!) { - delete_users(where: {email: {_eq: $email}}) { - affected_rows - } - } -`; - -const GET_VENDORS = ` - query GetVendors($shopId: uuid!) { - vendors(where: {bodyshopid: {_eq: $shopId}}) { - name - } - } -`; +const { + DELETE_SHOP, + DELETE_VENDORS_BY_SHOP, + GET_BODYSHOP, + GET_ASSOCIATED_USERS, + DELETE_ASSOCIATIONS_BY_SHOP, + GET_USER_ASSOCIATIONS_COUNT, + DELETE_USER, + GET_VENDORS, + GET_JOBS_BY_SHOP, + DELETE_JOBLINES_BY_JOB_IDS, + DELETE_JOBS_BY_IDS, + DELETE_AUDIT_TRAIL_BY_SHOP +} = require("../partsManagement.queries"); /** * Deletes a Firebase user by UID. @@ -94,6 +43,38 @@ const deleteBodyshop = async (shopId) => { await client.request(DELETE_SHOP, { id: shopId }); }; +/** + * Fetch job ids for a given shop + * @param shopId + * @returns {Promise} + */ +const getJobIdsForShop = async (shopId) => { + const resp = await client.request(GET_JOBS_BY_SHOP, { shopId }); + return resp.jobs.map((j) => j.id); +}; + +/** + * Delete joblines for the given job ids + * @param jobIds {string[]} + * @returns {Promise} affected rows + */ +const deleteJoblinesForJobs = async (jobIds) => { + if (!jobIds.length) return 0; + const resp = await client.request(DELETE_JOBLINES_BY_JOB_IDS, { jobIds }); + return resp.delete_joblines.affected_rows; +}; + +/** + * Delete jobs for the given job ids + * @param jobIds {string[]} + * @returns {Promise} affected rows + */ +const deleteJobsByIds = async (jobIds) => { + if (!jobIds.length) return 0; + const resp = await client.request(DELETE_JOBS_BY_IDS, { jobIds }); + return resp.delete_jobs.affected_rows; +}; + /** * Handles deprovisioning a shop for parts management. * @param req @@ -156,6 +137,15 @@ const partsManagementDeprovisioning = async (req, res) => { } } + // Get all job ids for this shop, then delete joblines and jobs (joblines first) + const jobIds = await getJobIdsForShop(p.shopId); + const joblinesDeleted = await deleteJoblinesForJobs(jobIds); + const jobsDeleted = await deleteJobsByIds(jobIds); + + // Delete any audit trail entries tied to this bodyshop to avoid FK violations + const auditResp = await client.request(DELETE_AUDIT_TRAIL_BY_SHOP, { shopId: p.shopId }); + const auditDeleted = auditResp.delete_audit_trail.affected_rows; + // Delete vendors await deleteVendorsByShop(p.shopId); @@ -163,16 +153,26 @@ const partsManagementDeprovisioning = async (req, res) => { await deleteBodyshop(p.shopId); // Summary log - console.log( - `Deleted bodyshop ${p.shopId} (${shop.shopname}), ${associationsDeleted} associations, ${deletedUsers.length} users (${deletedUsers.join(", ") || "none"}), ${deletedVendors.length} vendors (${deletedVendors.join(", ") || "none"}).` - ); + logger.log("admin-delete-shop-summary", "info", null, null, { + shopId: p.shopId, + shopname: shop.shopname, + associationsDeleted, + deletedUsers, + deletedVendors, + joblinesDeleted, + jobsDeleted, + auditDeleted + }); return res.status(200).json({ message: `Bodyshop ${p.shopId} and associated resources deleted successfully.`, deletedShop: { id: p.shopId, name: shop.shopname }, deletedAssociationsCount: associationsDeleted, deletedUsers: deletedUsers, - deletedVendors: deletedVendors + deletedVendors: deletedVendors, + deletedJoblinesCount: joblinesDeleted, + deletedJobsCount: jobsDeleted, + deletedAuditTrailCount: auditDeleted }); } catch (err) { logger.log("admin-delete-shop-error", "error", null, null, { diff --git a/server/integrations/partsManagement/partsManagement.queries.js b/server/integrations/partsManagement/partsManagement.queries.js index c56e82493..4e8fb2107 100644 --- a/server/integrations/partsManagement/partsManagement.queries.js +++ b/server/integrations/partsManagement/partsManagement.queries.js @@ -126,7 +126,7 @@ const CREATE_SHOP = ` const DELETE_VENDORS_BY_SHOP = ` mutation DELETE_VENDORS($shopId: uuid!) { - delete_vendors(where: { shopid: { _eq: $shopId } }) { + delete_vendors(where: { bodyshopid: { _eq: $shopId } }) { affected_rows } } @@ -147,6 +147,92 @@ const CREATE_USER = ` } `; +const GET_BODYSHOP = ` + query GetBodyshop($id: uuid!) { + bodyshops_by_pk(id: $id) { + external_shop_id + shopname + } + } +`; + +const GET_ASSOCIATED_USERS = ` + query GetAssociatedUsers($shopId: uuid!) { + associations(where: {shopid: {_eq: $shopId}}) { + user { + authid + email + } + } + } +`; + +const DELETE_ASSOCIATIONS_BY_SHOP = ` + mutation DeleteAssociationsByShop($shopId: uuid!) { + delete_associations(where: {shopid: {_eq: $shopId}}) { + affected_rows + } + } +`; + +const GET_USER_ASSOCIATIONS_COUNT = ` + query GetUserAssociationsCount($userEmail: String!) { + associations_aggregate(where: {useremail: {_eq: $userEmail}}) { + aggregate { + count + } + } + } +`; + +const DELETE_USER = ` + mutation DeleteUser($email: String!) { + delete_users(where: {email: {_eq: $email}}) { + affected_rows + } + } +`; + +const GET_VENDORS = ` + query GetVendors($shopId: uuid!) { + vendors(where: {bodyshopid: {_eq: $shopId}}) { + name + } + } +`; + +const GET_JOBS_BY_SHOP = ` + query GetJobsByShop($shopId: uuid!) { + jobs(where: {shopid: {_eq: $shopId}}) { + id + } + } +`; + +const DELETE_JOBLINES_BY_JOB_IDS = ` + mutation DeleteJoblinesByJobIds($jobIds: [uuid!]!) { + delete_joblines(where: {jobid: {_in: $jobIds}}) { + affected_rows + } + } +`; + +const DELETE_JOBS_BY_IDS = ` + mutation DeleteJobsByIds($jobIds: [uuid!]!) { + delete_jobs(where: {id: {_in: $jobIds}}) { + affected_rows + } + } +`; + +const DELETE_AUDIT_TRAIL_BY_SHOP = ` + mutation DeleteAuditTrailByShop($shopId: uuid!) { + delete_audit_trail(where: {bodyshopid: {_eq: $shopId}}) { + affected_rows + } + } +`; + module.exports = { GET_BODYSHOP_STATUS, GET_VEHICLE_BY_SHOP_VIN, @@ -162,5 +248,15 @@ module.exports = { CREATE_SHOP, DELETE_VENDORS_BY_SHOP, DELETE_SHOP, - CREATE_USER + CREATE_USER, + GET_BODYSHOP, + GET_ASSOCIATED_USERS, + DELETE_ASSOCIATIONS_BY_SHOP, + GET_USER_ASSOCIATIONS_COUNT, + DELETE_USER, + GET_VENDORS, + GET_JOBS_BY_SHOP, + DELETE_JOBLINES_BY_JOB_IDS, + DELETE_JOBS_BY_IDS, + DELETE_AUDIT_TRAIL_BY_SHOP };