diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index f2c3b671a..45256e2f5 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -11149,6 +11149,27 @@ + + dateinpast + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + dlexpirebeforereturn false @@ -12370,6 +12391,27 @@ + + leasereturn + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + out false @@ -14735,6 +14777,27 @@ + + vacationadded + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + @@ -32699,6 +32762,27 @@ + + saving + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + selectexistingornew false @@ -33103,6 +33187,27 @@ + + tax_number + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + @@ -41346,6 +41451,27 @@ + + efficiencyoverperiod + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + jobs false diff --git a/client/src/App/App.styles.scss b/client/src/App/App.styles.scss index e7243044d..e8389290f 100644 --- a/client/src/App/App.styles.scss +++ b/client/src/App/App.styles.scss @@ -142,3 +142,9 @@ } } } + + +//Update row highlighting on production board. +.ant-table-tbody > tr.ant-table-row:hover > td { + background: #eaeaea !important; +} \ No newline at end of file diff --git a/client/src/components/bill-detail-edit/bill-detail-edit-component.jsx b/client/src/components/bill-detail-edit/bill-detail-edit-component.jsx new file mode 100644 index 000000000..e92bb175b --- /dev/null +++ b/client/src/components/bill-detail-edit/bill-detail-edit-component.jsx @@ -0,0 +1,251 @@ +import { useMutation, useQuery } from "@apollo/client"; +import { Button, Form, PageHeader, Popconfirm, Space } from "antd"; +import moment from "moment"; +import queryString from "query-string"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { + DELETE_BILL_LINE, + INSERT_NEW_BILL_LINES, + UPDATE_BILL_LINE +} from "../../graphql/bill-lines.queries"; +import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import AuditTrailMapping from "../../utils/AuditTrailMappings"; +import AlertComponent from "../alert/alert.component"; +import BillFormContainer from "../bill-form/bill-form.container"; +import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component"; +import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-button.component"; +import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-gallery.container"; +import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container"; +import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; +import BillDetailEditReturn from "./bill-detail-edit-return.component"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); +const mapDispatchToProps = (dispatch) => ({ + setPartsOrderContext: (context) => + dispatch(setModalContext({ context: context, modal: "partsOrder" })), + insertAuditTrail: ({ jobid, operation }) => + dispatch(insertAuditTrail({ jobid, operation })), +}); + +export default connect( + mapStateToProps, + mapDispatchToProps +)(BillDetailEditcontainer); + +export function BillDetailEditcontainer({ + setPartsOrderContext, + insertAuditTrail, + bodyshop, +}) { + const search = queryString.parse(useLocation().search); + + const { t } = useTranslation(); + const [form] = Form.useForm(); + const [visible, setVisible] = useState(false); + const [updateLoading, setUpdateLoading] = useState(false); + const [update_bill] = useMutation(UPDATE_BILL); + const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES); + const [updateBillLine] = useMutation(UPDATE_BILL_LINE); + const [deleteBillLine] = useMutation(DELETE_BILL_LINE); + + const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, { + variables: { billid: search.billid }, + skip: !!!search.billid, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + }); + + const handleSave = () => { + //It's got a previously deducted bill line! + if ( + data.bills_by_pk.billlines.filter((b) => b.deductedfromlbr).length > 0 || + form.getFieldValue("billlines").filter((b) => b.deductedfromlbr).length > + 0 + ) + setVisible(true); + else { + form.submit(); + } + }; + + const handleFinish = async (values) => { + setUpdateLoading(true); + //let adjustmentsToInsert = {}; + + const { billlines, upload, ...bill } = values; + const updates = []; + updates.push( + update_bill({ + variables: { billId: search.billid, bill: bill }, + }) + ); + + billlines.forEach((l) => { + delete l.selected; + }); + + //Find bill lines that were deleted. + const deletedJobLines = []; + + data.bills_by_pk.billlines.forEach((a) => { + const matchingRecord = billlines.find((b) => b.id === a.id); + if (!matchingRecord) { + deletedJobLines.push(a); + } + }); + + deletedJobLines.forEach((d) => { + updates.push(deleteBillLine({ variables: { id: d.id } })); + }); + + billlines.forEach((billline) => { + const { deductedfromlbr, inventories, jobline, ...il } = billline; + delete il.__typename; + + if (il.id) { + updates.push( + updateBillLine({ + variables: { + billLineId: il.id, + billLine: { + ...il, + deductedfromlbr: deductedfromlbr, + joblineid: il.joblineid === "noline" ? null : il.joblineid, + }, + }, + }) + ); + } else { + //It's a new line, have to insert it. + updates.push( + insertBillLine({ + variables: { + billLines: [ + { + ...il, + deductedfromlbr: deductedfromlbr, + billid: search.billid, + joblineid: il.joblineid === "noline" ? null : il.joblineid, + }, + ], + }, + }) + ); + } + }); + + await Promise.all(updates); + + insertAuditTrail({ + jobid: bill.jobid, + billid: search.billid, + operation: AuditTrailMapping.billupdated(bill.invoice_number), + }); + + await refetch(); + form.setFieldsValue(transformData(data)); + form.resetFields(); + setVisible(false); + setUpdateLoading(false); + }; + + if (error) return ; + if (!search.billid) return <>; //
{t("bills.labels.noneselected")}
; + + const exported = data && data.bills_by_pk && data.bills_by_pk.exported; + + return ( + <> + {loading && } + {data && ( + <> + + + + form.submit()} + onCancel={() => setVisible(false)} + okButtonProps={{ loading: updateLoading }} + title={t("bills.labels.editadjwarning")} + > + + + + + + } + /> +
+ + + {bodyshop.uselocalmediaserver ? ( + + ) : ( + + )} + + + )} + + ); +} + +const transformData = (data) => { + return data + ? { + ...data.bills_by_pk, + + billlines: data.bills_by_pk.billlines.map((i) => { + return { + ...i, + joblineid: !!i.joblineid ? i.joblineid : "noline", + applicable_taxes: { + federal: + (i.applicable_taxes && i.applicable_taxes.federal) || false, + state: (i.applicable_taxes && i.applicable_taxes.state) || false, + local: (i.applicable_taxes && i.applicable_taxes.local) || false, + }, + }; + }), + date: data.bills_by_pk ? moment(data.bills_by_pk.date) : null, + } + : {}; +}; diff --git a/client/src/components/bill-detail-edit/bill-detail-edit-return.component.jsx b/client/src/components/bill-detail-edit/bill-detail-edit-return.component.jsx new file mode 100644 index 000000000..99f4cb53b --- /dev/null +++ b/client/src/components/bill-detail-edit/bill-detail-edit-return.component.jsx @@ -0,0 +1,172 @@ +import { Button, Checkbox, Form, Modal } from "antd"; +import queryString from "query-string"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useHistory, useLocation } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); +const mapDispatchToProps = (dispatch) => ({ + setPartsOrderContext: (context) => + dispatch(setModalContext({ context: context, modal: "partsOrder" })), + insertAuditTrail: ({ jobid, operation }) => + dispatch(insertAuditTrail({ jobid, operation })), +}); + +export default connect( + mapStateToProps, + mapDispatchToProps +)(BillDetailEditReturn); + +export function BillDetailEditReturn({ + setPartsOrderContext, + insertAuditTrail, + bodyshop, + data, + disabled, +}) { + const search = queryString.parse(useLocation().search); + const history = useHistory(); + const { t } = useTranslation(); + const [form] = Form.useForm(); + const [visible, setVisible] = useState(false); + + const handleFinish = ({ billlines }) => { + const selectedLines = billlines.filter((l) => l.selected).map((l) => l.id); + + setPartsOrderContext({ + actions: {}, + context: { + jobId: data.bills_by_pk.jobid, + vendorId: data.bills_by_pk.vendorid, + returnFromBill: data.bills_by_pk.id, + invoiceNumber: data.bills_by_pk.invoice_number, + linesToOrder: data.bills_by_pk.billlines + .filter((l) => selectedLines.includes(l.id)) + .map((i) => { + return { + line_desc: i.line_desc, + // db_price: i.actual_price, + act_price: i.actual_price, + cost: i.actual_cost, + quantity: i.quantity, + joblineid: i.joblineid, + oem_partno: i.jobline && i.jobline.oem_partno, + part_type: i.jobline && i.jobline.part_type, + }; + }), + isReturn: true, + }, + }); + delete search.billid; + + history.push({ search: queryString.stringify(search) }); + setVisible(false); + }; + useEffect(() => { + if (visible === false) form.resetFields(); + }, [visible, form]); + + return ( + <> + setVisible(false)} + destroyOnClose + title={t("bills.actions.return")} + onOk={() => form.submit()} + > +
+ + {(fields, { add, remove, move }) => { + return ( + + + + + + + + + + + + {fields.map((field, index) => ( + + + + + + + + ))} + +
{t("billlines.fields.line_desc")}{t("billlines.fields.quantity")}{t("billlines.fields.actual_price")}{t("billlines.fields.actual_cost")}
+ + + + + + + + + + + + + + + + + + + +
+ ); + }} +
+
+
+ + + ); +} diff --git a/client/src/components/bill-detail-edit/bill-detail-edit.container.jsx b/client/src/components/bill-detail-edit/bill-detail-edit.container.jsx index b9bbb791b..4df0959c7 100644 --- a/client/src/components/bill-detail-edit/bill-detail-edit.container.jsx +++ b/client/src/components/bill-detail-edit/bill-detail-edit.container.jsx @@ -1,68 +1,12 @@ -import { useMutation, useQuery } from "@apollo/client"; -import { - Button, - Drawer, - Form, - Grid, - PageHeader, - Popconfirm, - Space, -} from "antd"; -import moment from "moment"; +import { Drawer, Grid } from "antd"; import queryString from "query-string"; -import React, { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { connect } from "react-redux"; +import React from "react"; import { useHistory, useLocation } from "react-router-dom"; -import { createStructuredSelector } from "reselect"; -import { - DELETE_BILL_LINE, - INSERT_NEW_BILL_LINES, - UPDATE_BILL_LINE, -} from "../../graphql/bill-lines.queries"; -import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries"; -import { insertAuditTrail } from "../../redux/application/application.actions"; -import { setModalContext } from "../../redux/modals/modals.actions"; -import { selectBodyshop } from "../../redux/user/user.selectors"; -import AuditTrailMapping from "../../utils/AuditTrailMappings"; -import AlertComponent from "../alert/alert.component"; -import BillFormContainer from "../bill-form/bill-form.container"; -import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component"; -import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-button.component"; -import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-gallery.container"; -import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container"; -import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; +import BillDetailEditComponent from "./bill-detail-edit-component"; -const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, -}); -const mapDispatchToProps = (dispatch) => ({ - setPartsOrderContext: (context) => - dispatch(setModalContext({ context: context, modal: "partsOrder" })), - insertAuditTrail: ({ jobid, operation }) => - dispatch(insertAuditTrail({ jobid, operation })), -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(BillDetailEditcontainer); - -export function BillDetailEditcontainer({ - setPartsOrderContext, - insertAuditTrail, - bodyshop, -}) { +export default function BillDetailEditcontainer() { const search = queryString.parse(useLocation().search); const history = useHistory(); - const { t } = useTranslation(); - const [form] = Form.useForm(); - const [visible, setVisible] = useState(false); - const [updateLoading, setUpdateLoading] = useState(false); - const [update_bill] = useMutation(UPDATE_BILL); - const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES); - const [updateBillLine] = useMutation(UPDATE_BILL_LINE); - const [deleteBillLine] = useMutation(DELETE_BILL_LINE); const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) .filter((screen) => !!screen[1]) @@ -80,114 +24,6 @@ export function BillDetailEditcontainer({ ? bpoints[selectedBreakpoint[0]] : "100%"; - const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, { - variables: { billid: search.billid }, - skip: !!!search.billid, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); - - const handleSave = () => { - //It's got a previously deducted bill line! - if ( - data.bills_by_pk.billlines.filter((b) => b.deductedfromlbr).length > 0 || - form.getFieldValue("billlines").filter((b) => b.deductedfromlbr).length > - 0 - ) - setVisible(true); - else { - form.submit(); - } - }; - - const handleFinish = async (values) => { - setUpdateLoading(true); - //let adjustmentsToInsert = {}; - - const { billlines, upload, ...bill } = values; - const updates = []; - updates.push( - update_bill({ - variables: { billId: search.billid, bill: bill }, - }) - ); - - //Find bill lines that were deleted. - const deletedJobLines = []; - - data.bills_by_pk.billlines.forEach((a) => { - const matchingRecord = billlines.find((b) => b.id === a.id); - if (!matchingRecord) { - deletedJobLines.push(a); - } - }); - - deletedJobLines.forEach((d) => { - updates.push(deleteBillLine({ variables: { id: d.id } })); - }); - - billlines.forEach((billline) => { - const { deductedfromlbr, inventories, jobline, ...il } = billline; - delete il.__typename; - - if (il.id) { - updates.push( - updateBillLine({ - variables: { - billLineId: il.id, - billLine: { - ...il, - deductedfromlbr: deductedfromlbr, - joblineid: il.joblineid === "noline" ? null : il.joblineid, - }, - }, - }) - ); - } else { - //It's a new line, have to insert it. - updates.push( - insertBillLine({ - variables: { - billLines: [ - { - ...il, - deductedfromlbr: deductedfromlbr, - billid: search.billid, - joblineid: il.joblineid === "noline" ? null : il.joblineid, - }, - ], - }, - }) - ); - } - }); - - await Promise.all(updates); - - insertAuditTrail({ - jobid: bill.jobid, - billid: search.billid, - operation: AuditTrailMapping.billupdated(bill.invoice_number), - }); - - await refetch(); - form.setFieldsValue(transformData(data)); - form.resetFields(); - setVisible(false); - setUpdateLoading(false); - }; - - useEffect(() => { - if (search.billid && data) { - form.resetFields(); - } - }, [form, search.billid, data]); - - if (error) return ; - if (!search.billid) return <>; //
{t("bills.labels.noneselected")}
; - - const exported = data && data.bills_by_pk && data.bills_by_pk.exported; - return ( - {loading && } - {!loading && ( - <> - - - - form.submit()} - onCancel={() => setVisible(false)} - okButtonProps={{ loading: updateLoading }} - title={t("bills.labels.editadjwarning")} - > - - - - - - } - /> -
- - - {bodyshop.uselocalmediaserver ? ( - - ) : ( - - )} - - - )} +
); } - -const transformData = (data) => { - return data - ? { - ...data.bills_by_pk, - - billlines: data.bills_by_pk.billlines.map((i) => { - return { - ...i, - joblineid: !!i.joblineid ? i.joblineid : "noline", - applicable_taxes: { - federal: - (i.applicable_taxes && i.applicable_taxes.federal) || false, - state: (i.applicable_taxes && i.applicable_taxes.state) || false, - local: (i.applicable_taxes && i.applicable_taxes.local) || false, - }, - }; - }), - date: data.bills_by_pk ? moment(data.bills_by_pk.date) : null, - } - : {}; -}; diff --git a/client/src/components/bill-form/bill-form.lines.component.jsx b/client/src/components/bill-form/bill-form.lines.component.jsx index d884b61d8..c41e60369 100644 --- a/client/src/components/bill-form/bill-form.lines.component.jsx +++ b/client/src/components/bill-form/bill-form.lines.component.jsx @@ -1,14 +1,14 @@ import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons"; +import { useTreatments } from "@splitsoftware/splitio-react"; import { - Button, - Form, + Button, Form, Input, InputNumber, Select, Space, Switch, Table, - Tooltip, + Tooltip } from "antd"; import React from "react"; import { useTranslation } from "react-i18next"; @@ -17,9 +17,8 @@ import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; import CiecaSelect from "../../utils/Ciecaselect"; import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component"; -import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component"; -import { useTreatments } from "@splitsoftware/splitio-react"; +import CurrencyInput from "../form-items-formatted/currency-form-item.component"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser diff --git a/client/src/components/bills-list-table/bills-list-table.component.jsx b/client/src/components/bills-list-table/bills-list-table.component.jsx index 5c7553fd5..8257c0b39 100644 --- a/client/src/components/bills-list-table/bills-list-table.component.jsx +++ b/client/src/components/bills-list-table/bills-list-table.component.jsx @@ -12,6 +12,7 @@ import { DateFormatter } from "../../utils/DateFormatter"; import { alphaSort, dateSort } from "../../utils/sorters"; import { TemplateList } from "../../utils/TemplateConstants"; import BillDeleteButton from "../bill-delete-button/bill-delete-button.component"; +import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-return.component"; import PrintWrapperComponent from "../print-wrapper/print-wrapper.component"; const mapStateToProps = createStructuredSelector({ @@ -58,6 +59,14 @@ export function BillsListTableComponent({ )} +