Merged in feature/IO-3499-React-19 (pull request #2851)
Feature/IO-3499 React 19
This commit is contained in:
@@ -124,7 +124,7 @@ export function BillFormComponent({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<FormFieldsChanged form={form} />
|
<FormFieldsChanged form={form} />
|
||||||
<Form.Item style={{ display: "none" }} name="isinhouse" valuePropName="checked">
|
<Form.Item hidden name="isinhouse" valuePropName="checked">
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<LayoutFormRow grow>
|
<LayoutFormRow grow>
|
||||||
|
|||||||
@@ -15,7 +15,20 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
const mapDispatchToProps = () => ({});
|
const mapDispatchToProps = () => ({});
|
||||||
|
|
||||||
const iconStyle = { marginLeft: ".3rem" };
|
const iconStyle = {
|
||||||
|
marginLeft: ".3rem"
|
||||||
|
};
|
||||||
|
|
||||||
|
const iconClickableStyle = {
|
||||||
|
marginLeft: ".3rem",
|
||||||
|
cursor: "pointer"
|
||||||
|
};
|
||||||
|
|
||||||
|
const iconDisabledStyle = {
|
||||||
|
marginLeft: ".3rem",
|
||||||
|
cursor: "not-allowed",
|
||||||
|
opacity: 0.5
|
||||||
|
};
|
||||||
|
|
||||||
export function JobEmployeeAssignments({
|
export function JobEmployeeAssignments({
|
||||||
bodyshop,
|
bodyshop,
|
||||||
@@ -58,7 +71,7 @@ export function JobEmployeeAssignments({
|
|||||||
|
|
||||||
const renderAssigner = (operation) => {
|
const renderAssigner = (operation) => {
|
||||||
if (jobRO) {
|
if (jobRO) {
|
||||||
return <PlusCircleFilled disabled style={iconStyle} />;
|
return <PlusCircleFilled style={iconDisabledStyle} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const popContent = (
|
const popContent = (
|
||||||
@@ -117,6 +130,7 @@ export function JobEmployeeAssignments({
|
|||||||
<span
|
<span
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
style={{ display: "inline-flex", alignItems: "center", cursor: "pointer" }}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
// Prevent the click from being re-interpreted as "outside"
|
// Prevent the click from being re-interpreted as "outside"
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -129,9 +143,8 @@ export function JobEmployeeAssignments({
|
|||||||
openFor(operation);
|
openFor(operation);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
style={{ display: "inline-flex", alignItems: "center" }}
|
|
||||||
>
|
>
|
||||||
<PlusCircleFilled style={iconStyle} />
|
<PlusCircleFilled style={iconClickableStyle} />
|
||||||
</span>
|
</span>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ export default function PartsOrderModalPriceChange({ form, field }) {
|
|||||||
{
|
{
|
||||||
key: "custom",
|
key: "custom",
|
||||||
label: (
|
label: (
|
||||||
<InputNumber
|
<Space.Compact>
|
||||||
onClick={(e) => e.stopPropagation()}
|
<InputNumber
|
||||||
addonAfter="%"
|
onClick={(e) => e.stopPropagation()}
|
||||||
onKeyUp={(e) => {
|
onKeyUp={(e) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
const values = form.getFieldsValue();
|
const values = form.getFieldsValue();
|
||||||
const { parts_order_lines } = values;
|
const { parts_order_lines } = values;
|
||||||
|
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
parts_order_lines: {
|
parts_order_lines: {
|
||||||
@@ -56,10 +56,12 @@ export default function PartsOrderModalPriceChange({ form, field }) {
|
|||||||
});
|
});
|
||||||
e.target.value = 0;
|
e.target.value = 0;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
min={0}
|
min={0}
|
||||||
max={100}
|
max={100}
|
||||||
/>
|
/>
|
||||||
|
<span style={{ padding: "0 11px", backgroundColor: "#fafafa", border: "1px solid #d9d9d9", borderLeft: 0 }}>%</span>
|
||||||
|
</Space.Compact>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export function PartsOrderModalComponent({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item name="returnfrombill" style={{ display: "none" }}>
|
<Form.Item name="returnfrombill" hidden>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<LayoutFormRow grow noDivider>
|
<LayoutFormRow grow noDivider>
|
||||||
@@ -199,7 +199,10 @@ export function PartsOrderModalComponent({
|
|||||||
key={`${index}act_price`}
|
key={`${index}act_price`}
|
||||||
name={[field.name, "act_price"]}
|
name={[field.name, "act_price"]}
|
||||||
>
|
>
|
||||||
<CurrencyInput addonBefore={<PartsOrderModalPriceChange form={form} field={field} />} />
|
<Space.Compact style={{ width: "100%" }}>
|
||||||
|
<PartsOrderModalPriceChange form={form} field={field} />
|
||||||
|
<CurrencyInput style={{ flex: 1 }} />
|
||||||
|
</Space.Compact>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{isReturn && (
|
{isReturn && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
|||||||
@@ -80,36 +80,75 @@ export function PartsOrderModalContainer({
|
|||||||
const handleFinish = async ({ order_type, removefrompartsqueue, is_quote, ...values }) => {
|
const handleFinish = async ({ order_type, removefrompartsqueue, is_quote, ...values }) => {
|
||||||
logImEXEvent("parts_order_insert");
|
logImEXEvent("parts_order_insert");
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
let insertResult;
|
|
||||||
|
|
||||||
insertResult = await insertPartOrder({
|
// Force job_line_id from context so it never gets dropped by AntD form submission behavior.
|
||||||
variables: {
|
const submittedLines = values?.parts_order_lines?.data ?? [];
|
||||||
po: [
|
const forcedLines = submittedLines.map((p, index) => {
|
||||||
{
|
const originalLine = linesToOrder?.[index];
|
||||||
...values,
|
const jobLineId = isReturn ? originalLine?.joblineid : originalLine?.id;
|
||||||
order_date: dayjs().format("YYYY-MM-DD"),
|
|
||||||
orderedby: currentUser.email,
|
return {
|
||||||
jobid: jobId,
|
...p,
|
||||||
user_email: currentUser.email,
|
job_line_id: jobLineId
|
||||||
return: isReturn,
|
};
|
||||||
status: is_quote
|
|
||||||
? bodyshop.md_order_statuses.default_quote || "Quote"
|
|
||||||
: bodyshop.md_order_statuses.default_ordered || "Ordered*"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"]
|
|
||||||
});
|
});
|
||||||
if (insertResult.errors) {
|
|
||||||
|
const missingIdx = forcedLines.findIndex((l) => !l?.job_line_id);
|
||||||
|
if (missingIdx !== -1) {
|
||||||
notification.error({
|
notification.error({
|
||||||
title: t("parts_orders.errors.creating"),
|
title: t("parts_orders.errors.creating"),
|
||||||
description: JSON.stringify(insertResult.errors)
|
description: `Missing job_line_id for parts line #${missingIdx + 1}`
|
||||||
});
|
});
|
||||||
|
setSaving(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let insertResult;
|
||||||
|
try {
|
||||||
|
insertResult = await insertPartOrder({
|
||||||
|
variables: {
|
||||||
|
po: [
|
||||||
|
{
|
||||||
|
...values,
|
||||||
|
order_date: dayjs().format("YYYY-MM-DD"),
|
||||||
|
orderedby: currentUser.email,
|
||||||
|
jobid: jobId,
|
||||||
|
user_email: currentUser.email,
|
||||||
|
return: isReturn,
|
||||||
|
status: is_quote
|
||||||
|
? bodyshop.md_order_statuses.default_quote || "Quote"
|
||||||
|
: bodyshop.md_order_statuses.default_ordered || "Ordered*",
|
||||||
|
|
||||||
|
// override nested lines to guarantee linkage
|
||||||
|
parts_order_lines: { data: forcedLines }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (insertResult.errors) {
|
||||||
|
notification.error({
|
||||||
|
title: t("parts_orders.errors.creating"),
|
||||||
|
description: JSON.stringify(insertResult.errors)
|
||||||
|
});
|
||||||
|
setSaving(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
notification.error({
|
||||||
|
title: t("parts_orders.errors.creating"),
|
||||||
|
description: err?.message || String(err)
|
||||||
|
});
|
||||||
|
console.error("Parts order insert error:", err);
|
||||||
|
setSaving(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
notification.success({
|
notification.success({
|
||||||
title: values.isReturn ? t("parts_orders.successes.return_created") : t("parts_orders.successes.created")
|
title: isReturn ? t("parts_orders.successes.return_created") : t("parts_orders.successes.created")
|
||||||
});
|
});
|
||||||
|
|
||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: jobId,
|
jobid: jobId,
|
||||||
operation: isReturn
|
operation: isReturn
|
||||||
@@ -118,36 +157,54 @@ export function PartsOrderModalContainer({
|
|||||||
type: isReturn ? "jobspartsreturn" : "jobspartsorder"
|
type: isReturn ? "jobspartsreturn" : "jobspartsorder"
|
||||||
});
|
});
|
||||||
|
|
||||||
const jobLinesResult = await updateJobLines({
|
// Use linesToOrder from context instead of form values to preserve job line ids
|
||||||
variables: {
|
const jobLineIds = (linesToOrder ?? [])
|
||||||
ids: values.parts_order_lines.data.filter((item) => item.job_line_id).map((item) => item.job_line_id),
|
.filter((line) => (isReturn ? line.joblineid : line.id))
|
||||||
status: isReturn
|
.map((line) => (isReturn ? line.joblineid : line.id));
|
||||||
? bodyshop.md_order_statuses.default_returned || "Returned*"
|
|
||||||
: is_quote
|
|
||||||
? bodyshop.md_order_statuses.default_quote || "Quote"
|
|
||||||
: bodyshop.md_order_statuses.default_ordered || "Ordered*"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isReturn && removefrompartsqueue) {
|
try {
|
||||||
await updateJob({
|
const jobLinesResult = await updateJobLines({
|
||||||
variables: {
|
variables: {
|
||||||
jobId: jobId,
|
ids: jobLineIds,
|
||||||
job: {
|
status: isReturn
|
||||||
queued_for_parts: false
|
? bodyshop.md_order_statuses.default_returned || "Returned*"
|
||||||
}
|
: is_quote
|
||||||
|
? bodyshop.md_order_statuses.default_quote || "Quote"
|
||||||
|
: bodyshop.md_order_statuses.default_ordered || "Ordered*"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (jobLinesResult.errors) {
|
||||||
|
notification.error({
|
||||||
|
title: t("parts_orders.errors.updating_status"),
|
||||||
|
description: JSON.stringify(jobLinesResult.errors)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
notification.error({
|
||||||
|
title: t("parts_orders.errors.updating_status"),
|
||||||
|
description: err?.message || String(err)
|
||||||
|
});
|
||||||
|
console.error("Job lines update error:", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jobLinesResult.errors) {
|
if (!isReturn && removefrompartsqueue) {
|
||||||
notification.error({
|
try {
|
||||||
title: t("parts_orders.errors.creating"),
|
await updateJob({
|
||||||
description: JSON.stringify(jobLinesResult.errors)
|
variables: {
|
||||||
});
|
jobId: jobId,
|
||||||
|
job: {
|
||||||
|
queued_for_parts: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Update job queue error:", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (values.vendorid === bodyshop.inhousevendorid) {
|
if (values.vendorid === bodyshop.inhousevendorid) {
|
||||||
|
// Use linesToOrder for joblineid, and use forcedLines for the user-entered fields
|
||||||
setBillEnterContext({
|
setBillEnterContext({
|
||||||
actions: { refetch: refetch },
|
actions: { refetch: refetch },
|
||||||
context: {
|
context: {
|
||||||
@@ -159,11 +216,12 @@ export function PartsOrderModalContainer({
|
|||||||
isinhouse: true,
|
isinhouse: true,
|
||||||
date: dayjs(),
|
date: dayjs(),
|
||||||
total: 0,
|
total: 0,
|
||||||
billlines: values.parts_order_lines.data.map((p) => {
|
billlines: forcedLines.map((p, index) => {
|
||||||
|
const originalLine = linesToOrder?.[index];
|
||||||
return {
|
return {
|
||||||
joblineid: p.job_line_id,
|
joblineid: isReturn ? originalLine?.joblineid : originalLine?.id,
|
||||||
actual_price: p.act_price,
|
actual_price: p.act_price,
|
||||||
actual_cost: 0, //p.act_price,
|
actual_cost: 0, // p.act_price,
|
||||||
line_desc: p.line_desc,
|
line_desc: p.line_desc,
|
||||||
line_remarks: p.line_remarks,
|
line_remarks: p.line_remarks,
|
||||||
part_type: p.part_type,
|
part_type: p.part_type,
|
||||||
@@ -178,6 +236,8 @@ export function PartsOrderModalContainer({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setSaving(false);
|
||||||
toggleModalVisible();
|
toggleModalVisible();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -187,9 +247,8 @@ export function PartsOrderModalContainer({
|
|||||||
const Templates = TemplateList("partsorder", context);
|
const Templates = TemplateList("partsorder", context);
|
||||||
|
|
||||||
if (sendType === "e") {
|
if (sendType === "e") {
|
||||||
const matchingVendor = data.vendors.filter((item) => item.id === values.vendorid)[0];
|
const matchingVendor = data?.vendors?.find((item) => item.id === values.vendorid);
|
||||||
|
const vendorEmails = matchingVendor?.email && matchingVendor.email.split(RegExp("[;,]"));
|
||||||
let vendorEmails = matchingVendor?.email && matchingVendor.email.split(RegExp("[;,]"));
|
|
||||||
|
|
||||||
GenerateDocument(
|
GenerateDocument(
|
||||||
{
|
{
|
||||||
@@ -233,7 +292,7 @@ export function PartsOrderModalContainer({
|
|||||||
notification
|
notification
|
||||||
);
|
);
|
||||||
} else if (sendType === "oec") {
|
} else if (sendType === "oec") {
|
||||||
//Send to Partner OEC.
|
// Send to Partner OEC.
|
||||||
try {
|
try {
|
||||||
const partsOrder = await client.query({
|
const partsOrder = await client.query({
|
||||||
query: QUERY_PARTS_ORDER_OEC,
|
query: QUERY_PARTS_ORDER_OEC,
|
||||||
@@ -241,26 +300,21 @@ export function PartsOrderModalContainer({
|
|||||||
id: insertResult.data.insert_parts_orders.returning[0].id
|
id: insertResult.data.insert_parts_orders.returning[0].id
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let po;
|
let po;
|
||||||
//Massage the data based on the split. Should they be able to overwrite OEC pricing?
|
// Massage the data based on the split. Should they be able to overwrite OEC pricing?
|
||||||
if (OEConnection_PriceChange.treatment === "on") {
|
if (OEConnection_PriceChange.treatment === "on") {
|
||||||
//Set the flag to include the override.
|
|
||||||
po = _.cloneDeep(partsOrder.data.parts_orders_by_pk);
|
po = _.cloneDeep(partsOrder.data.parts_orders_by_pk);
|
||||||
po.parts_order_lines.forEach((pol) => {
|
po.parts_order_lines.forEach((pol) => {
|
||||||
pol.priceChange = true;
|
pol.priceChange = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const oecResponse = await axios.post(
|
const oecResponse = await axios.post("http://localhost:1337/oec/", po || partsOrder.data.parts_orders_by_pk, {
|
||||||
"http://localhost:1337/oec/",
|
headers: {
|
||||||
|
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`
|
||||||
po || partsOrder.data.parts_orders_by_pk,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
if (oecResponse.data && oecResponse.data.success === false) {
|
if (oecResponse.data && oecResponse.data.success === false) {
|
||||||
notification.error({
|
notification.error({
|
||||||
@@ -269,17 +323,18 @@ export function PartsOrderModalContainer({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.log("Error OEC.", error);
|
console.log("Error OEC.", err);
|
||||||
notification.error({
|
notification.error({
|
||||||
title: t("parts_orders.errors.oec", {
|
title: t("parts_orders.errors.oec", {
|
||||||
error: JSON.stringify(error.message)
|
error: JSON.stringify(err?.message || String(err))
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
toggleModalVisible();
|
toggleModalVisible();
|
||||||
};
|
};
|
||||||
@@ -290,7 +345,6 @@ export function PartsOrderModalContainer({
|
|||||||
deliver_by: isReturn ? dayjs(new Date()) : null,
|
deliver_by: isReturn ? dayjs(new Date()) : null,
|
||||||
vendorid: vendorId,
|
vendorid: vendorId,
|
||||||
returnfrombill: returnFromBill,
|
returnfrombill: returnFromBill,
|
||||||
|
|
||||||
parts_order_lines: {
|
parts_order_lines: {
|
||||||
data: linesToOrder
|
data: linesToOrder
|
||||||
? linesToOrder.reduce((acc, value) => {
|
? linesToOrder.reduce((acc, value) => {
|
||||||
|
|||||||
@@ -46,10 +46,10 @@ export function PartsReceiveModalComponent({ bodyshop, form }) {
|
|||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
<Form.Item required={false} key={field.key}>
|
<Form.Item required={false} key={field.key}>
|
||||||
<div style={{ display: "flex", alignItems: "center" }}>
|
<div style={{ display: "flex", alignItems: "center" }}>
|
||||||
<Form.Item style={{ display: "none" }} key={`${index}joblineid`} name={[field.name, "joblineid"]}>
|
<Form.Item hidden key={`${index}joblineid`} name={[field.name, "joblineid"]}>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item style={{ display: "none" }} key={`${index}id`} name={[field.name, "id"]}>
|
<Form.Item hidden key={`${index}id`} name={[field.name, "id"]}>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<LayoutFormRow grow style={{ flex: 1 }}>
|
<LayoutFormRow grow style={{ flex: 1 }}>
|
||||||
|
|||||||
@@ -37,39 +37,85 @@ export function PartsReceiveModalContainer({ partsReceiveModal, toggleModalVisib
|
|||||||
const handleFinish = async (values) => {
|
const handleFinish = async (values) => {
|
||||||
logImEXEvent("parts_order_receive");
|
logImEXEvent("parts_order_receive");
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const result = await Promise.all(
|
|
||||||
values.partsorderlines.map((li) => {
|
|
||||||
return receivePartsLine({
|
|
||||||
variables: {
|
|
||||||
lineId: li.joblineid,
|
|
||||||
line: {
|
|
||||||
location: li.location,
|
|
||||||
status: bodyshop.md_order_statuses.default_received || "Received*"
|
|
||||||
},
|
|
||||||
orderLineId: li.id,
|
|
||||||
orderLine: {
|
|
||||||
status: bodyshop.md_order_statuses.default_received || "Received*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
result.forEach((jobLinesResult) => {
|
try {
|
||||||
if (jobLinesResult.errors) {
|
const submittedLines = values.partsorderlines ?? [];
|
||||||
|
|
||||||
|
// Preserve ids from modal context, merge editable fields from form submission (e.g. location)
|
||||||
|
const mergedLines = (partsorderlines ?? []).map((ctxLine, idx) => ({
|
||||||
|
...ctxLine,
|
||||||
|
...submittedLines[idx]
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Optional: hard guard to catch the exact failure early with a better message
|
||||||
|
const missing = mergedLines
|
||||||
|
.map((l, idx) => ({
|
||||||
|
idx,
|
||||||
|
orderLineId: l?.id,
|
||||||
|
jobLineId: l?.joblineid // adjust if your ctx uses job_line_id instead
|
||||||
|
}))
|
||||||
|
.filter((x) => !x.orderLineId || !x.jobLineId);
|
||||||
|
|
||||||
|
if (missing.length) {
|
||||||
notification.error({
|
notification.error({
|
||||||
title: t("parts_orders.errors.creating"),
|
title: t("parts_orders.errors.creating"),
|
||||||
description: JSON.stringify(jobLinesResult.errors)
|
description: `Missing required ids for lines: ${missing
|
||||||
|
.map((m) => `#${m.idx + 1} (orderLineId=${m.orderLineId}, jobLineId=${m.jobLineId})`)
|
||||||
|
.join(", ")}`
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
notification.success({
|
const results = await Promise.allSettled(
|
||||||
title: t("parts_orders.successes.received")
|
mergedLines.map((li) =>
|
||||||
});
|
receivePartsLine({
|
||||||
setLoading(false);
|
variables: {
|
||||||
if (refetch) refetch();
|
lineId: li.joblineid,
|
||||||
toggleModalVisible();
|
line: {
|
||||||
|
location: li.location,
|
||||||
|
status: bodyshop.md_order_statuses.default_received || "Received*"
|
||||||
|
},
|
||||||
|
orderLineId: li.id,
|
||||||
|
orderLine: {
|
||||||
|
status: bodyshop.md_order_statuses.default_received || "Received*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Ensures GraphQL errors come back on the result when possible (instead of only throwing)
|
||||||
|
errorPolicy: "all"
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const errors = [];
|
||||||
|
results.forEach((r, idx) => {
|
||||||
|
if (r.status === "rejected") {
|
||||||
|
errors.push({ idx, message: r.reason?.message ?? String(r.reason) });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (r.value?.errors?.length) {
|
||||||
|
errors.push({
|
||||||
|
idx,
|
||||||
|
message: r.value.errors.map((e) => e.message).join(" | ")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (errors.length) {
|
||||||
|
errors.forEach((e) =>
|
||||||
|
notification.error({
|
||||||
|
title: t("parts_orders.errors.creating"),
|
||||||
|
description: `Line ${e.idx + 1}: ${e.message}`
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return; // keep modal open so user can retry
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.success({ title: t("parts_orders.successes.received") });
|
||||||
|
if (refetch) refetch();
|
||||||
|
toggleModalVisible();
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
|
|||||||
@@ -1500,7 +1500,7 @@ export function ShopInfoResponsibilityCenters({ bodyshop, form }) {
|
|||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
style={{ display: "none" }}
|
hidden
|
||||||
label={t("jobs.fields.parts_tax_rates.prt_tx_ty1")}
|
label={t("jobs.fields.parts_tax_rates.prt_tx_ty1")}
|
||||||
name={["md_responsibility_centers", "parts_tax_rates", "PAN", "prt_tx_ty1"]}
|
name={["md_responsibility_centers", "parts_tax_rates", "PAN", "prt_tx_ty1"]}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -93,7 +93,10 @@ export default function TimeTicketCalculatorComponent({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item name="percent">
|
<Form.Item name="percent">
|
||||||
<InputNumber min={0} max={100} precision={1} addonAfter="%" />
|
<Space.Compact>
|
||||||
|
<InputNumber min={0} max={100} precision={1} />
|
||||||
|
<span style={{ padding: "0 11px", backgroundColor: "#fafafa", border: "1px solid #d9d9d9", borderLeft: 0, display: "flex", alignItems: "center" }}>%</span>
|
||||||
|
</Space.Compact>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Button htmlType="submit">Calculate</Button>
|
<Button htmlType="submit">Calculate</Button>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -96,7 +96,10 @@ export function TimeTicketListTeamPay({ bodyshop, context }) {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item name="percent">
|
<Form.Item name="percent">
|
||||||
<InputNumber min={0} max={100} precision={1} addonAfter="%" />
|
<Space.Compact>
|
||||||
|
<InputNumber min={0} max={100} precision={1} />
|
||||||
|
<span style={{ padding: "0 11px", backgroundColor: "#fafafa", border: "1px solid #d9d9d9", borderLeft: 0, display: "flex", alignItems: "center" }}>%</span>
|
||||||
|
</Space.Compact>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
|
|
||||||
|
|||||||
@@ -184,13 +184,7 @@ export function TimeTicketModalComponent({
|
|||||||
}}
|
}}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item name="flat_rate" label={t("timetickets.fields.flat_rate")} valuePropName="checked" noStyle hidden>
|
||||||
name="flat_rate"
|
|
||||||
label={t("timetickets.fields.flat_rate")}
|
|
||||||
valuePropName="checked"
|
|
||||||
noStyle
|
|
||||||
style={{ display: "none" }}
|
|
||||||
>
|
|
||||||
<Switch style={{ display: "none" }} />
|
<Switch style={{ display: "none" }} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
|
|||||||
Reference in New Issue
Block a user