diff --git a/client/src/components/invoice-detail-edit/invoice-detail-edit.component.jsx b/client/src/components/invoice-detail-edit/invoice-detail-edit.component.jsx deleted file mode 100644 index 2f4d49c73..000000000 --- a/client/src/components/invoice-detail-edit/invoice-detail-edit.component.jsx +++ /dev/null @@ -1,136 +0,0 @@ -import { DatePicker, Form, Input, Switch, Tag } from "antd"; -import React, { useState } from "react"; -import { useTranslation } from "react-i18next"; -import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import DocumentsUploadContainer from "../documents-upload/documents-upload.container"; -import CurrencyInput from "../form-items-formatted/currency-form-item.component"; -import InvoiceEnterModalLinesComponent from "../invoice-enter-modal/invoice-enter-modal.lines.component"; -import JobSearchSelect from "../job-search-select/job-search-select.component"; - -export default function InvoiceDetailEditComponent({ - form, - roAutoCompleteOptions, - loadLines, - lineData, - responsibilityCenters, -}) { - const { t } = useTranslation(); - const [amounts, setAmounts] = useState({ invoiceTotal: 0, enteredAmount: 0 }); - const { getFieldsValue } = form; - - const calculateTotals = () => { - setAmounts({ - invoiceTotal: getFieldsValue().total || 0, - enteredTotal: getFieldsValue("invoicelines").invoicelines - ? getFieldsValue("invoicelines").invoicelines.reduce( - (acc, value) => - acc + (value && value.actual_cost ? value.actual_cost : 0), - 0 - ) - : 0, - }); - }; - - return ( -
-
- - { - if (form.getFieldValue("jobid") !== null) { - //loadLines({ variables: { id: form.getFieldValue("jobid") } }); - } - }} - /> - -
-
- - - - - - - - - - - - -
- - - - - - {t("invoicelines.labels.entered")} - {amounts.enteredTotal || 0} - - {amounts.invoiceTotal - amounts.enteredTotal === 0 ? ( - {t("invoicelines.labels.reconciled")} - ) : ( - - {t("invoicelines.labels.unreconciled")}: - - {amounts.invoiceTotal - amounts.enteredTotal} - - - )} - -
- ); -} diff --git a/client/src/components/invoice-detail-edit/invoice-detail-edit.container.jsx b/client/src/components/invoice-detail-edit/invoice-detail-edit.container.jsx index 017e7fdfb..1799e23fc 100644 --- a/client/src/components/invoice-detail-edit/invoice-detail-edit.container.jsx +++ b/client/src/components/invoice-detail-edit/invoice-detail-edit.container.jsx @@ -8,12 +8,10 @@ import { connect } from "react-redux"; import { useLocation } from "react-router-dom"; import { createStructuredSelector } from "reselect"; import { QUERY_INVOICE_BY_PK } from "../../graphql/invoices.queries"; -import { GET_JOB_LINES_TO_ENTER_INVOICE } from "../../graphql/jobs-lines.queries"; -import { ACTIVE_JOBS_FOR_AUTOCOMPLETE } from "../../graphql/jobs.queries"; import { selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; +import InvoiceFormContainer from "../invoice-form/invoice-form.container"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; -import InvoiceDetailEditComponent from "./invoice-detail-edit.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -29,35 +27,19 @@ export function InvoiceDetailEditContainer({ bodyshop }) { skip: !!!search.invoiceid, }); - const { data: RoAutoCompleteData } = useQuery(ACTIVE_JOBS_FOR_AUTOCOMPLETE, { - fetchPolicy: "network-only", - variables: { statuses: bodyshop.md_ro_statuses.open_statuses || ["Open"] }, - }); - - const { - loading: linesLoading, - data: lineData, - refetch: loadLines, - } = useQuery(GET_JOB_LINES_TO_ENTER_INVOICE, { - variables: { id: data && data.invoices_by_pk.jobid }, - fetchPolicy: "network-only", - skip: !!!(data && data.invoices_by_pk.id), - }); - const handleFinish = (values) => { console.log("values", values); }; useEffect(() => { - // if (data) { - // loadLines(); - // if (lineData) //form.resetFields(); - // } - }, [data, lineData]); + if (search.invoiceid) { + form.resetFields(); + } + }, [form, search.invoiceid]); if (error) return ; return ( - +
- +
); diff --git a/client/src/components/invoice-enter-modal/invoice-enter-modal.container.jsx b/client/src/components/invoice-enter-modal/invoice-enter-modal.container.jsx index 79b3fe0a3..bb8b7ab8c 100644 --- a/client/src/components/invoice-enter-modal/invoice-enter-modal.container.jsx +++ b/client/src/components/invoice-enter-modal/invoice-enter-modal.container.jsx @@ -1,61 +1,27 @@ -import { useLazyQuery, useMutation, useQuery } from "@apollo/react-hooks"; -import { Form, Modal, notification, Button } from "antd"; -import React, { useState, useEffect } from "react"; +import { useMutation } from "@apollo/react-hooks"; +import { Button, Form, Modal, notification } from "antd"; +import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { INSERT_NEW_INVOICE } from "../../graphql/invoices.queries"; -import { GET_JOB_LINES_TO_ENTER_INVOICE } from "../../graphql/jobs-lines.queries"; -import { ACTIVE_JOBS_FOR_AUTOCOMPLETE } from "../../graphql/jobs.queries"; -import { SEARCH_VENDOR_AUTOCOMPLETE } from "../../graphql/vendors.queries"; import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { selectInvoiceEnterModal } from "../../redux/modals/modals.selectors"; -import { selectBodyshop } from "../../redux/user/user.selectors"; -import InvoiceEnterModalComponent from "./invoice-enter-modal.component"; -import { setModalContext } from "../../redux/modals/modals.actions"; +import InvoiceFormContainer from "../invoice-form/invoice-form.container"; const mapStateToProps = createStructuredSelector({ invoiceEnterModal: selectInvoiceEnterModal, - bodyshop: selectBodyshop, }); const mapDispatchToProps = (dispatch) => ({ toggleModalVisible: () => dispatch(toggleModalVisible("invoiceEnter")), - setInvoiceEnterContext: (context) => - dispatch(setModalContext({ context: context, modal: "invoiceEnter" })), }); -function InvoiceEnterModalContainer({ - invoiceEnterModal, - toggleModalVisible, - bodyshop, - setInvoiceEnterContext, -}) { +function InvoiceEnterModalContainer({ invoiceEnterModal, toggleModalVisible }) { const [form] = Form.useForm(); const { t } = useTranslation(); const [enterAgain, setEnterAgain] = useState(false); const [insertInvoice] = useMutation(INSERT_NEW_INVOICE); - const { data: RoAutoCompleteData } = useQuery(ACTIVE_JOBS_FOR_AUTOCOMPLETE, { - fetchPolicy: "network-only", - variables: { statuses: bodyshop.md_ro_statuses.open_statuses || ["Open"] }, - skip: !invoiceEnterModal.visible, - }); - - const { data: VendorAutoCompleteData } = useQuery( - SEARCH_VENDOR_AUTOCOMPLETE, - { - fetchPolicy: "network-only", - skip: !invoiceEnterModal.visible, - } - ); - - const [loadLines, { data: lineData }] = useLazyQuery( - GET_JOB_LINES_TO_ENTER_INVOICE, - { - fetchPolicy: "network-only", - } - ); - const handleFinish = (values) => { insertInvoice({ variables: { @@ -107,11 +73,7 @@ function InvoiceEnterModalContainer({ return ( - + ); diff --git a/client/src/components/invoice-enter-modal/invoice-enter-modal.lines.component.jsx b/client/src/components/invoice-enter-modal/invoice-enter-modal.lines.component.jsx deleted file mode 100644 index 7fc1de42c..000000000 --- a/client/src/components/invoice-enter-modal/invoice-enter-modal.lines.component.jsx +++ /dev/null @@ -1,172 +0,0 @@ -import { DeleteFilled } from "@ant-design/icons"; -import { Button, Form, Input, Select } from "antd"; -import React from "react"; -import { useTranslation } from "react-i18next"; -import CurrencyInput from "../form-items-formatted/currency-form-item.component"; -import InvoiceLineSearchSelect from "../invoice-line-search-select/invoice-line-search-select.component"; - -export default function InvoiceEnterModalLinesComponent({ - lineData, - discount, - form, - responsibilityCenters, - calculateTotals, -}) { - const { t } = useTranslation(); - const { setFieldsValue, getFieldsValue } = form; - console.log("calculateTotals", calculateTotals); - return ( -
- - {(fields, { add, remove }) => { - console.log("fields", fields); - return ( -
- {fields.map((field, index) => ( - -
- - { - setFieldsValue({ - invoicelines: getFieldsValue([ - "invoicelines", - ]).invoicelines.map((item, idx) => { - if (idx === index) { - return { - ...item, - line_desc: opt.line_desc, - actual_price: opt.cost, - cost_center: opt.part_type - ? responsibilityCenters.defaults[ - opt.part_type - ] || null - : null, - }; - } - return item; - }), - }); - }} - /> - - - - - - - - { - setFieldsValue({ - invoicelines: getFieldsValue( - "invoicelines" - ).invoicelines.map((item, idx) => { - if (idx === index) { - return { - ...item, - actual_cost: !!item.actual_cost - ? item.actual_cost - : parseFloat(e.target.value) * - (1 - discount), - }; - } - return item; - }), - }); - }} - /> - - - { - calculateTotals(); - }} - /> - - - - - { - remove(field.name); - calculateTotals(); - }} - /> -
-
- ))} - - - -
- ); - }} -
-
- ); -} diff --git a/client/src/components/invoice-enter-modal/invoice-enter-modal.component.jsx b/client/src/components/invoice-form/invoice-form.component.jsx similarity index 71% rename from client/src/components/invoice-enter-modal/invoice-enter-modal.component.jsx rename to client/src/components/invoice-form/invoice-form.component.jsx index d848ac8c8..76b171f41 100644 --- a/client/src/components/invoice-enter-modal/invoice-enter-modal.component.jsx +++ b/client/src/components/invoice-form/invoice-form.component.jsx @@ -1,38 +1,25 @@ -import { DatePicker, Form, Input, Switch, Tag } from "antd"; +import { DatePicker, Form, Input, Switch } from "antd"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import DocumentsUploadContainer from "../documents-upload/documents-upload.container"; import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import JobSearchSelect from "../job-search-select/job-search-select.component"; import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component"; -import InvoiceEnterModalLinesComponent from "./invoice-enter-modal.lines.component"; -import CurrencyFormatter from "../../utils/CurrencyFormatter"; +import InvoiceFormLines from "./invoice-form.lines.component"; -export default function InvoiceEnterModalComponent({ +export default function InvoiceFormComponent({ form, roAutoCompleteOptions, vendorAutoCompleteOptions, lineData, responsibilityCenters, loadLines, + hideVendor, }) { const { t } = useTranslation(); const [discount, setDiscount] = useState(0); - const [amounts, setAmounts] = useState({ invoiceTotal: 0, enteredAmount: 0 }); - const { getFieldsValue } = form; - const calculateTotals = () => { - setAmounts({ - invoiceTotal: getFieldsValue().total || 0, - enteredTotal: getFieldsValue("invoicelines").invoicelines - ? getFieldsValue("invoicelines").invoicelines.reduce( - (acc, value) => - acc + (value && value.actual_cost ? value.actual_cost : 0), - 0 - ) - : 0, - }); - }; + const handleVendorSelect = (props, opt) => { setDiscount(opt.discount); }; @@ -62,6 +49,7 @@ export default function InvoiceEnterModalComponent({ - @@ -137,22 +124,8 @@ export default function InvoiceEnterModalComponent({ console.log(form.getFieldsValue()); }} > - a + Get Field Values - - {t("invoicelines.labels.entered")} - {amounts.enteredTotal || 0} - - {amounts.invoiceTotal - amounts.enteredTotal === 0 ? ( - {t("invoicelines.labels.reconciled")} - ) : ( - - {t("invoicelines.labels.unreconciled")}: - - {amounts.invoiceTotal - amounts.enteredTotal} - - - )} ); } diff --git a/client/src/components/invoice-form/invoice-form.container.jsx b/client/src/components/invoice-form/invoice-form.container.jsx new file mode 100644 index 000000000..8ef37f518 --- /dev/null +++ b/client/src/components/invoice-form/invoice-form.container.jsx @@ -0,0 +1,40 @@ +import { useLazyQuery, useQuery } from "@apollo/react-hooks"; +import React from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { GET_JOB_LINES_TO_ENTER_INVOICE } from "../../graphql/jobs-lines.queries"; +import { ACTIVE_JOBS_FOR_AUTOCOMPLETE } from "../../graphql/jobs.queries"; +import { SEARCH_VENDOR_AUTOCOMPLETE } from "../../graphql/vendors.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import InvoiceFormComponent from "./invoice-form.component"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); + +export function InvoiceFormContainer({ bodyshop, form, hideVendor }) { + const { data: RoAutoCompleteData } = useQuery(ACTIVE_JOBS_FOR_AUTOCOMPLETE, { + variables: { statuses: bodyshop.md_ro_statuses.open_statuses || ["Open"] }, + }); + + const { data: VendorAutoCompleteData } = useQuery(SEARCH_VENDOR_AUTOCOMPLETE); + + const [loadLines, { data: lineData }] = useLazyQuery( + GET_JOB_LINES_TO_ENTER_INVOICE + ); + + return ( + + ); +} +export default connect(mapStateToProps, null)(InvoiceFormContainer); diff --git a/client/src/components/invoice-form/invoice-form.lines.component.jsx b/client/src/components/invoice-form/invoice-form.lines.component.jsx new file mode 100644 index 000000000..4a1948d75 --- /dev/null +++ b/client/src/components/invoice-form/invoice-form.lines.component.jsx @@ -0,0 +1,162 @@ +import { DeleteFilled } from "@ant-design/icons"; +import { Button, Form, Input, Select } from "antd"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import CurrencyInput from "../form-items-formatted/currency-form-item.component"; +import InvoiceLineSearchSelect from "../invoice-line-search-select/invoice-line-search-select.component"; + +export default function InvoiceEnterModalLinesComponent({ + lineData, + discount, + form, + responsibilityCenters, +}) { + const { t } = useTranslation(); + const { setFieldsValue, getFieldsValue } = form; + + return ( + + {(fields, { add, remove }) => { + return ( +
+ {fields.map((field, index) => ( + +
+ + { + setFieldsValue({ + invoicelines: getFieldsValue([ + "invoicelines", + ]).invoicelines.map((item, idx) => { + if (idx === index) { + return { + ...item, + line_desc: opt.line_desc, + actual_price: opt.cost, + cost_center: opt.part_type + ? responsibilityCenters.defaults[ + opt.part_type + ] || null + : null, + }; + } + return item; + }), + }); + }} + /> + + + + + + + + { + setFieldsValue({ + invoicelines: getFieldsValue( + "invoicelines" + ).invoicelines.map((item, idx) => { + if (idx === index) { + return { + ...item, + actual_cost: !!item.actual_cost + ? item.actual_cost + : parseFloat(e.target.value) * (1 - discount), + }; + } + return item; + }), + }); + }} + /> + + + + + + + + { + remove(field.name); + }} + /> +
+
+ ))} + + + +
+ ); + }} +
+ ); +} diff --git a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx index 63775e055..f6800aa52 100644 --- a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx +++ b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx @@ -9,41 +9,49 @@ import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util"; import DuplicateJob from "./jobs-detail-header-actions.duplicate.util"; - +import { setModalContext } from "../../redux/modals/modals.actions"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser bodyshop: selectBodyshop, }); const mapDispatchToProps = (dispatch) => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) + setInvoiceEnterContext: (context) => + dispatch(setModalContext({ context: context, modal: "invoiceEnter" })), }); -export function JobsDetailHeaderActions({ job, bodyshop, refetch }) { +export function JobsDetailHeaderActions({ + job, + bodyshop, + refetch, + setInvoiceEnterContext, +}) { const { t } = useTranslation(); const client = useApolloClient(); const history = useHistory(); const statusmenu = ( - - + + + }} + > {t("menus.jobsactions.newcccontract")} AddToProduction(client, job.id, refetch)}> + onClick={() => AddToProduction(client, job.id, refetch)} + > {t("jobs.actions.addtoproduction")} - + e.stopPropagation()} onConfirm={() => DuplicateJob( @@ -55,14 +63,28 @@ export function JobsDetailHeaderActions({ job, bodyshop, refetch }) { } ) } - getPopupContainer={(trigger) => trigger.parentNode}> + getPopupContainer={(trigger) => trigger.parentNode} + > {t("menus.jobsactions.duplicate")} + { + setInvoiceEnterContext({ + actions: { refetch: refetch }, + context: { + job: job, + }, + }); + }} + > + {t("jobs.actions.postInvoices")} + ); return ( - + diff --git a/client/src/graphql/invoices.queries.js b/client/src/graphql/invoices.queries.js index 0c3a4c97c..a227886a9 100644 --- a/client/src/graphql/invoices.queries.js +++ b/client/src/graphql/invoices.queries.js @@ -89,6 +89,7 @@ export const QUERY_INVOICE_BY_PK = gql` jobid total updated_at + vendorid vendor { id name