diff --git a/client/src/components/shop-info/shop-info.general.component.jsx b/client/src/components/shop-info/shop-info.general.component.jsx
index fa3a2c3af..cf97cd9ec 100644
--- a/client/src/components/shop-info/shop-info.general.component.jsx
+++ b/client/src/components/shop-info/shop-info.general.component.jsx
@@ -12,6 +12,8 @@ import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.c
import { buildSectionActionButton, renderListOrEmpty } from "../layout-form-row/config-list-actions.utils.jsx";
import InlineValidatedFormRow from "../layout-form-row/inline-validated-form-row.component.jsx";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
+import { selectBodyshop } from "../../redux/user/user.selectors";
+import { bodyshopHasDmsKey, DMS_MAP, getDmsMode } from "../../utils/dmsUtils.js";
import {
INLINE_TITLE_GROUP_STYLE,
INLINE_TITLE_HANDLE_STYLE,
@@ -25,16 +27,21 @@ import {
const timeZonesList = Intl.supportedValuesOf("timeZone");
-const mapStateToProps = createStructuredSelector({});
+const mapStateToProps = createStructuredSelector({
+ bodyshop: selectBodyshop
+});
const mapDispatchToProps = () => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoGeneral);
-export function ShopInfoGeneral({ form }) {
+export function ShopInfoGeneral({ form, bodyshop }) {
const { t } = useTranslation();
const insuranceCompanies = Form.useWatch(["md_ins_cos"], form) || [];
const duplicateInsuranceCompanyIndexes = getDuplicateIndexSetByNormalizedName(insuranceCompanies, "name");
+ const hasDMSKey = bodyshop ? bodyshopHasDmsKey(bodyshop) : false;
+ const dmsMode = bodyshop ? getDmsMode(bodyshop, "off") : "none";
+ const isReynoldsMode = hasDMSKey && dmsMode === DMS_MAP.reynolds;
return (
@@ -174,7 +181,9 @@ export function ShopInfoGeneral({ form }) {
>
-
{t("bodyshop.fields.scoreboard_setup.ignore_blocked_days")}
+
+ {t("bodyshop.fields.scoreboard_setup.ignore_blocked_days")}
+
@@ -311,7 +320,12 @@ export function ShopInfoGeneral({ form }) {
-
+
@@ -478,7 +492,12 @@ export function ShopInfoGeneral({ form }) {
{t("bodyshop.fields.system_settings.job_costing.use_paint_scale_data")}
-
+
@@ -558,7 +577,12 @@ export function ShopInfoGeneral({ form }) {
-
+
+ {isReynoldsMode && (
+
+
+
+ )}
>
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index 33f89f2fd..304423d4e 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -463,6 +463,7 @@
"md_email_cc": "Auto Email CC: $t(parts_orders.labels.{{template}})",
"md_from_emails": "Additional From Emails",
"md_functionality_toggles": {
+ "enhanced_early_ros": "Enable Enhance Early ROs",
"parts_queue_toggle": "Auto Add Imported/Supplemented Jobs to Parts Queue"
},
"md_hour_split": {
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index 58c7a74b5..9fdfa21b6 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -457,6 +457,7 @@
"md_email_cc": "",
"md_from_emails": "",
"md_functionality_toggles": {
+ "enhanced_early_ros": "",
"parts_queue_toggle": ""
},
"md_hour_split": {
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index e5cccbe55..2f07205c9 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -457,6 +457,7 @@
"md_email_cc": "",
"md_from_emails": "",
"md_functionality_toggles": {
+ "enhanced_early_ros": "",
"parts_queue_toggle": ""
},
"md_hour_split": {
diff --git a/server/rr/rr-job-export.js b/server/rr/rr-job-export.js
index 15e2f2a34..c4d4d8328 100644
--- a/server/rr/rr-job-export.js
+++ b/server/rr/rr-job-export.js
@@ -4,7 +4,7 @@ const CreateRRLogEvent = require("./rr-logger-event");
const { withRRRequestXml } = require("./rr-log-xml");
const { extractRrResponsibilityCenters } = require("./rr-responsibility-centers");
const CdkCalculateAllocations = require("./rr-calculate-allocations").default;
-const { resolveRROpCodeFromBodyshop } = require("./rr-utils");
+const { isEnhancedEarlyROEnabled, resolveRROpCodeFromBodyshop } = require("./rr-utils");
/**
* Derive RR status information from response object.
@@ -139,11 +139,14 @@ const createMinimalRRRepairOrder = async (args) => {
resolvedMileageIn: mileageIn
});
- const earlyRoOpCode = resolveRROpCode(bodyshop, txEnvelope);
- const earlyRoLabor = buildMinimalRolaborFromJob(job, {
- opCode: earlyRoOpCode,
- payType: "Cust"
- });
+ const enhancedEarlyROEnabled = isEnhancedEarlyROEnabled(bodyshop);
+ const earlyRoOpCode = enhancedEarlyROEnabled ? resolveRROpCode(bodyshop, txEnvelope) : null;
+ const earlyRoLabor = enhancedEarlyROEnabled
+ ? buildMinimalRolaborFromJob(job, {
+ opCode: earlyRoOpCode,
+ payType: "Cust"
+ })
+ : null;
const payload = {
customerNo: String(selected),
@@ -176,13 +179,19 @@ const createMinimalRRRepairOrder = async (args) => {
CreateRRLogEvent(socket, "INFO", "Creating minimal RR Repair Order (early creation)", {
payload,
+ enhancedEarlyROEnabled,
earlyRoOpCode,
hasRolabor: !!earlyRoLabor
});
const response = await client.createRepairOrder(payload, finalOpts);
- CreateRRLogEvent(socket, "INFO", "RR minimal Repair Order created", withRRRequestXml(response, { payload, response }));
+ CreateRRLogEvent(
+ socket,
+ "INFO",
+ "RR minimal Repair Order created",
+ withRRRequestXml(response, { payload, response })
+ );
const data = response?.data || null;
const statusBlocks = response?.statusBlocks || {};
diff --git a/server/rr/rr-job-export.test.js b/server/rr/rr-job-export.test.js
new file mode 100644
index 000000000..df8be1ff5
--- /dev/null
+++ b/server/rr/rr-job-export.test.js
@@ -0,0 +1,105 @@
+import { afterEach, describe, expect, it, vi } from "vitest";
+import { createRequire } from "node:module";
+
+const require = createRequire(import.meta.url);
+const mock = require("mock-require");
+
+const helpersModuleId = require.resolve("./rr-job-helpers");
+const lookupModuleId = require.resolve("./rr-lookup");
+const loggerEventModuleId = require.resolve("./rr-logger-event");
+const logXmlModuleId = require.resolve("./rr-log-xml");
+const responsibilityCentersModuleId = require.resolve("./rr-responsibility-centers");
+const allocationsModuleId = require.resolve("./rr-calculate-allocations");
+const jobExportModuleId = require.resolve("./rr-job-export");
+
+const makeBodyshop = (mdFunctionalityToggles) => ({
+ rr_configuration: {
+ defaults: {
+ prefix: "51",
+ base: "DOZ",
+ suffix: ""
+ }
+ },
+ ...(mdFunctionalityToggles ? { md_functionality_toggles: mdFunctionalityToggles } : {})
+});
+
+const makeJob = () => ({
+ id: "job-1",
+ ro_number: "RO-123",
+ v_vin: "1HGBH41JXMN109186",
+ joblines: [{ mod_lbr_ty: "LAB", mod_lb_hrs: 2, lbr_amt: 200 }]
+});
+
+const loadJobExport = ({
+ buildMinimalRolaborFromJob = vi.fn(() => ({ ops: [{ opCode: "51DOZ" }] })),
+ createRepairOrder = vi.fn(async () => ({ success: true, data: { dmsRoNo: "12345" } }))
+} = {}) => {
+ mock.stopAll();
+ mock(helpersModuleId, {
+ buildRRRepairOrderPayload: vi.fn(),
+ buildMinimalRolaborFromJob
+ });
+ mock(lookupModuleId, {
+ buildClientAndOpts: () => ({
+ client: { createRepairOrder },
+ opts: { envelope: { sender: {} } }
+ })
+ });
+ mock(loggerEventModuleId, vi.fn());
+ mock(logXmlModuleId, {
+ withRRRequestXml: (response, payload) => payload
+ });
+ mock(responsibilityCentersModuleId, {
+ extractRrResponsibilityCenters: vi.fn(() => [])
+ });
+ mock(allocationsModuleId, {
+ default: vi.fn()
+ });
+
+ delete require.cache[jobExportModuleId];
+ return {
+ ...require(jobExportModuleId),
+ buildMinimalRolaborFromJob,
+ createRepairOrder
+ };
+};
+
+afterEach(() => {
+ mock.stopAll();
+ delete require.cache[jobExportModuleId];
+});
+
+describe("server/rr/rr-job-export", () => {
+ it("sends early RO labor totals by default", async () => {
+ const { createMinimalRRRepairOrder, createRepairOrder, buildMinimalRolaborFromJob } = loadJobExport();
+
+ await createMinimalRRRepairOrder({
+ bodyshop: makeBodyshop(),
+ job: makeJob(),
+ advisorNo: "70754",
+ selectedCustomer: { custNo: "1134485" },
+ txEnvelope: {}
+ });
+
+ expect(buildMinimalRolaborFromJob).toHaveBeenCalledWith(makeJob(), {
+ opCode: "51DOZ",
+ payType: "Cust"
+ });
+ expect(createRepairOrder.mock.calls[0][0].rolabor).toEqual({ ops: [{ opCode: "51DOZ" }] });
+ });
+
+ it("omits early RO labor totals when the shop opts out", async () => {
+ const { createMinimalRRRepairOrder, createRepairOrder, buildMinimalRolaborFromJob } = loadJobExport();
+
+ await createMinimalRRRepairOrder({
+ bodyshop: makeBodyshop({ enhanced_early_ros: false }),
+ job: makeJob(),
+ advisorNo: "70754",
+ selectedCustomer: { custNo: "1134485" },
+ txEnvelope: {}
+ });
+
+ expect(buildMinimalRolaborFromJob).not.toHaveBeenCalled();
+ expect(createRepairOrder.mock.calls[0][0].rolabor).toBeUndefined();
+ });
+});
diff --git a/server/rr/rr-utils.js b/server/rr/rr-utils.js
index 0a8cb49ab..5629e7fda 100644
--- a/server/rr/rr-utils.js
+++ b/server/rr/rr-utils.js
@@ -205,10 +205,19 @@ const resolveRROpCodeFromBodyshop = (bodyshop) => {
return `${prefix}${base}${suffix}`;
};
+/**
+ * Enhanced Early RO labor totals are enabled by default for backwards compatibility.
+ * Shops can explicitly set md_functionality_toggles.enhanced_early_ros to false to opt out.
+ * @param bodyshop
+ * @returns {boolean}
+ */
+const isEnhancedEarlyROEnabled = (bodyshop) => bodyshop?.md_functionality_toggles?.enhanced_early_ros !== false;
+
module.exports = {
RRCacheEnums,
defaultRRTTL,
getTransactionType,
+ isEnhancedEarlyROEnabled,
ownersFromVinBlocks,
makeVehicleSearchPayloadFromJob,
normalizeCustomerCandidates,
diff --git a/server/rr/rr-utils.test.js b/server/rr/rr-utils.test.js
new file mode 100644
index 000000000..3f6810fe7
--- /dev/null
+++ b/server/rr/rr-utils.test.js
@@ -0,0 +1,18 @@
+import { describe, expect, it } from "vitest";
+import { createRequire } from "node:module";
+
+const require = createRequire(import.meta.url);
+const { isEnhancedEarlyROEnabled } = require("./rr-utils");
+
+describe("server/rr/rr-utils", () => {
+ it("keeps enhanced early ROs enabled when the shop setting is missing", () => {
+ expect(isEnhancedEarlyROEnabled()).toBe(true);
+ expect(isEnhancedEarlyROEnabled({})).toBe(true);
+ expect(isEnhancedEarlyROEnabled({ md_functionality_toggles: {} })).toBe(true);
+ });
+
+ it("only disables enhanced early ROs when the shop explicitly opts out", () => {
+ expect(isEnhancedEarlyROEnabled({ md_functionality_toggles: { enhanced_early_ros: false } })).toBe(false);
+ expect(isEnhancedEarlyROEnabled({ md_functionality_toggles: { enhanced_early_ros: true } })).toBe(true);
+ });
+});