even more updates.

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2023-12-14 16:27:00 -05:00
parent 1261e8001b
commit b2c8e45d5e
41 changed files with 7326 additions and 7388 deletions

View File

@@ -1,23 +1,11 @@
import { DeleteFilled, WarningFilled, DownOutlined } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react";
import {
Divider,
Form,
Input,
InputNumber,
Radio,
Space,
Tag,
Select,
Menu,
Dropdown,
Checkbox,
} from "antd";
import {DeleteFilled, DownOutlined, WarningFilled} from "@ant-design/icons";
import {useSplitTreatments} from "@splitsoftware/splitio-react";
import {Checkbox, Divider, Dropdown, Form, Input, InputNumber, Menu, Radio, Select, Space, Tag,} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectBodyshop} from "../../redux/user/user.selectors";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
@@ -26,329 +14,319 @@ import VendorSearchSelect from "../vendor-search-select/vendor-search-select.com
import PartsOrderModalPriceChange from "./parts-order-modal-price-change.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(PartsOrderModalComponent);
export function PartsOrderModalComponent({
bodyshop,
vendorList,
sendTypeState,
isReturn,
preferredMake,
job,
form,
}) {
const [sendType, setSendType] = sendTypeState;
const { OEConnection } = useTreatments(
["OEConnection"],
{},
bodyshop.imexshopid
);
const { OEConnection_PriceChange } = useTreatments(
["OEConnection_PriceChange"],
{},
bodyshop.imexshopid
);
const { t } = useTranslation();
const handleClick = ({ item, key, keyPath }) => {
form.setFieldsValue({ comments: item.props.value });
};
export function PartsOrderModalComponent({bodyshop, vendorList, sendTypeState, isReturn, preferredMake, job, form,}) {
const [sendType, setSendType] = sendTypeState;
const menu = (
<div>
<Menu onClick={handleClick}>
{bodyshop.md_parts_order_comment.map((comment, idx) => (
<Menu.Item value={comment.comment} key={idx}>
{comment.label}
</Menu.Item>
))}
</Menu>
</div>
);
const {treatments: {OEConnection, OEConnection_PriceChange}} = useSplitTreatments({
attributes: {},
names: ["OEConnection", "OEConnection_PriceChange"],
splitKey: bodyshop.imexshopid,
});
return (
<div>
<Form.Item name="returnfrombill" style={{ display: "none" }}>
<Input />
</Form.Item>
<LayoutFormRow grow noDivider>
<Form.Item
name="vendorid"
label={t("vendors.fields.name")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<VendorSearchSelect
options={vendorList}
disabled={isReturn}
preferredMake={preferredMake}
showPhone
/>
</Form.Item>
<Form.Item
name="deliver_by"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
label={t("parts_orders.fields.deliver_by")}
>
<FormDatePicker onlyFuture />
</Form.Item>
{job && job.special_coverage_policy && (
<Tag color="tomato">
<Space>
<WarningFilled />
<span>{t("jobs.labels.specialcoveragepolicy")}</span>
</Space>
</Tag>
)}
{!isReturn && (
<Form.Item
name="removefrompartsqueue"
label={t("parts_orders.labels.removefrompartsqueue")}
valuePropName="checked"
>
<Checkbox />
</Form.Item>
)}
{OEConnection.treatment === "on" && !isReturn && (
<Form.Item
name="is_quote"
label={t("parts_orders.labels.is_quote")}
valuePropName="checked"
>
<Checkbox />
</Form.Item>
)}
<Form.Item
name="order_type"
initialValue="parts_order"
label={t("parts_orders.labels.order_type")}
>
<Radio.Group disabled={sendType === "oec"}>
<Radio value={"parts_order"}>
{t("parts_orders.labels.parts_order")}
</Radio>
<Radio value={"sublet"}>
{t("parts_orders.labels.sublet_order")}
</Radio>
</Radio.Group>
</Form.Item>
</LayoutFormRow>
<Divider orientation="left">
{t("parts_orders.labels.inthisorder")}
</Divider>
<Form.List name={["parts_order_lines", "data"]}>
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item required={false} key={field.key}>
<div style={{ display: "flex" }}>
<LayoutFormRow grow noDivider style={{ flex: 1 }}>
<Form.Item
//span={8}
label={t("parts_orders.fields.line_desc")}
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
rules={[
{
const {t} = useTranslation();
const handleClick = ({item, key, keyPath}) => {
form.setFieldsValue({comments: item.props.value});
};
const menu = (
<div>
<Menu onClick={handleClick}>
{bodyshop.md_parts_order_comment.map((comment, idx) => (
<Menu.Item value={comment.comment} key={idx}>
{comment.label}
</Menu.Item>
))}
</Menu>
</div>
);
return (
<div>
<Form.Item name="returnfrombill" style={{display: "none"}}>
<Input/>
</Form.Item>
<LayoutFormRow grow noDivider>
<Form.Item
name="vendorid"
label={t("vendors.fields.name")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("parts_orders.fields.line_remarks")}
key={`${index}line_remarks`}
name={[field.name, "line_remarks"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("parts_orders.fields.part_type")}
key={`${index}part_type`}
name={[field.name, "part_type"]}
>
<Select
disabled={
!(
sendType === "oec" &&
OEConnection_PriceChange.treatment === "on"
)
}
>
<Select.Option value="PAA">
{t("joblines.fields.part_types.PAA")}
</Select.Option>
<Select.Option value="PAC">
{t("joblines.fields.part_types.PAC")}
</Select.Option>
<Select.Option value="PAL">
{t("joblines.fields.part_types.PAL")}
</Select.Option>
<Select.Option value="PAG">
{t("joblines.fields.part_types.PAG")}
</Select.Option>
<Select.Option value="PAM">
{t("joblines.fields.part_types.PAM")}
</Select.Option>
<Select.Option value="PAP">
{t("joblines.fields.part_types.PAP")}
</Select.Option>
<Select.Option value="PAN">
{t("joblines.fields.part_types.PAN")}
</Select.Option>
<Select.Option value="PAO">
{t("joblines.fields.part_types.PAO")}
</Select.Option>
<Select.Option value="PAR">
{t("joblines.fields.part_types.PAR")}
</Select.Option>
<Select.Option value="PAS">
{t("joblines.fields.part_types.PAS")}
</Select.Option>
</Select>
</Form.Item>
<Form.Item
label={t("parts_orders.fields.oem_partno")}
key={`${index}oem_partno`}
name={[field.name, "oem_partno"]}
>
<Input />
</Form.Item>
{
// <Form.Item
// label={t("parts_orders.fields.db_price")}
// key={`${index}db_price`}
// name={[field.name, "db_price"]}
// >
// <CurrencyInput />
// </Form.Item>
}
<Form.Item
label={t("parts_orders.fields.quantity")}
key={`${index}quantity`}
name={[field.name, "quantity"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("parts_orders.fields.act_price")}
key={`${index}act_price`}
name={[field.name, "act_price"]}
>
<CurrencyInput
addonBefore={
<PartsOrderModalPriceChange
form={form}
field={field}
/>
}
/>
</Form.Item>
{isReturn && (
<Form.Item
label={t("parts_orders.fields.cost")}
key={`${index}cost`}
name={[field.name, "cost"]}
>
<CurrencyInput />
</Form.Item>
)}
</LayoutFormRow>
<Space wrap size="small" align="center">
<div>
<DeleteFilled
style={{ margin: "1rem" }}
onClick={() => {
remove(field.name);
}}
/>
</div>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</Space>
</div>
},
]}
>
<VendorSearchSelect
options={vendorList}
disabled={isReturn}
preferredMake={preferredMake}
showPhone
/>
</Form.Item>
))}
</div>
);
}}
</Form.List>
<Form.Item
name="comments"
label={
<Space>
{t("parts_orders.fields.comments")}
<Dropdown overlay={menu}>
<a
className="ant-dropdown-link"
href=" #"
onClick={(e) => e.preventDefault()}
>
<DownOutlined />
</a>
</Dropdown>
</Space>
}
>
<Input.TextArea rows={3} />
</Form.Item>
<Form.Item
name="deliver_by"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
label={t("parts_orders.fields.deliver_by")}
>
<FormDatePicker onlyFuture/>
</Form.Item>
{job && job.special_coverage_policy && (
<Tag color="tomato">
<Space>
<WarningFilled/>
<span>{t("jobs.labels.specialcoveragepolicy")}</span>
</Space>
</Tag>
)}
{!isReturn && (
<Form.Item
name="removefrompartsqueue"
label={t("parts_orders.labels.removefrompartsqueue")}
valuePropName="checked"
>
<Checkbox/>
</Form.Item>
)}
{OEConnection.treatment === "on" && !isReturn && (
<Form.Item
name="is_quote"
label={t("parts_orders.labels.is_quote")}
valuePropName="checked"
>
<Checkbox/>
</Form.Item>
)}
<Form.Item noStyle shouldUpdate>
{() => {
const is_quote = form.getFieldValue("is_quote");
if (is_quote) setSendType("oec");
return (
<Radio.Group
defaultValue={sendType}
value={sendType}
onChange={(e) => setSendType(e.target.value)}
<Form.Item
name="order_type"
initialValue="parts_order"
label={t("parts_orders.labels.order_type")}
>
<Radio.Group disabled={sendType === "oec"}>
<Radio value={"parts_order"}>
{t("parts_orders.labels.parts_order")}
</Radio>
<Radio value={"sublet"}>
{t("parts_orders.labels.sublet_order")}
</Radio>
</Radio.Group>
</Form.Item>
</LayoutFormRow>
<Divider orientation="left">
{t("parts_orders.labels.inthisorder")}
</Divider>
<Form.List name={["parts_order_lines", "data"]}>
{(fields, {add, remove, move}) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item required={false} key={field.key}>
<div style={{display: "flex"}}>
<LayoutFormRow grow noDivider style={{flex: 1}}>
<Form.Item
//span={8}
label={t("parts_orders.fields.line_desc")}
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input/>
</Form.Item>
<Form.Item
label={t("parts_orders.fields.line_remarks")}
key={`${index}line_remarks`}
name={[field.name, "line_remarks"]}
>
<Input/>
</Form.Item>
<Form.Item
label={t("parts_orders.fields.part_type")}
key={`${index}part_type`}
name={[field.name, "part_type"]}
>
<Select
disabled={
!(
sendType === "oec" &&
OEConnection_PriceChange.treatment === "on"
)
}
>
<Select.Option value="PAA">
{t("joblines.fields.part_types.PAA")}
</Select.Option>
<Select.Option value="PAC">
{t("joblines.fields.part_types.PAC")}
</Select.Option>
<Select.Option value="PAL">
{t("joblines.fields.part_types.PAL")}
</Select.Option>
<Select.Option value="PAG">
{t("joblines.fields.part_types.PAG")}
</Select.Option>
<Select.Option value="PAM">
{t("joblines.fields.part_types.PAM")}
</Select.Option>
<Select.Option value="PAP">
{t("joblines.fields.part_types.PAP")}
</Select.Option>
<Select.Option value="PAN">
{t("joblines.fields.part_types.PAN")}
</Select.Option>
<Select.Option value="PAO">
{t("joblines.fields.part_types.PAO")}
</Select.Option>
<Select.Option value="PAR">
{t("joblines.fields.part_types.PAR")}
</Select.Option>
<Select.Option value="PAS">
{t("joblines.fields.part_types.PAS")}
</Select.Option>
</Select>
</Form.Item>
<Form.Item
label={t("parts_orders.fields.oem_partno")}
key={`${index}oem_partno`}
name={[field.name, "oem_partno"]}
>
<Input/>
</Form.Item>
{
// <Form.Item
// label={t("parts_orders.fields.db_price")}
// key={`${index}db_price`}
// name={[field.name, "db_price"]}
// >
// <CurrencyInput />
// </Form.Item>
}
<Form.Item
label={t("parts_orders.fields.quantity")}
key={`${index}quantity`}
name={[field.name, "quantity"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber/>
</Form.Item>
<Form.Item
label={t("parts_orders.fields.act_price")}
key={`${index}act_price`}
name={[field.name, "act_price"]}
>
<CurrencyInput
addonBefore={
<PartsOrderModalPriceChange
form={form}
field={field}
/>
}
/>
</Form.Item>
{isReturn && (
<Form.Item
label={t("parts_orders.fields.cost")}
key={`${index}cost`}
name={[field.name, "cost"]}
>
<CurrencyInput/>
</Form.Item>
)}
</LayoutFormRow>
<Space wrap size="small" align="center">
<div>
<DeleteFilled
style={{margin: "1rem"}}
onClick={() => {
remove(field.name);
}}
/>
</div>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</Space>
</div>
</Form.Item>
))}
</div>
);
}}
</Form.List>
<Form.Item
name="comments"
label={
<Space>
{t("parts_orders.fields.comments")}
<Dropdown overlay={menu}>
<a
className="ant-dropdown-link"
href=" #"
onClick={(e) => e.preventDefault()}
>
<DownOutlined/>
</a>
</Dropdown>
</Space>
}
>
<Radio disabled={is_quote} value={"none"}>
{t("general.labels.none")}
</Radio>
<Radio disabled={is_quote} value={"e"}>
{t("parts_orders.labels.email")}
</Radio>
<Radio disabled={is_quote} value={"p"}>
{t("parts_orders.labels.print")}
</Radio>
{OEConnection.treatment === "on" && !isReturn && (
<Radio value={"oec"}>{t("parts_orders.labels.oec")}</Radio>
)}
</Radio.Group>
);
}}
</Form.Item>
</div>
);
<Input.TextArea rows={3}/>
</Form.Item>
<Form.Item noStyle shouldUpdate>
{() => {
const is_quote = form.getFieldValue("is_quote");
if (is_quote) setSendType("oec");
return (
<Radio.Group
defaultValue={sendType}
value={sendType}
onChange={(e) => setSendType(e.target.value)}
>
<Radio disabled={is_quote} value={"none"}>
{t("general.labels.none")}
</Radio>
<Radio disabled={is_quote} value={"e"}>
{t("parts_orders.labels.email")}
</Radio>
<Radio disabled={is_quote} value={"p"}>
{t("parts_orders.labels.print")}
</Radio>
{OEConnection.treatment === "on" && !isReturn && (
<Radio value={"oec"}>{t("parts_orders.labels.oec")}</Radio>
)}
</Radio.Group>
);
}}
</Form.Item>
</div>
);
}

View File

@@ -1,395 +1,380 @@
import { useMutation, useQuery, useApolloClient } from "@apollo/client";
import { Form, Modal, notification } from "antd";
import {useApolloClient, useMutation, useQuery} from "@apollo/client";
import {Form, Modal, notification} from "antd";
import dayjs from "../../utils/day";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent, auth } from "../../firebase/firebase.utils";
import { UPDATE_JOB_LINE_STATUS } from "../../graphql/jobs-lines.queries";
import {
INSERT_NEW_PARTS_ORDERS,
QUERY_PARTS_ORDER_OEC,
} from "../../graphql/parts-orders.queries";
import { QUERY_ALL_VENDORS_FOR_ORDER } from "../../graphql/vendors.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { setEmailOptions } from "../../redux/email/email.actions";
import {
setModalContext,
toggleModalVisible,
} from "../../redux/modals/modals.actions";
import { selectPartsOrder } from "../../redux/modals/modals.selectors";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {auth, logImEXEvent} from "../../firebase/firebase.utils";
import {UPDATE_JOB_LINE_STATUS} from "../../graphql/jobs-lines.queries";
import {INSERT_NEW_PARTS_ORDERS, QUERY_PARTS_ORDER_OEC,} from "../../graphql/parts-orders.queries";
import {QUERY_ALL_VENDORS_FOR_ORDER} from "../../graphql/vendors.queries";
import {insertAuditTrail} from "../../redux/application/application.actions";
import {setEmailOptions} from "../../redux/email/email.actions";
import {setModalContext, toggleModalVisible,} from "../../redux/modals/modals.actions";
import {selectPartsOrder} from "../../redux/modals/modals.selectors";
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
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 { useTreatments } from "@splitsoftware/splitio-react";
import {useSplitTreatments} from "@splitsoftware/splitio-react";
import _ from "lodash";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import {UPDATE_JOB} from "../../graphql/jobs.queries";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
bodyshop: selectBodyshop,
partsOrderModal: selectPartsOrder,
currentUser: selectCurrentUser,
bodyshop: selectBodyshop,
partsOrderModal: selectPartsOrder,
});
const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
toggleModalVisible: () => dispatch(toggleModalVisible("partsOrder")),
setBillEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "billEnter" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
toggleModalVisible: () => dispatch(toggleModalVisible("partsOrder")),
setBillEnterContext: (context) =>
dispatch(setModalContext({context: context, modal: "billEnter"})),
insertAuditTrail: ({jobid, operation}) =>
dispatch(insertAuditTrail({jobid, operation})),
});
export function PartsOrderModalContainer({
partsOrderModal,
toggleModalVisible,
currentUser,
bodyshop,
setEmailOptions,
setBillEnterContext,
insertAuditTrail,
}) {
const { t } = useTranslation();
const client = useApolloClient();
const { OEConnection_PriceChange } = useTreatments(
["OEConnection_PriceChange"],
{},
bodyshop.imexshopid
);
const { visible, context, actions } = partsOrderModal;
const {
jobId,
linesToOrder,
isReturn,
vendorId,
returnFromBill,
invoiceNumber,
job,
} = context;
export function PartsOrderModalContainer({partsOrderModal,toggleModalVisible,currentUser, bodyshop, setEmailOptions, setBillEnterContext, insertAuditTrail,}) {
const {t} = useTranslation();
const client = useApolloClient();
const { refetch } = actions;
const [form] = Form.useForm();
const [saving, setSaving] = useState(false);
const sendTypeState = useState("e");
const sendType = sendTypeState[0];
const { loading, error, data } = useQuery(QUERY_ALL_VENDORS_FOR_ORDER, {
skip: !visible,
variables: { jobId: jobId },
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const [insertPartOrder] = useMutation(INSERT_NEW_PARTS_ORDERS);
const [updateJobLines] = useMutation(UPDATE_JOB_LINE_STATUS);
const [updateJob] = useMutation(UPDATE_JOB);
const handleFinish = async ({
order_type,
removefrompartsqueue,
is_quote,
...values
}) => {
logImEXEvent("parts_order_insert");
setSaving(true);
let insertResult;
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*",
},
],
},
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"],
});
if (!!insertResult.errors) {
notification["error"]({
message: t("parts_orders.errors.creating"),
description: JSON.stringify(insertResult.errors),
});
return;
}
notification["success"]({
message: values.isReturn
? t("parts_orders.successes.return_created")
: t("parts_orders.successes.created"),
});
insertAuditTrail({
jobid: jobId,
operation: isReturn
? AuditTrailMapping.jobspartsreturn(
insertResult.data.insert_parts_orders.returning[0].order_number
)
: AuditTrailMapping.jobspartsorder(
insertResult.data.insert_parts_orders.returning[0].order_number
),
const {treatments: {OEConnection_PriceChange}} = useSplitTreatments({
attributes: {},
names: ["OEConnection_PriceChange"],
splitKey: bodyshop.imexshopid,
});
const jobLinesResult = await updateJobLines({
variables: {
ids: values.parts_order_lines.data
.filter((item) => item.job_line_id)
.map((item) => item.job_line_id),
status: isReturn
? bodyshop.md_order_statuses.default_returned || "Returned*"
: is_quote
? bodyshop.md_order_statuses.default_quote || "Quote"
: bodyshop.md_order_statuses.default_ordered || "Ordered*",
},
const {visible, context, actions} = partsOrderModal;
const {
jobId,
linesToOrder,
isReturn,
vendorId,
returnFromBill,
invoiceNumber,
job,
} = context;
const {refetch} = actions;
const [form] = Form.useForm();
const [saving, setSaving] = useState(false);
const sendTypeState = useState("e");
const sendType = sendTypeState[0];
const {loading, error, data} = useQuery(QUERY_ALL_VENDORS_FOR_ORDER, {
skip: !visible,
variables: {jobId: jobId},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
if (!isReturn && removefrompartsqueue) {
await updateJob({
variables: {
jobId: jobId,
job: {
queued_for_parts: false,
},
},
});
}
const [insertPartOrder] = useMutation(INSERT_NEW_PARTS_ORDERS);
const [updateJobLines] = useMutation(UPDATE_JOB_LINE_STATUS);
const [updateJob] = useMutation(UPDATE_JOB);
if (!!jobLinesResult.errors) {
notification["error"]({
message: t("parts_orders.errors.creating"),
description: JSON.stringify(jobLinesResult.errors),
});
}
const handleFinish = async ({
order_type,
removefrompartsqueue,
is_quote,
...values
}) => {
logImEXEvent("parts_order_insert");
setSaving(true);
let insertResult;
if (values.vendorid === bodyshop.inhousevendorid) {
setBillEnterContext({
actions: { refetch: refetch },
context: {
disableInvNumber: true,
job: { id: jobId },
bill: {
vendorid: bodyshop.inhousevendorid,
invoice_number: "ih",
isinhouse: true,
date: new dayjs(),
total: 0,
billlines: values.parts_order_lines.data.map((p) => {
return {
joblineid: p.job_line_id,
actual_price: p.act_price,
actual_cost: 0, //p.act_price,
line_desc: p.line_desc,
line_remarks: p.line_remarks,
part_type: p.part_type,
quantity: p.quantity || 1,
applicable_taxes: {
local: false,
state: false,
federal: false,
},
};
}),
},
},
});
toggleModalVisible();
return;
}
if (refetch) refetch();
const Templates = TemplateList("partsorder", context);
if (sendType === "e") {
const matchingVendor = data.vendors.filter(
(item) => item.id === values.vendorid
)[0];
let vendorEmails =
matchingVendor &&
matchingVendor.email &&
matchingVendor.email.split(RegExp("[;,]"));
GenerateDocument(
{
name: isReturn
? Templates.parts_return_slip.key
: order_type === "parts_order"
? Templates.parts_order.key
: Templates.sublet_order.key,
variables: {
id: insertResult.data.insert_parts_orders.returning[0].id,
},
},
{
to: matchingVendor ? vendorEmails : null,
replyTo: bodyshop.email,
subject: isReturn
? Templates.parts_return_slip.subject
: order_type === "parts_order"
? Templates.parts_order.subject
: Templates.sublet_order.subject,
},
"e",
jobId
);
} else if (sendType === "p") {
GenerateDocument(
{
name: isReturn
? Templates.parts_return_slip.key
: order_type === "parts_order"
? Templates.parts_order.key
: Templates.sublet_order.key,
variables: {
id: insertResult.data.insert_parts_orders.returning[0].id,
},
},
{},
"p"
);
} else if (sendType === "oec") {
//Send to Partner OEC.
try {
const partsOrder = await client.query({
query: QUERY_PARTS_ORDER_OEC,
variables: {
id: insertResult.data.insert_parts_orders.returning[0].id,
},
});
let po;
//Massage the data based on the split. Should they be able to overwrite OEC pricing?
if (OEConnection_PriceChange.treatment === "on") {
//Set the flag to include the override.
po = _.cloneDeep(partsOrder.data.parts_orders_by_pk);
po.parts_order_lines.forEach((pol) => {
pol.priceChange = true;
});
}
const oecResponse = await axios.post(
"http://localhost:1337/oec/",
po || partsOrder.data.parts_orders_by_pk,
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
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*",
},
],
},
}
);
if (oecResponse.data && oecResponse.data.success === false) {
notification.open({
type: "error",
message: t("parts_orders.errors.oec", {
error: oecResponse.data.error,
}),
});
}
} catch (error) {
console.log("Error OEC.", error);
notification["error"]({
message: t("parts_orders.errors.oec", {
error: JSON.stringify(error.message),
}),
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"],
});
setSaving(false);
return;
}
}
setSaving(false);
toggleModalVisible();
};
const initialValues = {
jobid: jobId,
return: isReturn,
deliver_by: isReturn ? dayjs(new Date()) : null,
vendorid: vendorId,
returnfrombill: returnFromBill,
parts_order_lines: {
data: linesToOrder
? linesToOrder.reduce((acc, value) => {
acc.push({
line_desc: value.line_desc,
oem_partno: value.oem_partno,
db_price: value.db_price,
act_price: value.act_price,
cost: value.cost,
quantity: value.part_qty,
job_line_id: isReturn ? value.joblineid : value.id,
part_type: value.part_type,
...(isReturn && { cm_received: false }),
if (!!insertResult.errors) {
notification["error"]({
message: t("parts_orders.errors.creating"),
description: JSON.stringify(insertResult.errors),
});
return acc;
}, [])
: [],
},
};
return;
}
notification["success"]({
message: values.isReturn
? t("parts_orders.successes.return_created")
: t("parts_orders.successes.created"),
});
insertAuditTrail({
jobid: jobId,
operation: isReturn
? AuditTrailMapping.jobspartsreturn(
insertResult.data.insert_parts_orders.returning[0].order_number
)
: AuditTrailMapping.jobspartsorder(
insertResult.data.insert_parts_orders.returning[0].order_number
),
});
useEffect(() => {
if (visible && !!linesToOrder) {
form.resetFields();
}
}, [visible, linesToOrder, form]);
const jobLinesResult = await updateJobLines({
variables: {
ids: values.parts_order_lines.data
.filter((item) => item.job_line_id)
.map((item) => item.job_line_id),
status: isReturn
? bodyshop.md_order_statuses.default_returned || "Returned*"
: is_quote
? bodyshop.md_order_statuses.default_quote || "Quote"
: bodyshop.md_order_statuses.default_ordered || "Ordered*",
},
});
return (
<Modal
open={visible}
title={
isReturn
? `${t("parts_orders.labels.returnpartsorder")} ${invoiceNumber}`
: t("parts_orders.labels.newpartsorder")
}
onCancel={() => toggleModalVisible()}
onOk={() => form.submit()}
okButtonProps={{ loading: saving }}
cancelButtonProps={{ loading: saving }}
destroyOnClose
width="75%"
forceRender
>
{error ? <AlertComponent message={error.message} type="error" /> : null}
<Form
form={form}
layout="vertical"
autoComplete="no"
onFinish={handleFinish}
initialValues={initialValues}
>
{loading ? (
<LoadingSpinner />
) : (
<PartsOrderModalComponent
form={form}
vendorList={(data && data.vendors) || []}
sendTypeState={sendTypeState}
isReturn={isReturn}
preferredMake={data && data.jobs[0] && data.jobs[0].v_make_desc}
job={job}
/>
)}
</Form>
</Modal>
);
if (!isReturn && removefrompartsqueue) {
await updateJob({
variables: {
jobId: jobId,
job: {
queued_for_parts: false,
},
},
});
}
if (!!jobLinesResult.errors) {
notification["error"]({
message: t("parts_orders.errors.creating"),
description: JSON.stringify(jobLinesResult.errors),
});
}
if (values.vendorid === bodyshop.inhousevendorid) {
setBillEnterContext({
actions: {refetch: refetch},
context: {
disableInvNumber: true,
job: {id: jobId},
bill: {
vendorid: bodyshop.inhousevendorid,
invoice_number: "ih",
isinhouse: true,
date: new dayjs(),
total: 0,
billlines: values.parts_order_lines.data.map((p) => {
return {
joblineid: p.job_line_id,
actual_price: p.act_price,
actual_cost: 0, //p.act_price,
line_desc: p.line_desc,
line_remarks: p.line_remarks,
part_type: p.part_type,
quantity: p.quantity || 1,
applicable_taxes: {
local: false,
state: false,
federal: false,
},
};
}),
},
},
});
toggleModalVisible();
return;
}
if (refetch) refetch();
const Templates = TemplateList("partsorder", context);
if (sendType === "e") {
const matchingVendor = data.vendors.filter(
(item) => item.id === values.vendorid
)[0];
let vendorEmails =
matchingVendor &&
matchingVendor.email &&
matchingVendor.email.split(RegExp("[;,]"));
GenerateDocument(
{
name: isReturn
? Templates.parts_return_slip.key
: order_type === "parts_order"
? Templates.parts_order.key
: Templates.sublet_order.key,
variables: {
id: insertResult.data.insert_parts_orders.returning[0].id,
},
},
{
to: matchingVendor ? vendorEmails : null,
replyTo: bodyshop.email,
subject: isReturn
? Templates.parts_return_slip.subject
: order_type === "parts_order"
? Templates.parts_order.subject
: Templates.sublet_order.subject,
},
"e",
jobId
);
} else if (sendType === "p") {
GenerateDocument(
{
name: isReturn
? Templates.parts_return_slip.key
: order_type === "parts_order"
? Templates.parts_order.key
: Templates.sublet_order.key,
variables: {
id: insertResult.data.insert_parts_orders.returning[0].id,
},
},
{},
"p"
);
} else if (sendType === "oec") {
//Send to Partner OEC.
try {
const partsOrder = await client.query({
query: QUERY_PARTS_ORDER_OEC,
variables: {
id: insertResult.data.insert_parts_orders.returning[0].id,
},
});
let po;
//Massage the data based on the split. Should they be able to overwrite OEC pricing?
if (OEConnection_PriceChange.treatment === "on") {
//Set the flag to include the override.
po = _.cloneDeep(partsOrder.data.parts_orders_by_pk);
po.parts_order_lines.forEach((pol) => {
pol.priceChange = true;
});
}
const oecResponse = await axios.post(
"http://localhost:1337/oec/",
po || partsOrder.data.parts_orders_by_pk,
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
if (oecResponse.data && oecResponse.data.success === false) {
notification.open({
type: "error",
message: t("parts_orders.errors.oec", {
error: oecResponse.data.error,
}),
});
}
} catch (error) {
console.log("Error OEC.", error);
notification["error"]({
message: t("parts_orders.errors.oec", {
error: JSON.stringify(error.message),
}),
});
setSaving(false);
return;
}
}
setSaving(false);
toggleModalVisible();
};
const initialValues = {
jobid: jobId,
return: isReturn,
deliver_by: isReturn ? dayjs(new Date()) : null,
vendorid: vendorId,
returnfrombill: returnFromBill,
parts_order_lines: {
data: linesToOrder
? linesToOrder.reduce((acc, value) => {
acc.push({
line_desc: value.line_desc,
oem_partno: value.oem_partno,
db_price: value.db_price,
act_price: value.act_price,
cost: value.cost,
quantity: value.part_qty,
job_line_id: isReturn ? value.joblineid : value.id,
part_type: value.part_type,
...(isReturn && {cm_received: false}),
});
return acc;
}, [])
: [],
},
};
useEffect(() => {
if (visible && !!linesToOrder) {
form.resetFields();
}
}, [visible, linesToOrder, form]);
return (
<Modal
open={visible}
title={
isReturn
? `${t("parts_orders.labels.returnpartsorder")} ${invoiceNumber}`
: t("parts_orders.labels.newpartsorder")
}
onCancel={() => toggleModalVisible()}
onOk={() => form.submit()}
okButtonProps={{loading: saving}}
cancelButtonProps={{loading: saving}}
destroyOnClose
width="75%"
forceRender
>
{error ? <AlertComponent message={error.message} type="error"/> : null}
<Form
form={form}
layout="vertical"
autoComplete="no"
onFinish={handleFinish}
initialValues={initialValues}
>
{loading ? (
<LoadingSpinner/>
) : (
<PartsOrderModalComponent
form={form}
vendorList={(data && data.vendors) || []}
sendTypeState={sendTypeState}
isReturn={isReturn}
preferredMake={data && data.jobs[0] && data.jobs[0].v_make_desc}
job={job}
/>
)}
</Form>
</Modal>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(PartsOrderModalContainer);