Merged in release/2026-05-08 (pull request #3200)
feature/IO-3647-Reynolds-Integration-Phase-2-Optional - Add option to make 'Enhanced Early ROS' optional
This commit is contained in:
@@ -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 (
|
||||
<div>
|
||||
@@ -174,7 +181,9 @@ export function ShopInfoGeneral({ form }) {
|
||||
>
|
||||
<div aria-hidden style={INLINE_TITLE_SEPARATOR_STYLE} />
|
||||
<div style={INLINE_TITLE_SWITCH_GROUP_STYLE}>
|
||||
<div style={INLINE_TITLE_LABEL_STYLE}>{t("bodyshop.fields.scoreboard_setup.ignore_blocked_days")}</div>
|
||||
<div style={INLINE_TITLE_LABEL_STYLE}>
|
||||
{t("bodyshop.fields.scoreboard_setup.ignore_blocked_days")}
|
||||
</div>
|
||||
<Form.Item noStyle name={["scoreboard_target", "ignoreblockeddays"]} valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
@@ -311,7 +320,12 @@ export function ShopInfoGeneral({ form }) {
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} xl={8}>
|
||||
<Form.Item key="use_fippa" label={t("bodyshop.fields.use_fippa")} name={["use_fippa"]} valuePropName="checked">
|
||||
<Form.Item
|
||||
key="use_fippa"
|
||||
label={t("bodyshop.fields.use_fippa")}
|
||||
name={["use_fippa"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
@@ -478,7 +492,12 @@ export function ShopInfoGeneral({ form }) {
|
||||
<div style={INLINE_TITLE_LABEL_STYLE}>
|
||||
{t("bodyshop.fields.system_settings.job_costing.use_paint_scale_data")}
|
||||
</div>
|
||||
<Form.Item noStyle key="use_paint_scale_data" name={["use_paint_scale_data"]} valuePropName="checked">
|
||||
<Form.Item
|
||||
noStyle
|
||||
key="use_paint_scale_data"
|
||||
name={["use_paint_scale_data"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</div>
|
||||
@@ -558,7 +577,12 @@ export function ShopInfoGeneral({ form }) {
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
<LayoutFormRow header={t("bodyshop.labels.shop_enabled_features")} id="sharing" grow style={{ marginBottom: 0 }}>
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.shop_enabled_features")}
|
||||
id="sharing"
|
||||
grow
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<Form.Item
|
||||
label={t("general.actions.sharetoteams")}
|
||||
valuePropName="checked"
|
||||
@@ -566,6 +590,16 @@ export function ShopInfoGeneral({ form }) {
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
{isReynoldsMode && (
|
||||
<Form.Item
|
||||
initialValue
|
||||
label={t("bodyshop.fields.md_functionality_toggles.enhanced_early_ros")}
|
||||
name={["md_functionality_toggles", "enhanced_early_ros"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
)}
|
||||
</LayoutFormRow>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -457,6 +457,7 @@
|
||||
"md_email_cc": "",
|
||||
"md_from_emails": "",
|
||||
"md_functionality_toggles": {
|
||||
"enhanced_early_ros": "",
|
||||
"parts_queue_toggle": ""
|
||||
},
|
||||
"md_hour_split": {
|
||||
|
||||
@@ -457,6 +457,7 @@
|
||||
"md_email_cc": "",
|
||||
"md_from_emails": "",
|
||||
"md_functionality_toggles": {
|
||||
"enhanced_early_ros": "",
|
||||
"parts_queue_toggle": ""
|
||||
},
|
||||
"md_hour_split": {
|
||||
|
||||
@@ -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 || {};
|
||||
|
||||
105
server/rr/rr-job-export.test.js
Normal file
105
server/rr/rr-job-export.test.js
Normal file
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
18
server/rr/rr-utils.test.js
Normal file
18
server/rr/rr-utils.test.js
Normal file
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user