import { useMutation, useQuery, useApolloClient } from "@apollo/client"; import { Form, Modal } 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 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 { useSplitTreatments } from "@splitsoftware/splitio-react"; import _ from "lodash"; import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; const mapStateToProps = createStructuredSelector({ 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, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); export function PartsOrderModalContainer({ partsOrderModal, toggleModalVisible, currentUser, bodyshop, setEmailOptions, setBillEnterContext, insertAuditTrail }) { const { t } = useTranslation(); const client = useApolloClient(); const notification = useNotification(); const { treatments: { OEConnection_PriceChange } } = useSplitTreatments({ attributes: {}, names: ["OEConnection_PriceChange"], splitKey: bodyshop.imexshopid }); const { open, 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: !open, 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), type: isReturn ? "jobspartsreturn" : "jobspartsorder" }); 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*" } }); 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: 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, notification ); } 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", null, notification ); } 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 (open && !!linesToOrder) { form.resetFields(); } }, [open, linesToOrder, form]); return ( toggleModalVisible()} onOk={() => form.submit()} okButtonProps={{ loading: saving }} cancelButtonProps={{ loading: saving }} destroyOnClose width="75%" forceRender > {error ? : null}
{loading ? ( ) : ( )}
); } export default connect(mapStateToProps, mapDispatchToProps)(PartsOrderModalContainer);