import { useApolloClient, useMutation } from "@apollo/client"; import { Button, Checkbox, Form, Modal, notification, Space } from "antd"; import _ from "lodash"; import React, { useEffect, useState, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { INSERT_NEW_BILL } from "../../graphql/bills.queries"; import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries"; import { QUERY_JOB_LBR_ADJUSTMENTS, UPDATE_JOB, } from "../../graphql/jobs.queries"; import { MUTATION_MARK_RETURN_RECEIVED } from "../../graphql/parts-orders.queries"; import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries"; import { insertAuditTrail } from "../../redux/application/application.actions"; import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { selectBillEnterModal } from "../../redux/modals/modals.selectors"; import { selectBodyshop, selectCurrentUser, } from "../../redux/user/user.selectors"; import confirmDialog from "../../utils/asyncConfirm"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; import BillFormContainer from "../bill-form/bill-form.container"; import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility"; import { handleUpload } from "../documents-upload/documents-upload.utility"; import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility"; import useLocalStorage from "../../utils/useLocalStorage"; import { GenerateDocument } from "../../utils/RenderTemplate"; import { TemplateList } from "../../utils/TemplateConstants"; const mapStateToProps = createStructuredSelector({ billEnterModal: selectBillEnterModal, bodyshop: selectBodyshop, currentUser: selectCurrentUser, }); const mapDispatchToProps = (dispatch) => ({ toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")), insertAuditTrail: ({ jobid, operation }) => dispatch(insertAuditTrail({ jobid, operation })), }); const Templates = TemplateList("job_special"); function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop, currentUser, insertAuditTrail, }) { const [form] = Form.useForm(); const { t } = useTranslation(); const [enterAgain, setEnterAgain] = useState(false); const [insertBill] = useMutation(INSERT_NEW_BILL); const [updateJobLines] = useMutation(UPDATE_JOB_LINE); const [updatePartsOrderLines] = useMutation(MUTATION_MARK_RETURN_RECEIVED); const [updateInventoryLines] = useMutation(UPDATE_INVENTORY_LINES); const [loading, setLoading] = useState(false); const client = useApolloClient(); const [generateLabel, setGenerateLabel] = useLocalStorage( "enter_bill_generate_label", false ); const formValues = useMemo(() => { return { ...billEnterModal.context.bill, jobid: (billEnterModal.context.job && billEnterModal.context.job.id) || null, federal_tax_rate: (bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.federal_tax_rate) || 0, state_tax_rate: (bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.state_tax_rate) || 0, local_tax_rate: (bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.local_tax_rate) || 0, }; }, [billEnterModal, bodyshop]); const handleFinish = async (values) => { let totals = CalculateBillTotal(values); if (totals.discrepancy.getAmount() !== 0) { if (!(await confirmDialog(t("bills.labels.savewithdiscrepancy")))) { return; } } setLoading(true); const { upload, location, outstanding_returns, inventory, ...remainingValues } = values; let adjustmentsToInsert = {}; const r1 = await insertBill({ variables: { bill: [ { ...remainingValues, billlines: { data: remainingValues.billlines && remainingValues.billlines.map((i) => { const { deductedfromlbr, lbr_adjustment, location: lineLocation, part_type, ...restI } = i; if (deductedfromlbr) { adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] = (adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] || 0) - restI.actual_price / lbr_adjustment.rate; } return { ...restI, deductedfromlbr: deductedfromlbr, lbr_adjustment, joblineid: i.joblineid === "noline" ? null : i.joblineid, }; }), }, }, ], }, refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"], }); const adjKeys = Object.keys(adjustmentsToInsert); if (adjKeys.length > 0) { //Query the adjustments, merge, and update them. const existingAdjustments = await client.query({ query: QUERY_JOB_LBR_ADJUSTMENTS, variables: { id: values.jobid, }, }); const newAdjustments = _.cloneDeep( existingAdjustments.data.jobs_by_pk.lbr_adjustments ); adjKeys.forEach((key) => { newAdjustments[key] = (newAdjustments[key] || 0) + adjustmentsToInsert[key]; insertAuditTrail({ jobid: values.jobid, operation: AuditTrailMapping.jobmodifylbradj({ mod_lbr_ty: key, hours: adjustmentsToInsert[key].toFixed(1), }), }); }); const jobUpdate = client.mutate({ mutation: UPDATE_JOB, variables: { jobId: values.jobid, job: { lbr_adjustments: newAdjustments }, }, }); if (!!jobUpdate.errors) { notification["error"]({ message: t("jobs.errors.saving", { message: JSON.stringify(jobUpdate.errors), }), }); return; } } const markPolReceived = outstanding_returns && outstanding_returns.filter((o) => o.cm_received === true); if (markPolReceived && markPolReceived.length > 0) { const r2 = await updatePartsOrderLines({ variables: { partsLineIds: markPolReceived.map((p) => p.id) }, }); if (!!r2.errors) { setLoading(false); setEnterAgain(false); notification["error"]({ message: t("parts_orders.errors.updating", { message: JSON.stringify(r2.errors), }), }); } } if (!!r1.errors) { setLoading(false); setEnterAgain(false); notification["error"]({ message: t("bills.errors.creating", { message: JSON.stringify(r1.errors), }), }); } const billId = r1.data.insert_bills.returning[0].id; const markInventoryConsumed = inventory && inventory.filter((i) => i.consumefrominventory); if (markInventoryConsumed && markInventoryConsumed.length > 0) { const r2 = await updateInventoryLines({ variables: { InventoryIds: markInventoryConsumed.map((p) => p.id), consumedbybillid: billId, }, }); if (!!r2.errors) { setLoading(false); setEnterAgain(false); notification["error"]({ message: t("inventory.errors.updating", { message: JSON.stringify(r2.errors), }), }); } } //If it's not a credit memo, update the statuses. if (!values.is_credit_memo) { await Promise.all( remainingValues.billlines .filter((il) => il.joblineid !== "noline") .map((li) => { return updateJobLines({ variables: { lineId: li.joblineid, line: { location: li.location || location, status: bodyshop.md_order_statuses.default_received || "Received*", }, }, }); }) ); } ///////////////////////// if (upload && upload.length > 0) { //insert Each of the documents? if (bodyshop.uselocalmediaserver) { upload.forEach((u) => { handleLocalUpload({ ev: { file: u.originFileObj }, context: { jobid: values.jobid, invoice_number: remainingValues.invoice_number, vendorid: remainingValues.vendorid, }, }); }); } else { upload.forEach((u) => { handleUpload( { file: u.originFileObj }, { bodyshop: bodyshop, uploaded_by: currentUser.email, jobId: values.jobid, billId: billId, tagsArray: null, callback: null, } ); }); } } /////////////////////////// setLoading(false); notification["success"]({ message: t("bills.successes.created"), }); if (generateLabel) { GenerateDocument( { name: Templates.parts_invoice_label_single.key, variables: { id: billId, }, }, {}, "p" ); } if (billEnterModal.actions.refetch) billEnterModal.actions.refetch(); insertAuditTrail({ jobid: values.jobid, billid: billId, operation: AuditTrailMapping.billposted(remainingValues.invoice_number), }); if (enterAgain) { form.resetFields(); form.resetFields(); form.setFieldsValue({ ...formValues, billlines: [], }); } else { toggleModalVisible(); } setEnterAgain(false); }; const handleCancel = () => { const r = window.confirm(t("general.labels.cancel")); if (r === true) { toggleModalVisible(); } }; useEffect(() => { if (enterAgain) form.submit(); }, [enterAgain, form]); useEffect(() => { if (billEnterModal.visible) { form.setFieldsValue(formValues); } else { form.resetFields(); } }, [billEnterModal.visible, form, formValues]); return ( form.submit()} onCancel={handleCancel} afterClose={() => { form.resetFields(); setLoading(false); }} footer={ setGenerateLabel(e.target.checked)} > {t("bills.labels.generatepartslabel")} {billEnterModal.context && billEnterModal.context.id ? null : ( )} } destroyOnClose >
{ setEnterAgain(false); }} >
); } export default connect( mapStateToProps, mapDispatchToProps )(BillEnterModalContainer);