Compare commits

...

33 Commits

Author SHA1 Message Date
Patrick Fic
ae05692c46 IO-3531 remove loading on parts order page. 2026-02-02 13:45:25 -08:00
Patrick Fic
e5b7fcb919 IO-3531 Change global apollo config setting to prevent rerenders. 2026-02-02 12:02:11 -08:00
Allan Carr
849d967b56 Merged in hotfix/2026-01-30 (pull request #2931)
IO-3529 Fix for Parts Return
2026-02-01 01:49:08 +00:00
Allan Carr
519d7e8d87 Merged in feature/IO-3529-DMS-Make-Code (pull request #2932)
IO-3529 CM Recieved Fix
2026-02-01 01:41:46 +00:00
Allan Carr
b08435607e IO-3529 CM Recieved Fix
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-01-31 17:43:16 -08:00
Allan Carr
ea9e4ffcad IO-3529 Fix for Parts Return
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-01-31 16:59:17 -08:00
Allan Carr
6c814c7dc6 Merged in feature/IO-3529-DMS-Make-Code (pull request #2929)
IO-3529 Fix for Parts Return
2026-02-01 00:57:43 +00:00
Allan Carr
cc9e536059 Merged in hotfix/2026-01-30 (pull request #2928)
IO-3529 Job Lines on Closing add IDs
2026-01-31 19:37:46 +00:00
Allan Carr
dadc9892d0 IO-3529 Job Lines on Closing add IDs
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-01-31 11:32:05 -08:00
Allan Carr
b05e20ce0d Merged in feature/IO-3529-DMS-Make-Code (pull request #2926)
IO-3529 Job Lines on Closing add IDs
2026-01-31 19:30:29 +00:00
Allan Carr
eb36b12cb0 Merged in hotfix/2026-01-30 (pull request #2925)
IO-3529 DMS Make Code
2026-01-31 06:46:32 +00:00
Allan Carr
bf5a099fa6 IO-3529 DMS Make Code
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-01-30 22:39:56 -08:00
Allan Carr
ff3d24c623 Merged in feature/IO-3529-DMS-Make-Code (pull request #2923)
IO-3529 DMS Make Code
2026-01-31 06:38:26 +00:00
Dave Richer
27b955a701 Merged in release/2026-01-23 (pull request #2918)
Release/2026 01 23 into master-AIO - IO-3497, IO-3499, IO-3503, IO-3509, IO-3512, IO-3514, IO-3523
2026-01-31 03:23:30 +00:00
Allan Carr
1896c4db59 Merged in feature/IO-3503-Job-Costing-Fixes (pull request #2921)
IO-3503 Job Costing Corrections

Approved-by: Dave Richer
2026-01-31 01:10:20 +00:00
Allan Carr
78770ed54e IO-3503 Job Costing Corrections
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-01-30 16:45:25 -08:00
Dave Richer
9e2ae2cc10 Merged in feature/IO-3499-React-19 (pull request #2919)
feature/IO-3499-React-19 -Checkpoint
2026-01-30 22:32:54 +00:00
Dave Richer
f0dfa2717f Merged in feature/IO-3499-React-19 (pull request #2916)
feature/IO-3499-React-19 -Checkpoint
2026-01-30 17:33:51 +00:00
Allan Carr
3d9ad799f3 Merged in hotfix/2026-01-29 (pull request #2915)
IO-3522 Replace Email with Phone1
2026-01-29 21:31:13 +00:00
Allan Carr
6e17ef10bb Merged in feature/IO-3522-Fortellis-Bug-Fix (pull request #2914)
IO-3522 Replace Email with Phone1
2026-01-29 21:28:48 +00:00
Allan Carr
fdc06e79a6 IO-3522 Replace Email with Phone1
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-01-29 13:29:01 -08:00
Allan Carr
66924367fc Merged in feature/IO-3522-Fortellis-Bug-Fix (pull request #2913)
IO-3522 Replace Email with Phone1
2026-01-29 21:28:11 +00:00
Allan Carr
f76165552e Merged in hotfix/2026-01-29 (pull request #2912)
IO-3522 Fortellis Null Coalesce for Owner data
2026-01-29 21:06:39 +00:00
Allan Carr
80fbb847d8 Merged in feature/IO-3522-Fortellis-Bug-Fix (pull request #2911)
IO-3522 Fortellis Null Coalesce for Owner data
2026-01-29 21:02:40 +00:00
Allan Carr
ca1703e724 Merged in feature/IO-3522-Fortellis-Bug-Fix (pull request #2910)
Feature/IO-3522 Fortellis Bug Fix

Approved-by: Patrick Fic
2026-01-29 21:00:45 +00:00
Allan Carr
163819809c IO-3522 Fortellis Null Coalesce for Owner data
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-01-29 12:57:14 -08:00
Dave Richer
42fa85e145 Merged in feature/IO-3499-React-19 (pull request #2908)
feature/IO-3499-React-19 -Checkpoint
2026-01-29 17:37:01 +00:00
Dave Richer
0c9f7df9ac Merged in feature/IO-3499-React-19 (pull request #2907)
feature/IO-3499-React-19 -Checkpoint
2026-01-29 17:33:26 +00:00
Dave Richer
78d816fa8b Merged in feature/IO-3499-React-19 (pull request #2905)
feature/IO-3499-React-19 -Checkpoint
2026-01-28 19:02:42 +00:00
Dave Richer
4a1b1fe905 Merged in feature/IO-3499-React-19 (pull request #2902)
Feature/IO-3499 React 19
2026-01-28 02:31:43 +00:00
Dave Richer
a9fb77189e Merged in feature/IO-3499-React-19 (pull request #2900)
Feature/IO-3499 React 19
2026-01-28 00:27:51 +00:00
Patrick Fic
70b6aa63ed Merged in feature/IO-3517-fortellis-hotfix (pull request #2899)
IO-3517 Resolve emit on fortellis completion.
2026-01-27 19:32:02 +00:00
Patrick Fic
844a879f1c IO-3517 Resolve emit on fortellis completion. 2026-01-27 11:31:10 -08:00
18 changed files with 83 additions and 46 deletions

View File

@@ -48,7 +48,7 @@ export function BillDetailEditReturn({ setPartsOrderContext, data, disabled }) {
// db_price: i.actual_price,
act_price: i.actual_price,
cost: i.actual_cost,
quantity: i.quantity,
part_qty: i.quantity,
joblineid: i.joblineid,
oem_partno: i.jobline && i.jobline.oem_partno,
part_type: i.jobline && i.jobline.part_type
@@ -104,6 +104,10 @@ export function BillDetailEditReturn({ setPartsOrderContext, data, disabled }) {
{fields.map((field, index) => (
<tr key={field.key}>
<td>
{/* Hidden field to preserve the id */}
<Form.Item name={[field.name, "id"]} hidden>
<input type="hidden" />
</Form.Item>
<Form.Item
// label={t("joblines.fields.selected")}
key={`${index}selected`}

View File

@@ -57,7 +57,6 @@ const CardPaymentModalComponent = ({
QUERY_RO_AND_OWNER_BY_JOB_PKS,
{
fetchPolicy: "network-only",
notifyOnNetworkStatusChange: true
}
);

View File

@@ -47,7 +47,6 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh
const [getConversations, { loading, data, refetch, called }] = useLazyQuery(CONVERSATION_LIST_QUERY, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
notifyOnNetworkStatusChange: true,
...(pollInterval > 0 ? { pollInterval } : {})
});

View File

@@ -49,12 +49,15 @@ export function DmsCdkVehicles({ form, job }) {
open={open}
onCancel={() => setOpen(false)}
onOk={() => {
form.setFieldsValue({
dms_make: selectedModel.makecode,
dms_model: selectedModel.modelcode
});
setOpen(false);
if (selectedModel) {
form.setFieldsValue({
dms_make: selectedModel.makecode,
dms_model: selectedModel.modelcode
});
setOpen(false);
}
}}
okButtonProps={{ disabled: !selectedModel }}
>
{error && <AlertComponent title={error.message} type="error" />}
<Table

View File

@@ -42,6 +42,10 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
<tbody>
{fields.map((field, index) => (
<tr key={field.key}>
{/* Hidden field to preserve jobline ID */}
<Form.Item hidden name={[field.name, "id"]}>
<input />
</Form.Item>
<td>
<Form.Item
// label={t("joblines.fields.line_desc")}

View File

@@ -157,7 +157,6 @@ export function JobsDetailHeaderActions({
variables: watcherVars,
skip: !jobId,
fetchPolicy: "cache-first",
notifyOnNetworkStatusChange: true
});
const jobWatchersCount = jobWatchersData?.job_watchers?.length ?? job?.job_watchers?.length ?? 0;

View File

@@ -56,7 +56,6 @@ const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount,
where: whereClause
},
fetchPolicy: "cache-and-network",
notifyOnNetworkStatusChange: true,
errorPolicy: "all",
pollInterval: isConnected ? 0 : day.duration(NOTIFICATION_POLL_INTERVAL_SECONDS, "seconds").asMilliseconds(),
skip: skipQuery

View File

@@ -13,6 +13,13 @@ import PartsOrderModalPriceChange from "./parts-order-modal-price-change.compone
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
import { selectIsPartsEntry } from "../../redux/application/application.selectors.js";
const PriceInputWrapper = ({ value, onChange, form, field }) => (
<Space.Compact style={{ width: "100%" }}>
<PartsOrderModalPriceChange form={form} field={field} />
<CurrencyInput style={{ flex: 1 }} value={value} onChange={onChange} />
</Space.Compact>
);
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
isPartsEntry: selectIsPartsEntry
@@ -199,10 +206,7 @@ export function PartsOrderModalComponent({
key={`${index}act_price`}
name={[field.name, "act_price"]}
>
<Space.Compact style={{ width: "100%" }}>
<PartsOrderModalPriceChange form={form} field={field} />
<CurrencyInput style={{ flex: 1 }} />
</Space.Compact>
<PriceInputWrapper form={form} field={field} />
</Form.Item>
{isReturn && (
<Form.Item

View File

@@ -17,7 +17,6 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import PartsOrderModalComponent from "./parts-order-modal.component";
import axios from "axios";
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
@@ -66,7 +65,7 @@ export function PartsOrderModalContainer({
const sendTypeState = useState("e");
const sendType = sendTypeState[0];
const { loading, error, data } = useQuery(QUERY_ALL_VENDORS_FOR_ORDER, {
const { error, data } = useQuery(QUERY_ALL_VENDORS_FOR_ORDER, {
skip: !open,
variables: { jobId: jobId },
fetchPolicy: "network-only",
@@ -89,7 +88,8 @@ export function PartsOrderModalContainer({
return {
...p,
job_line_id: jobLineId
job_line_id: jobLineId,
...(isReturn && { cm_received: false })
};
});
@@ -371,6 +371,7 @@ export function PartsOrderModalContainer({
}
}, [open, linesToOrder, form]);
//This used to have a loading component spinner for the vendor data. With Apollo 4, the NetworkState isn't emitting correctly, so loading just gets set to true the second time, and no longer works as expected.
return (
<Modal
open={open}
@@ -389,18 +390,14 @@ export function PartsOrderModalContainer({
>
{error ? <AlertComponent title={error.message} type="error" /> : null}
<Form form={form} layout="vertical" autoComplete="no" onFinish={handleFinish} initialValues={initialValues}>
{loading ? (
<LoadingSpinner />
) : (
<PartsOrderModalComponent
form={form}
vendorList={data?.vendors || []}
sendTypeState={sendTypeState}
isReturn={isReturn}
preferredMake={data && data.jobs[0] && data.jobs[0].v_make_desc}
job={job}
/>
)}
<PartsOrderModalComponent
form={form}
vendorList={data?.vendors || []}
sendTypeState={sendTypeState}
isReturn={isReturn}
preferredMake={data && data.jobs[0] && data.jobs[0].v_make_desc}
job={job}
/>
</Form>
</Modal>
);

View File

@@ -356,7 +356,10 @@ export const MUTATION_BACKORDER_PART_LINE = gql`
export const QUERY_UNRECEIVED_LINES = gql`
query QUERY_UNRECEIVED_LINES($jobId: uuid!, $vendorId: uuid!) {
parts_order_lines(
where: { parts_order: { jobid: { _eq: $jobId }, vendorid: { _eq: $vendorId } }, cm_received: { _neq: true } }
where: {
parts_order: { jobid: { _eq: $jobId }, vendorid: { _eq: $vendorId }, return: { _eq: true } }
_or: [{ cm_received: { _neq: true } }, { cm_received: { _is_null: true } }]
}
) {
cm_received
id

View File

@@ -82,10 +82,23 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, set
const handleFinish = async ({ removefromproduction, ...values }) => {
setLoading(true);
// Validate that all joblines have valid IDs
const joblinesWithIds = values.joblines.filter(jl => jl && jl.id);
if (joblinesWithIds.length !== values.joblines.length) {
notification.error({
title: t("jobs.errors.invalidjoblines"),
message: t("jobs.errors.missingjoblineids")
});
setLoading(false);
return;
}
const result = await client.mutate({
mutation: generateJobLinesUpdatesForInvoicing(values.joblines)
});
if (result.errors) {
setLoading(false);
return; // Abandon the rest of the close.
}

View File

@@ -1676,7 +1676,9 @@
"deleted": "Error deleting Job. {{error}}",
"exporting": "Error exporting Job. {{error}}",
"exporting-partner": "Unable to connect to partner application. Please ensure it is running and logged in.",
"invalidjoblines": "Job has invalid job lines.",
"invoicing": "Error invoicing Job. {{error}}",
"missingjoblineids": "Missing job line IDs for job lines.",
"noaccess": "This Job does not exist or you do not have access to it.",
"nodamage": "No damage points on estimate.",
"nodates": "No dates specified for this Job.",

View File

@@ -1674,7 +1674,9 @@
"deleted": "Error al eliminar el trabajo.",
"exporting": "",
"exporting-partner": "",
"invalidjoblines": "",
"invoicing": "",
"missingjoblineids": "",
"noaccess": "Este trabajo no existe o no tiene acceso a él.",
"nodamage": "",
"nodates": "No hay fechas especificadas para este trabajo.",

View File

@@ -1674,7 +1674,9 @@
"deleted": "Erreur lors de la suppression du travail.",
"exporting": "",
"exporting-partner": "",
"invalidjoblines": "",
"invoicing": "",
"missingjoblineids": "",
"noaccess": "Ce travail n'existe pas ou vous n'y avez pas accès.",
"nodamage": "",
"nodates": "Aucune date spécifiée pour ce travail.",

View File

@@ -248,7 +248,8 @@ const client = new ApolloClient({
watchQuery: {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
errorPolicy: "ignore"
errorPolicy: "ignore",
notifyOnNetworkStatusChange: false
},
query: {
fetchPolicy: "network-only",

View File

@@ -306,8 +306,7 @@ async function FortellisSelectedCustomer({ socket, redisHelpers, selectedCustome
CreateFortellisLogEvent(socket, "ERROR", `{7.1} Error posting vehicle service history. ${error.message}`);
}
//TODO: IF THE VEHICLE SERVICE HISTORY FAILS, WE NEED TO MARK IT AS SUCH AND NOT DELETE THE TRANSACTION.
//socket.emit("export-success", JobData.id);
socket.emit("export-success", JobData.id);
} else {
//There was something wrong. Throw an error to trigger clean up.
//throw new Error("Error posting DMS Batch Transaction");
@@ -431,10 +430,10 @@ async function QueryDmsCustomerByName({ socket, redisHelpers, JobData }) {
const ownerName =
JobData.ownr_co_nm && JobData.ownr_co_nm.trim() !== ""
//? [["firstName", JobData.ownr_co_nm.replace(replaceSpecialRegex, "").toUpperCase()]] // Commented out until we receive direction.
? [["email", JobData.ownr_ea.toUpperCase()]]
? [["phone", JobData.ownr_ph1?.replace(replaceSpecialRegex, "")]]
: [
["firstName", JobData.ownr_fn.replace(replaceSpecialRegex, "").toUpperCase()],
["lastName", JobData.ownr_ln.replace(replaceSpecialRegex, "").toUpperCase()]
["firstName", JobData.ownr_fn?.replace(replaceSpecialRegex, "").toUpperCase()],
["lastName", JobData.ownr_ln?.replace(replaceSpecialRegex, "").toUpperCase()]
];
try {
const result = await MakeFortellisCall({

View File

@@ -1725,6 +1725,7 @@ query QUERY_JOB_COSTING_DETAILS($id: uuid!) {
profitcenter_part
profitcenter_labor
act_price_before_ppc
manual_line
}
bills {
id
@@ -1842,6 +1843,7 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT
op_code_desc
profitcenter_part
profitcenter_labor
manual_line
}
bills {
id

View File

@@ -343,7 +343,7 @@ function GenerateCostingData(job) {
if (!acc.labor[laborProfitCenter]) acc.labor[laborProfitCenter] = Dinero();
acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add(laborAmount);
if (val.act_price > 0 && val.lbr_op === "OP14") {
if (val.act_price > 0 && val.lbr_op === "OP14" && !val.part_type) {
//Scenario where SGI may pay out hours using a part price.
acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add(
Dinero({
@@ -363,6 +363,9 @@ function GenerateCostingData(job) {
if (val.mod_lbr_ty !== "LAR" && mashOpCodes.includes(val.lbr_op)) {
materialsHours.mashHrs += val.mod_lb_hrs || 0;
}
if (val.manual_line === true && !mashOpCodes.includes(val.lbr_op) && val.mod_lbr_ty !== "LAR" ) {
materialsHours.mashHrs += val.mod_lb_hrs || 0;
}
}
}
@@ -499,7 +502,7 @@ function GenerateCostingData(job) {
let disc = Dinero(),
markup = Dinero();
const convertedKey = Object.keys(defaultProfits).find((k) => defaultProfits[k] === key);
if (job.parts_tax_rates && job.parts_tax_rates[convertedKey.toUpperCase()]) {
if (convertedKey && job.parts_tax_rates && job.parts_tax_rates[convertedKey.toUpperCase()]) {
if (
job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp !== undefined &&
job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp >= 0
@@ -523,14 +526,16 @@ function GenerateCostingData(job) {
}
if (InstanceManager({ rome: true })) {
const correspondingCiecaStlTotalLine = job.cieca_stl?.data.find(
(c) => c.ttl_typecd === convertedKey.toUpperCase()
);
if (
correspondingCiecaStlTotalLine &&
Math.abs(jobLineTotalsByProfitCenter.parts[key].getAmount() - correspondingCiecaStlTotalLine.ttl_amt * 100) > 1
) {
jobLineTotalsByProfitCenter.parts[key] = jobLineTotalsByProfitCenter.parts[key].add(disc).add(markup);
if (convertedKey) {
const correspondingCiecaStlTotalLine = job.cieca_stl?.data.find(
(c) => c.ttl_typecd === convertedKey.toUpperCase()
);
if (
correspondingCiecaStlTotalLine &&
Math.abs(jobLineTotalsByProfitCenter.parts[key].getAmount() - correspondingCiecaStlTotalLine.ttl_amt * 100) > 1
) {
jobLineTotalsByProfitCenter.parts[key] = jobLineTotalsByProfitCenter.parts[key].add(disc).add(markup);
}
}
}
});