MAJOR CHANGE: Renamed invoices to bills BOD-410

This commit is contained in:
Patrick Fic
2020-09-22 17:01:03 -07:00
parent 95ee3ae2bc
commit f84520c260
91 changed files with 2510 additions and 2624 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -4,18 +4,15 @@ import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters";
import InvoiceExportButton from "../invoice-export-button/invoice-export-button.component";
import InvoiceExportAllButton from "../invoice-export-all-button/invoice-export-all-button.component";
import PayableExportButton from "../payable-export-button/payable-export-button.component";
import PayableExportAll from "../payable-export-all-button/payable-export-all-button.component";
import { DateFormatter } from "../../utils/DateFormatter";
import queryString from "query-string";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function AccountingPayablesTableComponent({
loading,
invoices,
}) {
export default function AccountingPayablesTableComponent({ loading, bills }) {
const { t } = useTranslation();
const [selectedInvoices, setSelectedInvoices] = useState([]);
const [selectedBills, setSelectedBills] = useState([]);
const [transInProgress, setTransInProgress] = useState(false);
const [state, setState] = useState({
sortedInfo: {},
@@ -28,7 +25,7 @@ export default function AccountingPayablesTableComponent({
const columns = [
{
title: t("invoices.fields.vendorname"),
title: t("bills.fields.vendorname"),
dataIndex: "vendorname",
key: "vendorname",
sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
@@ -46,7 +43,7 @@ export default function AccountingPayablesTableComponent({
),
},
{
title: t("invoices.fields.invoice_number"),
title: t("bills.fields.invoice_number"),
dataIndex: "invoice_number",
key: "invoice_number",
sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number),
@@ -56,9 +53,9 @@ export default function AccountingPayablesTableComponent({
render: (text, record) => (
<Link
to={{
pathname: `/manage/invoices`,
pathname: `/manage/bills`,
search: queryString.stringify({
invoiceid: record.id,
billid: record.id,
vendorid: record.vendor.id,
}),
}}
@@ -79,7 +76,7 @@ export default function AccountingPayablesTableComponent({
),
},
{
title: t("invoices.fields.date"),
title: t("bills.fields.date"),
dataIndex: "date",
key: "date",
@@ -89,7 +86,7 @@ export default function AccountingPayablesTableComponent({
render: (text, record) => <DateFormatter>{record.date}</DateFormatter>,
},
{
title: t("invoices.fields.total"),
title: t("bills.fields.total"),
dataIndex: "total",
key: "total",
@@ -101,7 +98,7 @@ export default function AccountingPayablesTableComponent({
),
},
{
title: t("invoices.fields.is_credit_memo"),
title: t("bills.fields.is_credit_memo"),
dataIndex: "is_credit_memo",
key: "is_credit_memo",
sorter: (a, b) => a.is_credit_memo - b.is_credit_memo,
@@ -120,8 +117,8 @@ export default function AccountingPayablesTableComponent({
render: (text, record) => (
<div>
<InvoiceExportButton
invoiceId={record.id}
<PayableExportButton
billId={record.id}
disabled={transInProgress || !!record.exported}
loadingCallback={setTransInProgress}
/>
@@ -136,7 +133,7 @@ export default function AccountingPayablesTableComponent({
};
const dataSource = state.search
? invoices.filter(
? bills.filter(
(v) =>
(v.vendor.name || "")
.toLowerCase()
@@ -145,7 +142,7 @@ export default function AccountingPayablesTableComponent({
.toLowerCase()
.includes(state.search.toLowerCase())
)
: invoices;
: bills;
return (
<div>
@@ -160,11 +157,11 @@ export default function AccountingPayablesTableComponent({
placeholder={t("general.labels.search")}
allowClear
/>
<InvoiceExportAllButton
invoiceIds={selectedInvoices}
disabled={transInProgress || selectedInvoices.length === 0}
<PayableExportAll
billIds={selectedBills}
disabled={transInProgress || selectedBills.length === 0}
loadingCallback={setTransInProgress}
completedCallback={setSelectedInvoices}
completedCallback={setSelectedBills}
/>
</div>
);
@@ -177,14 +174,14 @@ export default function AccountingPayablesTableComponent({
onChange={handleTableChange}
rowSelection={{
onSelectAll: (selected, selectedRows) =>
setSelectedInvoices(selectedRows.map((i) => i.id)),
setSelectedBills(selectedRows.map((i) => i.id)),
onSelect: (record, selected, selectedRows, nativeEvent) => {
setSelectedInvoices(selectedRows.map((i) => i.id));
setSelectedBills(selectedRows.map((i) => i.id));
},
getCheckboxProps: (record) => ({
disabled: record.exported,
}),
selectedRowKeys: selectedInvoices,
selectedRowKeys: selectedBills,
type: "checkbox",
}}
/>

View File

@@ -1,57 +1,47 @@
import { useMutation, useQuery } from "@apollo/react-hooks";
import { Form, Button } from "antd";
import { Button, Form } from "antd";
import moment from "moment";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {
QUERY_INVOICE_BY_PK,
UPDATE_INVOICE,
} from "../../graphql/invoices.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { UPDATE_BILL_LINE } from "../../graphql/bill-lines.queries";
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
import AlertComponent from "../alert/alert.component";
import InvoiceFormContainer from "../invoice-form/invoice-form.container";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import { UPDATE_INVOICE_LINE } from "../../graphql/invoice-lines.queries";
import BillFormContainer from "../bill-form/bill-form.container";
import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-gallery.container";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
export function InvoiceDetailEditContainer({ bodyshop }) {
export default function BillDetailEditcontainer() {
const search = queryString.parse(useLocation().search);
const { t } = useTranslation();
const [form] = Form.useForm();
const [updateLoading, setUpdateLoading] = useState(false);
const [updateInvoice] = useMutation(UPDATE_INVOICE);
const [updateInvoiceLine] = useMutation(UPDATE_INVOICE_LINE);
const [update_bill] = useMutation(UPDATE_BILL);
const [updateBillLine] = useMutation(UPDATE_BILL_LINE);
const { loading, error, data, refetch } = useQuery(QUERY_INVOICE_BY_PK, {
variables: { invoiceid: search.invoiceid },
skip: !!!search.invoiceid,
const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, {
variables: { billid: search.billid },
skip: !!!search.billid,
});
const handleFinish = async (values) => {
setUpdateLoading(true);
const { invoicelines, upload, ...invoice } = values;
const { billlines, upload, ...bill } = values;
const updates = [];
updates.push(
updateInvoice({
variables: { invoiceId: search.invoiceid, invoice: invoice },
update_bill({
variables: { billId: search.billid, bill: bill },
})
);
invoicelines.forEach((il) => {
billlines.forEach((il) => {
delete il.__typename;
updates.push(
updateInvoiceLine({
updateBillLine({
variables: {
invoicelineId: il.id,
invoiceLine: {
billLineId: il.id,
billLine: {
...il,
joblineid: il.joblineid === "noline" ? null : il.joblineid,
},
@@ -65,14 +55,13 @@ export function InvoiceDetailEditContainer({ bodyshop }) {
};
useEffect(() => {
if (search.invoiceid) {
if (search.billid) {
form.resetFields();
}
}, [form, search.invoiceid]);
}, [form, search.billid]);
if (error) return <AlertComponent message={error.message} type="error" />;
if (!!!search.invoiceid)
return <div>{t("invoices.labels.noneselected")}</div>;
if (!!!search.billid) return <div>{t("bills.labels.noneselected")}</div>;
return (
<LoadingSkeleton loading={loading}>
<Form
@@ -81,9 +70,9 @@ export function InvoiceDetailEditContainer({ bodyshop }) {
initialValues={
data
? {
...data.invoices_by_pk,
...data.bills_by_pk,
invoicelines: data.invoices_by_pk.invoicelines.map((i) => {
billlines: data.bills_by_pk.billlines.map((i) => {
return {
...i,
joblineid: !!i.joblineid ? i.joblineid : "noline",
@@ -100,9 +89,7 @@ export function InvoiceDetailEditContainer({ bodyshop }) {
},
};
}),
date: data.invoices_by_pk
? moment(data.invoices_by_pk.date)
: null,
date: data.bills_by_pk ? moment(data.bills_by_pk.date) : null,
}
: {}
}
@@ -110,15 +97,14 @@ export function InvoiceDetailEditContainer({ bodyshop }) {
<Button htmlType="submit" loading={updateLoading} type="primary">
{t("general.actions.save")}
</Button>
<InvoiceFormContainer form={form} invoiceEdit />
<BillFormContainer form={form} billEdit />
<JobDocumentsGallery
jobId={data ? data.invoices_by_pk.jobid : null}
invoiceId={search.invoiceid}
documentsList={data ? data.invoices_by_pk.documents : []}
invoicesCallback={refetch}
jobId={data ? data.bills_by_pk.jobid : null}
billId={search.billid}
documentsList={data ? data.bills_by_pk.documents : []}
billsCallback={refetch}
/>
</Form>
</LoadingSkeleton>
);
}
export default connect(mapStateToProps, null)(InvoiceDetailEditContainer);

View File

@@ -4,28 +4,28 @@ 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 { INSERT_NEW_BILL } from "../../graphql/bills.queries";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectInvoiceEnterModal } from "../../redux/modals/modals.selectors";
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { handleUpload } from "../documents-upload/documents-upload.utility";
import InvoiceFormContainer from "../invoice-form/invoice-form.container";
import BillFormContainer from "../bill-form/bill-form.container";
import { UPDATE_JOB_LINE_STATUS } from "../../graphql/jobs-lines.queries";
const mapStateToProps = createStructuredSelector({
invoiceEnterModal: selectInvoiceEnterModal,
billEnterModal: selectBillEnterModal,
bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("invoiceEnter")),
toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")),
});
function InvoiceEnterModalContainer({
invoiceEnterModal,
function BillEnterModalContainer({
billEnterModal,
toggleModalVisible,
bodyshop,
currentUser,
@@ -33,21 +33,21 @@ function InvoiceEnterModalContainer({
const [form] = Form.useForm();
const { t } = useTranslation();
const [enterAgain, setEnterAgain] = useState(false);
const [insertInvoice] = useMutation(INSERT_NEW_INVOICE);
const [insertBill] = useMutation(INSERT_NEW_BILL);
const [updateJobLines] = useMutation(UPDATE_JOB_LINE_STATUS);
const [loading, setLoading] = useState(false);
const handleFinish = (values) => {
setLoading(true);
const { upload, location, ...remainingValues } = values;
insertInvoice({
insertBill({
variables: {
invoice: [
bill: [
Object.assign({}, remainingValues, {
invoicelines: {
billlines: {
data:
remainingValues.invoicelines &&
remainingValues.invoicelines.map((i) => {
remainingValues.billlines &&
remainingValues.billlines.map((i) => {
return {
...i,
joblineid: i.joblineid === "noline" ? null : i.joblineid,
@@ -59,11 +59,11 @@ function InvoiceEnterModalContainer({
},
})
.then((r) => {
const invoiceId = r.data.insert_invoices.returning[0].id;
const billId = r.data.insert_bills.returning[0].id;
updateJobLines({
variables: {
ids: remainingValues.invoicelines
ids: remainingValues.billlines
.filter((il) => il.joblineid !== "noline")
.map((li) => li.joblineid),
status: bodyshop.md_order_statuses.default_received || "Received*",
@@ -80,7 +80,7 @@ function InvoiceEnterModalContainer({
bodyshop: bodyshop,
uploaded_by: currentUser.email,
jobId: values.jobid,
invoiceId: invoiceId,
billId: billId,
tagsArray: null,
callback: null,
}
@@ -90,14 +90,13 @@ function InvoiceEnterModalContainer({
///////////////////////////
setLoading(false);
notification["success"]({
message: t("invoices.successes.created"),
message: t("bills.successes.created"),
});
if (invoiceEnterModal.actions.refetch)
invoiceEnterModal.actions.refetch();
if (billEnterModal.actions.refetch) billEnterModal.actions.refetch();
if (enterAgain) {
form.resetFields();
form.setFieldsValue({ invoicelines: [] });
form.setFieldsValue({ billlines: [] });
} else {
toggleModalVisible();
}
@@ -108,7 +107,7 @@ function InvoiceEnterModalContainer({
setLoading(false);
setEnterAgain(false);
notification["error"]({
message: t("invoices.errors.creating", {
message: t("bills.errors.creating", {
message: JSON.stringify(error),
}),
});
@@ -124,14 +123,14 @@ function InvoiceEnterModalContainer({
}, [enterAgain, form]);
useEffect(() => {
if (invoiceEnterModal.visible) form.resetFields();
}, [invoiceEnterModal.visible, form]);
if (billEnterModal.visible) form.resetFields();
}, [billEnterModal.visible, form]);
return (
<Modal
title={t("invoices.labels.new")}
title={t("bills.labels.new")}
width={"90%"}
visible={invoiceEnterModal.visible}
visible={billEnterModal.visible}
okText={t("general.actions.save")}
onOk={() => form.submit()}
onCancel={handleCancel}
@@ -142,7 +141,7 @@ function InvoiceEnterModalContainer({
<Button loading={loading} onClick={() => form.submit()}>
{t("general.actions.save")}
</Button>
{invoiceEnterModal.context && invoiceEnterModal.context.id ? null : (
{billEnterModal.context && billEnterModal.context.id ? null : (
<Button
type="primary"
loading={loading}
@@ -166,26 +165,25 @@ function InvoiceEnterModalContainer({
setEnterAgain(false);
}}
initialValues={{
...invoiceEnterModal.context.invoice,
...billEnterModal.context.bill,
jobid:
(invoiceEnterModal.context.job &&
invoiceEnterModal.context.job.id) ||
(billEnterModal.context.job && billEnterModal.context.job.id) ||
null,
federal_tax_rate:
(bodyshop.invoice_tax_rates &&
bodyshop.invoice_tax_rates.federal_tax_rate) ||
(bodyshop.bill_tax_rates &&
bodyshop.bill_tax_rates.federal_tax_rate) ||
0,
state_tax_rate:
(bodyshop.invoice_tax_rates &&
bodyshop.invoice_tax_rates.state_tax_rate) ||
(bodyshop.bill_tax_rates &&
bodyshop.bill_tax_rates.state_tax_rate) ||
0,
local_tax_rate:
(bodyshop.invoice_tax_rates &&
bodyshop.invoice_tax_rates.local_tax_rate) ||
(bodyshop.bill_tax_rates &&
bodyshop.bill_tax_rates.local_tax_rate) ||
0,
}}
>
<InvoiceFormContainer form={form} />
<BillFormContainer form={form} />
</Form>
</Modal>
);
@@ -194,4 +192,4 @@ function InvoiceEnterModalContainer({
export default connect(
mapStateToProps,
mapDispatchToProps
)(InvoiceEnterModalContainer);
)(BillEnterModalContainer);

View File

@@ -20,25 +20,21 @@ import CurrencyInput from "../form-items-formatted/currency-form-item.component"
import JobSearchSelect from "../job-search-select/job-search-select.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import InvoiceFormLines from "./invoice-form.lines.component";
import "./invoice-form.styles.scss";
import { CalculateInvoiceTotal } from "./invoice-form.totals.utility";
import BillFormLines from "./bill-form.lines.component";
import { CalculateBillTotal } from "./bill-form.totals.utility";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
const mapDispatchToProps = (dispatch) => ({});
export function InvoiceFormComponent({
export function BillFormComponent({
bodyshop,
form,
vendorAutoCompleteOptions,
lineData,
responsibilityCenters,
loadLines,
invoiceEdit,
billEdit,
}) {
const { t } = useTranslation();
@@ -69,7 +65,7 @@ export function InvoiceFormComponent({
<LayoutFormRow>
<Form.Item
name="jobid"
label={t("invoices.fields.ro_number")}
label={t("bills.fields.ro_number")}
rules={[
{
required: true,
@@ -78,7 +74,7 @@ export function InvoiceFormComponent({
]}
>
<JobSearchSelect
disabled={invoiceEdit}
disabled={billEdit}
onBlur={() => {
if (form.getFieldValue("jobid") !== null) {
loadLines({ variables: { id: form.getFieldValue("jobid") } });
@@ -87,9 +83,9 @@ export function InvoiceFormComponent({
/>
</Form.Item>
<Form.Item
label={t("invoices.fields.vendor")}
label={t("bills.fields.vendor")}
name="vendorid"
style={{ display: invoiceEdit ? "none" : null }}
style={{ display: billEdit ? "none" : null }}
rules={[
{
required: true,
@@ -106,7 +102,7 @@ export function InvoiceFormComponent({
<LayoutFormRow>
<Form.Item
label={t("invoices.fields.invoice_number")}
label={t("bills.fields.invoice_number")}
name="invoice_number"
rules={[
{
@@ -118,7 +114,7 @@ export function InvoiceFormComponent({
<Input />
</Form.Item>
<Form.Item
label={t("invoices.fields.date")}
label={t("bills.fields.date")}
name="date"
rules={[
{
@@ -130,14 +126,14 @@ export function InvoiceFormComponent({
<FormDatePicker />
</Form.Item>
<Form.Item
label={t("invoices.fields.is_credit_memo")}
label={t("bills.fields.is_credit_memo")}
name="is_credit_memo"
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("invoices.fields.total")}
label={t("bills.fields.total")}
name="total"
rules={[
{
@@ -149,28 +145,25 @@ export function InvoiceFormComponent({
<CurrencyInput min={0} />
</Form.Item>
<Form.Item
label={t("invoices.fields.federal_tax_rate")}
label={t("bills.fields.federal_tax_rate")}
name="federal_tax_rate"
>
<CurrencyInput min={0} />
</Form.Item>
<Form.Item
label={t("invoices.fields.state_tax_rate")}
label={t("bills.fields.state_tax_rate")}
name="state_tax_rate"
>
<CurrencyInput min={0} />
</Form.Item>
<Form.Item
label={t("invoices.fields.local_tax_rate")}
label={t("bills.fields.local_tax_rate")}
name="local_tax_rate"
>
<CurrencyInput min={0} />
</Form.Item>
<Form.Item
label={t("invoices.fields.allpartslocation")}
name="location"
>
<Form.Item label={t("bills.fields.allpartslocation")} name="location">
<Select style={{ width: "10rem" }}>
{bodyshop.md_parts_locations.map((loc, idx) => (
<Select.Option key={idx} value={loc}>
@@ -181,9 +174,9 @@ export function InvoiceFormComponent({
</Form.Item>
</LayoutFormRow>
<Typography.Title level={4}>
{t("invoices.labels.invoice_lines")}
{t("bills.labels.bill_lines")}
</Typography.Title>
<InvoiceFormLines
<BillFormLines
lineData={lineData}
discount={discount}
form={form}
@@ -193,7 +186,7 @@ export function InvoiceFormComponent({
<Form.Item
name="upload"
label="Upload"
style={{ display: invoiceEdit ? "none" : null }}
style={{ display: billEdit ? "none" : null }}
valuePropName="fileList"
getValueFromEvent={(e) => {
if (Array.isArray(e)) {
@@ -210,7 +203,7 @@ export function InvoiceFormComponent({
<Form.Item shouldUpdate>
{() => {
const values = form.getFieldsValue([
"invoicelines",
"billlines",
"total",
"federal_tax_rate",
"state_tax_rate",
@@ -219,46 +212,46 @@ export function InvoiceFormComponent({
let totals;
if (
!!values.total &&
!!values.invoicelines &&
values.invoicelines.length > 0
!!values.billlines &&
values.billlines.length > 0
)
totals = CalculateInvoiceTotal(values);
totals = CalculateBillTotal(values);
if (!!totals)
return (
<div>
<Space>
<Statistic
title={t("invoices.labels.subtotal")}
title={t("bills.labels.subtotal")}
value={totals.subtotal.toFormat()}
precision={2}
/>
<Statistic
title={t("invoices.labels.federal_tax")}
title={t("bills.labels.federal_tax")}
value={totals.federalTax.toFormat()}
precision={2}
/>
<Statistic
title={t("invoices.labels.state_tax")}
title={t("bills.labels.state_tax")}
value={totals.stateTax.toFormat()}
precision={2}
/>
<Statistic
title={t("invoices.labels.local_tax")}
title={t("bills.labels.local_tax")}
value={totals.localTax.toFormat()}
precision={2}
/>
<Statistic
title={t("invoices.labels.entered_total")}
title={t("bills.labels.entered_total")}
value={totals.enteredTotal.toFormat()}
precision={2}
/>
<Statistic
title={t("invoices.labels.invoice_total")}
title={t("bills.labels.bill_total")}
value={totals.invoiceTotal.toFormat()}
precision={2}
/>
<Statistic
title={t("invoices.labels.discrepancy")}
title={t("bills.labels.discrepancy")}
valueStyle={{
color:
totals.discrepancy.getAmount() === 0 ? "green" : "red",
@@ -270,7 +263,7 @@ export function InvoiceFormComponent({
{form.getFieldValue("is_credit_memo") ? (
<AlertComponent
type="warning"
message={t("invoices.labels.enteringcreditmemo")}
message={t("bills.labels.enteringcreditmemo")}
/>
) : null}
</div>
@@ -282,7 +275,4 @@ export function InvoiceFormComponent({
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(InvoiceFormComponent);
export default connect(mapStateToProps, mapDispatchToProps)(BillFormComponent);

View File

@@ -2,27 +2,27 @@ 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 { GET_JOB_LINES_TO_ENTER_BILL } from "../../graphql/jobs-lines.queries";
import { SEARCH_VENDOR_AUTOCOMPLETE } from "../../graphql/vendors.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import InvoiceFormComponent from "./invoice-form.component";
import BillFormComponent from "./bill-form.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
export function InvoiceFormContainer({ bodyshop, form, invoiceEdit }) {
export function BillFormContainer({ bodyshop, form, billEdit }) {
const { data: VendorAutoCompleteData } = useQuery(SEARCH_VENDOR_AUTOCOMPLETE);
const [loadLines, { data: lineData }] = useLazyQuery(
GET_JOB_LINES_TO_ENTER_INVOICE
GET_JOB_LINES_TO_ENTER_BILL
);
return (
<div>
<InvoiceFormComponent
<BillFormComponent
form={form}
invoiceEdit={invoiceEdit}
billEdit={billEdit}
vendorAutoCompleteOptions={
VendorAutoCompleteData && VendorAutoCompleteData.vendors
}
@@ -33,4 +33,4 @@ export function InvoiceFormContainer({ bodyshop, form, invoiceEdit }) {
</div>
);
}
export default connect(mapStateToProps, null)(InvoiceFormContainer);
export default connect(mapStateToProps, null)(BillFormContainer);

View File

@@ -11,10 +11,10 @@ import {
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";
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
export default function InvoiceEnterModalLinesComponent({
export default function BillEnterModalLinesComponent({
lineData,
discount,
form,
@@ -24,7 +24,7 @@ export default function InvoiceEnterModalLinesComponent({
const { setFieldsValue, getFieldsValue } = form;
return (
<Form.List name="invoicelines">
<Form.List name="billlines">
{(fields, { add, remove, move }) => {
return (
<div className="invoice-form-lines-wrapper">
@@ -34,7 +34,7 @@ export default function InvoiceEnterModalLinesComponent({
<div style={{ display: "flex", alignItems: "center" }}>
<LayoutFormRow style={{ flex: 1 }} grow>
<Form.Item
label={t("invoicelines.fields.jobline")}
label={t("billlines.fields.jobline")}
key={`${index}joblinename`}
name={[field.name, "joblineid"]}
rules={[
@@ -44,13 +44,13 @@ export default function InvoiceEnterModalLinesComponent({
},
]}
>
<InvoiceLineSearchSelect
<BillLineSearchSelect
options={lineData}
onSelect={(value, opt) => {
setFieldsValue({
invoicelines: getFieldsValue([
"invoicelines",
]).invoicelines.map((item, idx) => {
billlines: getFieldsValue([
"billlines",
]).billlines.map((item, idx) => {
if (idx === index) {
return {
...item,
@@ -72,7 +72,7 @@ export default function InvoiceEnterModalLinesComponent({
</Form.Item>
<Form.Item
label={t("invoicelines.fields.line_desc")}
label={t("billlines.fields.line_desc")}
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
rules={[
@@ -85,7 +85,7 @@ export default function InvoiceEnterModalLinesComponent({
<Input />
</Form.Item>
<Form.Item
label={t("invoicelines.fields.quantity")}
label={t("billlines.fields.quantity")}
key={`${index}quantity`}
name={[field.name, "quantity"]}
rules={[
@@ -98,7 +98,7 @@ export default function InvoiceEnterModalLinesComponent({
<InputNumber precision={0} min={0} />
</Form.Item>
<Form.Item
label={t("invoicelines.fields.actual")}
label={t("billlines.fields.actual")}
key={`${index}actual_price`}
name={[field.name, "actual_price"]}
rules={[
@@ -112,9 +112,9 @@ export default function InvoiceEnterModalLinesComponent({
min={0}
onBlur={(e) => {
setFieldsValue({
invoicelines: getFieldsValue(
"invoicelines"
).invoicelines.map((item, idx) => {
billlines: getFieldsValue(
"billlines"
).billlines.map((item, idx) => {
if (idx === index) {
return {
...item,
@@ -131,7 +131,7 @@ export default function InvoiceEnterModalLinesComponent({
/>
</Form.Item>
<Form.Item
label={t("invoicelines.fields.actual_cost")}
label={t("billlines.fields.actual_cost")}
key={`${index}actual_cost`}
name={[field.name, "actual_cost"]}
rules={[
@@ -145,8 +145,9 @@ export default function InvoiceEnterModalLinesComponent({
</Form.Item>
<Form.Item shouldUpdate>
{() => {
const line = getFieldsValue(["invoicelines"])
.invoicelines[index];
const line = getFieldsValue(["billlines"]).billlines[
index
];
if (!!!line) return null;
const lineDiscount = (
1 -
@@ -161,7 +162,7 @@ export default function InvoiceEnterModalLinesComponent({
}}
</Form.Item>
<Form.Item
label={t("invoicelines.fields.cost_center")}
label={t("billlines.fields.cost_center")}
key={`${index}cost_center`}
name={[field.name, "cost_center"]}
rules={[
@@ -180,7 +181,7 @@ export default function InvoiceEnterModalLinesComponent({
</Select>
</Form.Item>
<Form.Item
label={t("invoicelines.fields.federal_tax_applicable")}
label={t("billlines.fields.federal_tax_applicable")}
key={`${index}fedtax`}
initialValue={true}
valuePropName="checked"
@@ -189,7 +190,7 @@ export default function InvoiceEnterModalLinesComponent({
<Switch />
</Form.Item>
<Form.Item
label={t("invoicelines.fields.state_tax_applicable")}
label={t("billlines.fields.state_tax_applicable")}
key={`${index}statetax`}
valuePropName="checked"
name={[field.name, "applicable_taxes", "state"]}
@@ -197,7 +198,7 @@ export default function InvoiceEnterModalLinesComponent({
<Switch />
</Form.Item>
<Form.Item
label={t("invoicelines.fields.local_tax_applicable")}
label={t("billlines.fields.local_tax_applicable")}
key={`${index}localtax`}
valuePropName="checked"
name={[field.name, "applicable_taxes", "local"]}
@@ -227,7 +228,7 @@ export default function InvoiceEnterModalLinesComponent({
}}
style={{ width: "100%" }}
>
{t("invoicelines.actions.newline")}
{t("billlines.actions.newline")}
</Button>
</Form.Item>
</div>

View File

@@ -1,13 +1,12 @@
import Dinero from "dinero.js";
import { logImEXEvent } from "../../firebase/firebase.utils";
export const CalculateInvoiceTotal = (invoice) => {
export const CalculateBillTotal = (invoice) => {
logImEXEvent("invoice_calculate_total");
const {
total,
invoicelines,
billlines,
federal_tax_rate,
local_tax_rate,
state_tax_rate,
@@ -17,8 +16,10 @@ export const CalculateInvoiceTotal = (invoice) => {
let federalTax = Dinero({ amount: 0 });
let stateTax = Dinero({ amount: 0 });
let localTax = Dinero({ amount: 0 });
if (!!!invoicelines) return null;
invoicelines.forEach((i) => {
if (!!!billlines) return null;
billlines.forEach((i) => {
if (!!i) {
const itemTotal = Dinero({
amount: Math.round((i.actual_cost || 0) * 100) || 0,

View File

@@ -5,7 +5,7 @@ import CurrencyFormatter from "../../utils/CurrencyFormatter";
//To be used as a form element only.
const { Option } = Select;
const InvoiceLineSearchSelect = (
const BillLineSearchSelect = (
{ value, onChange, options, onBlur, onSelect },
ref
) => {
@@ -33,7 +33,7 @@ const InvoiceLineSearchSelect = (
onSelect={onSelect}
>
<Select.Option key={null} value={"noline"} cost={0} line_desc={""}>
{t("invoicelines.labels.other")}
{t("billlines.labels.other")}
</Select.Option>
{options
? options.map((item) => (
@@ -68,4 +68,4 @@ const InvoiceLineSearchSelect = (
</Select>
);
};
export default forwardRef(InvoiceLineSearchSelect);
export default forwardRef(BillLineSearchSelect);

View File

@@ -14,38 +14,34 @@ import { useLocation } from "react-router-dom";
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
setInvoiceEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "invoiceEnter" })),
setBillEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "billEnter" })),
setReconciliationContext: (context) =>
dispatch(setModalContext({ context: context, modal: "reconciliation" })),
});
export function InvoicesListTableComponent({
export function BillsListTableComponent({
job,
loading,
invoicesQuery,
billsQuery,
handleOnRowClick,
setPartsOrderContext,
setInvoiceEnterContext,
setBillEnterContext,
setReconciliationContext,
}) {
const { t } = useTranslation();
const [
selectedInvoiceLinesByInvoice,
setSelectedInvoiceLinesByInvoice,
] = useState({});
const [selectedBillLinesByBill, setSelectedBillLinesByBill] = useState({});
const [state, setState] = useState({
sortedInfo: {},
});
const search = queryString.parse(useLocation().search);
const selectedInvoice = search.invoiceid;
const selectedBill = search.billid;
const invoices = invoicesQuery.data ? invoicesQuery.data.invoices : [];
const { refetch } = invoicesQuery;
const bills = billsQuery.data ? billsQuery.data.bills : [];
const { refetch } = billsQuery;
const columns = [
{
title: t("invoices.fields.vendorname"),
title: t("bills.fields.vendorname"),
dataIndex: "vendorname",
key: "vendorname",
sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
@@ -54,7 +50,7 @@ export function InvoicesListTableComponent({
render: (text, record) => <span>{record.vendor.name}</span>,
},
{
title: t("invoices.fields.invoice_number"),
title: t("bills.fields.invoice_number"),
dataIndex: "invoice_number",
key: "invoice_number",
sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number),
@@ -63,7 +59,7 @@ export function InvoicesListTableComponent({
state.sortedInfo.order,
},
{
title: t("invoices.fields.date"),
title: t("bills.fields.date"),
dataIndex: "date",
key: "date",
sorter: (a, b) => a.date - b.date,
@@ -72,7 +68,7 @@ export function InvoicesListTableComponent({
render: (text, record) => <DateFormatter>{record.date}</DateFormatter>,
},
{
title: t("invoices.fields.total"),
title: t("bills.fields.total"),
dataIndex: "total",
key: "total",
sorter: (a, b) => a.total - b.total,
@@ -83,7 +79,7 @@ export function InvoicesListTableComponent({
),
},
{
title: t("invoices.fields.is_credit_memo"),
title: t("bills.fields.is_credit_memo"),
dataIndex: "is_credit_memo",
key: "is_credit_memo",
sorter: (a, b) => a.is_credit_memo - b.is_credit_memo,
@@ -99,9 +95,9 @@ export function InvoicesListTableComponent({
render: (text, record) => (
<div>
<Link
to={`/manage/invoices?invoiceid=${record.id}&vendorid=${record.vendorid}`}
to={`/manage/bills?billid=${record.id}&vendorid=${record.vendorid}`}
>
<Button>{t("invoices.actions.edit")}</Button>
<Button>{t("bills.actions.edit")}</Button>
</Link>
</div>
),
@@ -115,7 +111,7 @@ export function InvoicesListTableComponent({
const rowExpander = (record) => {
const columns = [
{
title: t("invoicelines.fields.line_desc"),
title: t("billlines.fields.line_desc"),
dataIndex: "line_desc",
key: "line_desc",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
@@ -123,7 +119,7 @@ export function InvoicesListTableComponent({
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
},
{
title: t("invoicelines.fields.retail"),
title: t("billlines.fields.retail"),
dataIndex: "actual_price",
key: "actual_price",
sorter: (a, b) => a.actual_price - b.actual_price,
@@ -135,7 +131,7 @@ export function InvoicesListTableComponent({
),
},
{
title: t("invoicelines.fields.actual_cost"),
title: t("billlines.fields.actual_cost"),
dataIndex: "actual_cost",
key: "actual_cost",
sorter: (a, b) => a.actual_cost - b.actual_cost,
@@ -147,7 +143,7 @@ export function InvoicesListTableComponent({
),
},
{
title: t("invoicelines.fields.quantity"),
title: t("billlines.fields.quantity"),
dataIndex: "quantity",
key: "quantity",
sorter: (a, b) => a.quantity - b.quantity,
@@ -155,7 +151,7 @@ export function InvoicesListTableComponent({
state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order,
},
{
title: t("invoicelines.fields.cost_center"),
title: t("billlines.fields.cost_center"),
dataIndex: "cost_center",
key: "cost_center",
sorter: (a, b) => alphaSort(a.cost_center, b.cost_center),
@@ -164,7 +160,7 @@ export function InvoicesListTableComponent({
state.sortedInfo.order,
},
{
title: t("invoicelines.fields.federal_tax_applicable"),
title: t("billlines.fields.federal_tax_applicable"),
dataIndex: "applicable_taxes.federal",
key: "applicable_taxes.federal",
render: (text, record) => (
@@ -178,7 +174,7 @@ export function InvoicesListTableComponent({
),
},
{
title: t("invoicelines.fields.state_tax_applicable"),
title: t("billlines.fields.state_tax_applicable"),
dataIndex: "applicable_taxes.state",
key: "applicable_taxes.state",
render: (text, record) => (
@@ -192,7 +188,7 @@ export function InvoicesListTableComponent({
),
},
{
title: t("invoicelines.fields.local_tax_applicable"),
title: t("billlines.fields.local_tax_applicable"),
dataIndex: "applicable_taxes.local",
key: "applicable_taxes.local",
render: (text, record) => (
@@ -207,26 +203,26 @@ export function InvoicesListTableComponent({
},
];
const handleOnInvoiceRowclick = (selectedRows) => {
setSelectedInvoiceLinesByInvoice({
...selectedInvoiceLinesByInvoice,
const handleOnBillrowclick = (selectedRows) => {
setSelectedBillLinesByBill({
...selectedBillLinesByBill,
[record.id]: selectedRows.map((r) => r.id),
});
};
return (
<div>
<Typography.Title level={3}>{`${t("invoices.fields.invoice_number")} ${
<Typography.Title level={3}>{`${t("bills.fields.invoice_number")} ${
record.invoice_number
}`}</Typography.Title>
<Descriptions>
<Descriptions.Item label={t("invoices.fields.federal_tax_rate")}>
<Descriptions.Item label={t("bills.fields.federal_tax_rate")}>
{`${record.federal_tax_rate}%` || ""}
</Descriptions.Item>
<Descriptions.Item label={t("invoices.fields.state_tax_rate")}>
<Descriptions.Item label={t("bills.fields.state_tax_rate")}>
{`${record.state_tax_rate}%` || ""}
</Descriptions.Item>
<Descriptions.Item label={t("invoices.fields.local_tax_rate")}>
<Descriptions.Item label={t("bills.fields.local_tax_rate")}>
{`${record.local_tax_rate}%` || ""}
</Descriptions.Item>
</Descriptions>
@@ -238,11 +234,11 @@ export function InvoicesListTableComponent({
context: {
jobId: job.id,
vendorId: record.vendorid,
returnFromInvoice: record.id,
returnFromBill: record.id,
invoiceNumber: record.invoice_number,
linesToOrder: record.invoicelines
linesToOrder: record.billlines
.filter((il) =>
selectedInvoiceLinesByInvoice[record.id].includes(il.id)
selectedBillLinesByBill[record.id].includes(il.id)
)
.map((i) => {
return {
@@ -258,7 +254,7 @@ export function InvoicesListTableComponent({
})
}
>
{t("invoices.actions.return")}
{t("bills.actions.return")}
</Button>
<Table
size="small"
@@ -266,15 +262,15 @@ export function InvoicesListTableComponent({
pagination={{ position: "top", defaultPageSize: 25 }}
columns={columns}
rowKey="id"
dataSource={record.invoicelines}
dataSource={record.billlines}
rowSelection={{
onSelect: (record, selected, selectedRows) => {
handleOnInvoiceRowclick(selectedRows);
handleOnBillrowclick(selectedRows);
},
onSelectAll: (selected, selectedRows, changeRows) => {
handleOnInvoiceRowclick(selectedRows);
handleOnBillrowclick(selectedRows);
},
selectedRowKeys: selectedInvoiceLinesByInvoice[record.id],
selectedRowKeys: selectedBillLinesByBill[record.id],
type: "checkbox",
}}
/>
@@ -284,11 +280,9 @@ export function InvoicesListTableComponent({
return (
<div>
<Typography.Title level={4}>
{t("invoices.labels.invoices")}
</Typography.Title>
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
<Table
loading={loading}
loading={billsQuery.loading}
size="small"
title={() => (
<div className="imex-table-header">
@@ -299,25 +293,23 @@ export function InvoicesListTableComponent({
<div>
<Button
onClick={() => {
setInvoiceEnterContext({
actions: { refetch: invoicesQuery.refetch },
setBillEnterContext({
actions: { refetch: billsQuery.refetch },
context: {
job,
},
});
}}
>
{t("jobs.actions.postInvoices")}
{t("jobs.actions.postbills")}
</Button>
<Button
onClick={() => {
setReconciliationContext({
actions: { refetch: invoicesQuery.refetch },
actions: { refetch: billsQuery.refetch },
context: {
job,
invoices:
(invoicesQuery.data && invoicesQuery.data.invoices) ||
[],
bills: (billsQuery.data && billsQuery.data.bills) || [],
},
});
}}
@@ -342,10 +334,10 @@ export function InvoicesListTableComponent({
pagination={{ position: "top", defaultPageSize: 25 }}
columns={columns}
rowKey="id"
dataSource={invoices}
dataSource={bills}
onChange={handleTableChange}
expandable={{
expandedRowKeys: [selectedInvoice],
expandedRowKeys: [selectedBill],
onExpand: (expanded, record) => {
handleOnRowClick(expanded ? record : null);
},
@@ -354,7 +346,7 @@ export function InvoicesListTableComponent({
onSelect: (record) => {
handleOnRowClick(record);
},
selectedRowKeys: [selectedInvoice],
selectedRowKeys: [selectedBill],
type: "radio",
}}
onRow={(record, rowIndex) => {
@@ -372,4 +364,4 @@ export function InvoicesListTableComponent({
</div>
);
}
export default connect(null, mapDispatchToProps)(InvoicesListTableComponent);
export default connect(null, mapDispatchToProps)(BillsListTableComponent);

View File

@@ -8,7 +8,7 @@ import { useTranslation } from "react-i18next";
import { alphaSort } from "../../utils/sorters";
import AlertComponent from "../alert/alert.component";
export default function InvoicesVendorsList() {
export default function BillsVendorsList() {
const search = queryString.parse(useLocation().search);
const history = useHistory();
@@ -51,7 +51,7 @@ export default function InvoicesVendorsList() {
const handleOnRowClick = (record) => {
if (record) {
delete search.invoiceid;
delete search.billid;
if (record.id) {
search.vendorid = record.id;
history.push({ search: queryString.stringify(search) });

View File

@@ -116,9 +116,9 @@ export function ContractConvertToRo({ bodyshop, currentUser, contract }) {
shopid: bodyshop.id,
ownerid: contract.job.ownerid,
vehicleid: contract.job.vehicleid,
federal_tax_rate: bodyshop.invoice_tax_rates.federal_tax_rate / 100,
state_tax_rate: bodyshop.invoice_tax_rates.state_tax_rate / 100,
local_tax_rate: bodyshop.invoice_tax_rates.local_tax_rate / 100,
federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate / 100,
state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate / 100,
local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate / 100,
clm_no: `${contract.job.clm_no}-CC`,
clm_total: 1234, //TODO
ownr_fn: contract.job.owner.ownr_fn,

View File

@@ -20,7 +20,7 @@ const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("courtesyCarReturn")),
});
export function InvoiceEnterModalContainer({
export function BillEnterModalContainer({
courtesyCarReturnModal,
toggleModalVisible,
bodyshop,
@@ -84,4 +84,4 @@ export function InvoiceEnterModalContainer({
export default connect(
mapStateToProps,
mapDispatchToProps
)(InvoiceEnterModalContainer);
)(BillEnterModalContainer);

View File

@@ -1,7 +1,10 @@
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import DocumentsUploadComponent from "./documents-upload.component";
import { handleUpload } from "./documents-upload.utility";
@@ -13,7 +16,7 @@ const mapStateToProps = createStructuredSelector({
export function DocumentsUploadContainer({
jobId,
tagsArray,
invoiceId,
billId,
currentUser,
bodyshop,
callbackAfterUpload,
@@ -26,7 +29,7 @@ export function DocumentsUploadContainer({
bodyshop: bodyshop,
uploaded_by: currentUser.email,
jobId: jobId,
invoiceId: invoiceId,
billId: billId,
tagsArray: tagsArray,
callback: callbackAfterUpload,
})

View File

@@ -67,14 +67,7 @@ export const uploadToS3 = (
onProgress,
context
) => {
const {
bodyshop,
jobId,
invoiceId,
uploaded_by,
callback,
tagsArray,
} = context;
const { bodyshop, jobId, billId, uploaded_by, callback, tagsArray } = context;
let timestamp = Math.floor(Date.now() / 1000);
let public_id = fileName;
@@ -121,7 +114,7 @@ export const uploadToS3 = (
jobid: jobId,
uploaded_by: uploaded_by,
key: fileName,
invoiceid: invoiceId,
billid: billId,
type: fileType,
},
],

View File

@@ -132,16 +132,16 @@ export default function GlobalSearch() {
}),
},
{
label: renderTitle(t("menus.header.search.invoices")),
options: data.search_invoices.map((invoice) => {
label: renderTitle(t("menus.header.search.bills")),
options: data.search_bills.map((bill) => {
return {
value: `${invoice.invoice_number}`,
value: `${bill.invoice_number}`,
label: (
<Link to={`/manage/invoices?invoiceid=${invoice.id}`}>
<Link to={`/manage/bills?billid=${bill.id}`}>
<div className="imex-flex-row">
<span className="imex-flex-row__margin-large">{`${invoice.invoice_number}`}</span>
<span className="imex-flex-row__margin-large">{`${invoice.vendor.name}`}</span>
<span className="imex-flex-row__margin-large">{`${invoice.date}`}</span>
<span className="imex-flex-row__margin-large">{`${bill.invoice_number}`}</span>
<span className="imex-flex-row__margin-large">{`${bill.vendor.name}`}</span>
<span className="imex-flex-row__margin-large">{`${bill.date}`}</span>
</div>
</Link>
),

View File

@@ -43,8 +43,8 @@ const mapStateToProps = createStructuredSelector({
});
const mapDispatchToProps = (dispatch) => ({
setInvoiceEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "invoiceEnter" })),
setBillEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "billEnter" })),
setTimeTicketContext: (context) =>
dispatch(setModalContext({ context: context, modal: "timeTicket" })),
setPaymentContext: (context) =>
@@ -57,7 +57,7 @@ function Header({
currentUser,
selectedHeader,
signOutStart,
setInvoiceEnterContext,
setBillEnterContext,
setTimeTicketContext,
setPaymentContext,
recentItems,
@@ -183,20 +183,20 @@ function Header({
</span>
}
>
<Menu.Item key="invoices">
<Link to="/manage/invoices">{t("menus.header.invoices")}</Link>
<Menu.Item key="bills">
<Link to="/manage/bills">{t("menus.header.bills")}</Link>
</Menu.Item>
<Menu.Item
key="enterinvoices"
key="enterbills"
onClick={() => {
setInvoiceEnterContext({
setBillEnterContext({
actions: {},
context: {},
});
}}
>
<Icon component={FaFileInvoiceDollar} />
{t("menus.header.enterinvoices")}
{t("menus.header.enterbills")}
</Menu.Item>
<Menu.Divider />
<Menu.Item key="allpayments">

View File

@@ -1,40 +0,0 @@
// .invoice-form-wrapper {
// display: flex;
// flex-direction: column;
// justify-content: left;
// }
// .invoice-form-totals {
// display: flex;
// justify-content: space-around;
// align-items: flex-start;
// flex-wrap: wrap;
// & > * {
// padding: 5px;
// }
// }
// .invoice-form-invoice-details {
// display: flex;
// align-items: flex-start;
// flex-wrap: wrap;
// & > * {
// padding: 5px;
// }
// }
// .invoice-form-lines-wrapper {
// border: 3px ridge rgba(28, 110, 164, 0.24);
// border-radius: 4px;
// }
// .invoice-form-line {
// display: flex;
// flex-wrap: wrap;
// align-items: flex-start;
// justify-content: space-around;
// border-bottom: 2px dashed rgba(7, 7, 7, 0.4);
// F & > * {
// margin: 5px;
// }
// }

View File

@@ -1,161 +0,0 @@
//DEPRECATED.
// import React, { useState } from "react";
// import { QUERY_INVOICES_BY_VENDOR_PAGINATED } from "../../graphql/invoices.queries";
// import { useQuery } from "@apollo/react-hooks";
// import queryString from "query-string";
// import { useHistory, useLocation } from "react-router-dom";
// import { Table, Input } from "antd";
// import { useTranslation } from "react-i18next";
// import { alphaSort } from "../../utils/sorters";
// import AlertComponent from "../alert/alert.component";
// import { DateFormatter } from "../../utils/DateFormatter";
// import CurrencyFormatter from "../../utils/CurrencyFormatter";
// export default function InvoicesByVendorList() {
// const search = queryString.parse(useLocation().search);
// const history = useHistory();
// const { page, sortcolumn, sortorder } = search;
// const { loading, error, data } = useQuery(
// QUERY_INVOICES_BY_VENDOR_PAGINATED,
// {
// variables: {
// vendorId: search.vendorid,
// offset: page ? (page - 1) * 25 : 0,
// limit: 25,
// order: [
// {
// [sortcolumn || "date"]: sortorder
// ? sortorder === "descend"
// ? "desc"
// : "asc"
// : "desc",
// },
// ],
// },
// skip: !!!search.vendorid,
// }
// );
// const { t } = useTranslation();
// const [state, setState] = useState({
// sortedInfo: {},
// search: "",
// });
// const handleTableChange = (pagination, filters, sorter) => {
// setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
// search.page = pagination.current;
// search.sortcolumn = sorter.columnKey;
// search.sortorder = sorter.order;
// history.push({ search: queryString.stringify(search) });
// };
// const handleOnRowClick = (record) => {
// if (record) {
// if (record.id) {
// search.invoiceid = record.id;
// history.push({ search: queryString.stringify(search) });
// }
// } else {
// delete search.invoiceid;
// history.push({ search: queryString.stringify(search) });
// }
// };
// const columns = [
// {
// title: t("invoices.fields.invoice_number"),
// dataIndex: "invoice_number",
// key: "invoice_number",
// sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number),
// sortOrder:
// state.sortedInfo.columnKey === "invoice_number" &&
// state.sortedInfo.order,
// },
// {
// title: t("invoices.fields.date"),
// dataIndex: "date",
// key: "date",
// sorter: (a, b) => a.date - b.date,
// sortOrder:
// state.sortedInfo.columnKey === "date" && state.sortedInfo.order,
// render: (text, record) => <DateFormatter>{record.date}</DateFormatter>,
// },
// {
// title: t("invoices.fields.total"),
// dataIndex: "total",
// key: "total",
// sorter: (a, b) => a.total - b.total,
// sortOrder:
// state.sortedInfo.columnKey === "total" && state.sortedInfo.order,
// render: (text, record) => (
// <CurrencyFormatter>{record.total}</CurrencyFormatter>
// ),
// },
// ];
// const handleSearch = (e) => {
// setState({ ...state, search: e.target.value });
// };
// const dataSource = state.search
// ? data.invoices.filter(
// (i) =>
// (i.invoice_number || "")
// .toLowerCase()
// .includes(state.search.toLowerCase()) ||
// (i.amount || "").toString().includes(state.search)
// )
// : (data && data.invoices) || [];
// if (error) return <AlertComponent message={error.message} type='error' />;
// return (
// <Table
// loading={loading}
// title={() => {
// return (
// <div>
// <Input
// value={state.search}
// onChange={handleSearch}
// placeholder={t("general.labels.search")}
// allowClear
// />
// </div>
// );
// }}
// dataSource={dataSource}
// size='small'
// scroll={{ x: true }}
// pagination={{
// position: "top",
// pageSize: 25,
// current: parseInt(page || 1),
// total: data ? data.invoices_aggregate.aggregate.count : 0,
// }}
// columns={columns}
// rowKey='id'
// onChange={handleTableChange}
// rowSelection={{
// onSelect: (record) => {
// handleOnRowClick(record);
// },
// selectedRowKeys: [search.invoiceid],
// type: "radio",
// }}
// onRow={(record, rowIndex) => {
// return {
// onClick: (event) => {
// handleOnRowClick(record);
// }, // click row
// };
// }}
// />
// );
// }

View File

@@ -4,9 +4,9 @@ import React from "react";
import { useTranslation } from "react-i18next";
import AlertComponent from "../alert/alert.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import "./job-invoices-total.styles.scss";
import "./job-bills-total.styles.scss";
export default function JobInvoiceTotals({ loading, invoices, jobTotals }) {
export default function JobBillsTotalComponent({ loading, bills, jobTotals }) {
const { t } = useTranslation();
if (loading) return <LoadingSkeleton />;
@@ -17,10 +17,10 @@ export default function JobInvoiceTotals({ loading, invoices, jobTotals }) {
const totals = jobTotals;
let invoiceTotals = Dinero({ amount: 0 });
invoices.forEach((i) =>
i.invoicelines.forEach((il) => {
invoiceTotals = invoiceTotals.add(
let billTotals = Dinero({ amount: 0 });
bills.forEach((i) =>
i.billlines.forEach((il) => {
billTotals = billTotals.add(
Dinero({
amount: Math.round(
(il.actual_cost || 0) * (i.is_credit_memo ? -1 : 1) * 100
@@ -30,10 +30,10 @@ export default function JobInvoiceTotals({ loading, invoices, jobTotals }) {
})
);
const discrepancy = Dinero(totals.parts.parts.total).subtract(invoiceTotals);
const discrepancy = Dinero(totals.parts.parts.total).subtract(billTotals);
return (
<div className="job-invoices-totals-container">
<div className="job-bills-totals-container">
<Statistic
title={t("jobs.labels.partstotal")}
value={Dinero(totals.parts.parts.total).toFormat()}
@@ -43,11 +43,11 @@ export default function JobInvoiceTotals({ loading, invoices, jobTotals }) {
value={Dinero(totals.parts.sublets.total).toFormat()}
/>
<Statistic
title={t("invoices.labels.retailtotal")}
value={invoiceTotals.toFormat()}
title={t("bills.labels.retailtotal")}
value={billTotals.toFormat()}
/>
<Statistic
title={t("invoices.labels.discrepancy")}
title={t("bills.labels.discrepancy")}
valueStyle={{
color: discrepancy.getAmount === 0 ? "green" : "red",
}}

View File

@@ -1,4 +1,4 @@
.job-invoices-totals-container {
.job-bills-totals-container {
margin: 0rem 2rem;
display: flex;
flex-direction: column;

View File

@@ -51,28 +51,25 @@ export function JobCostingModalComponent({ bodyshop, job }) {
{ parts: {}, labor: {} }
);
const invoiceTotalsByProfitCenter = job.invoices.reduce(
(inv_acc, inv_val) => {
//At the invoice level.
inv_val.invoicelines.map((line_val) => {
//At the invoice line level.
//console.log("JobCostingPartsTable -> line_val", line_val);
if (!!!inv_acc[line_val.cost_center])
inv_acc[line_val.cost_center] = Dinero();
const billTotalsByProfitCenter = job.bills.reduce((bill_acc, bill_val) => {
//At the invoice level.
bill_val.billlines.map((line_val) => {
//At the invoice line level.
//console.log("JobCostingPartsTable -> line_val", line_val);
if (!!!bill_acc[line_val.cost_center])
bill_acc[line_val.cost_center] = Dinero();
inv_acc[line_val.cost_center] = inv_acc[line_val.cost_center].add(
Dinero({
amount: Math.round((line_val.actual_cost || 0) * 100),
})
.multiply(line_val.quantity)
.multiply(inv_val.is_credit_memo ? -1 : 1)
);
return null;
});
return inv_acc;
},
{}
);
bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add(
Dinero({
amount: Math.round((line_val.actual_cost || 0) * 100),
})
.multiply(line_val.quantity)
.multiply(bill_val.is_credit_memo ? -1 : 1)
);
return null;
});
return bill_acc;
}, {});
const ticketTotalsByProfitCenter = job.timetickets.reduce(
(ticket_acc, ticket_val) => {
@@ -114,12 +111,11 @@ export function JobCostingModalComponent({ bodyshop, job }) {
const cost_labor =
ticketTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 });
const cost_parts =
invoiceTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 });
const cost_parts = billTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 });
const cost = (
invoiceTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 })
).add(ticketTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 }));
const cost = (billTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 })).add(
ticketTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 })
);
const totalSales = sale_labor.add(sale_parts);
const gpdollars = totalSales.subtract(cost);
const gppercent = (

View File

@@ -5,8 +5,8 @@ import { useTranslation } from "react-i18next";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters";
export default function JobReconciliationInvoiceTable({
invoiceLineState,
export default function JobReconciliationBillsTable({
billLineState,
invoiceLineData,
}) {
const { t } = useTranslation();
@@ -15,12 +15,12 @@ export default function JobReconciliationInvoiceTable({
sortedInfo: {},
});
const [selectedLines, setSelectedLines] = invoiceLineState;
const [selectedLines, setSelectedLines] = billLineState;
const [total, setTotal] = useState(Dinero({ amount: 0 }).toFormat());
const columns = [
{
title: t("invoicelines.fields.line_desc"),
title: t("billlines.fields.line_desc"),
dataIndex: "line_desc",
key: "line_desc",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
@@ -28,7 +28,7 @@ export default function JobReconciliationInvoiceTable({
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
},
{
title: t("invoicelines.fields.retail"),
title: t("billlines.fields.retail"),
dataIndex: "actual_price",
key: "actual_price",
sorter: (a, b) => a.actual_price - b.actual_price,
@@ -39,7 +39,7 @@ export default function JobReconciliationInvoiceTable({
),
},
{
title: t("invoicelines.fields.actual_cost"),
title: t("billlines.fields.actual_cost"),
dataIndex: "actual_cost",
key: "actual_cost",
sorter: (a, b) => a.actual_cost - b.actual_cost,
@@ -50,7 +50,7 @@ export default function JobReconciliationInvoiceTable({
),
},
{
title: t("invoicelines.fields.quantity"),
title: t("billlines.fields.quantity"),
dataIndex: "quantity",
key: "quantity",
sorter: (a, b) => a.quantity - b.quantity,
@@ -58,7 +58,7 @@ export default function JobReconciliationInvoiceTable({
state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order,
},
{
title: t("invoices.fields.is_credit_memo"),
title: t("bills.fields.is_credit_memo"),
dataIndex: "is_credit_memo",
key: "is_credit_memo",
sorter: (a, b) => a.is_credit_memo - b.is_credit_memo,
@@ -95,11 +95,11 @@ export default function JobReconciliationInvoiceTable({
return (
<div>
<Table
size='small'
size="small"
title={() => <div></div>}
pagination={{ position: "top", defaultPageSize: 25 }}
columns={columns}
rowKey='id'
rowKey="id"
dataSource={invoiceLineData}
onChange={handleTableChange}
rowSelection={{
@@ -107,7 +107,7 @@ export default function JobReconciliationInvoiceTable({
selectedRowKeys: selectedLines,
}}
/>
<Statistic value={total} title='total' />
<Statistic value={total} title="total" />
</div>
);
}

View File

@@ -1,16 +1,16 @@
import { Col, Row } from "antd";
import React, { useState } from "react";
import JobReconciliationInvoicesTable from "../job-reconciliation-invoices-table/job-reconciliation-invoices-table.component";
import JobReconciliationBillsTable from "../job-reconciliation-bills-table/job-reconciliation-bills-table.component";
import JobReconciliationPartsTable from "../job-reconciliation-parts-table/job-reconciliation-parts-table.component";
export default function JobReconciliationModalComponent({ job, invoices }) {
export default function JobReconciliationModalComponent({ job, bills }) {
const jobLineState = useState([]);
const invoiceLineState = useState([]);
const billLineState = useState([]);
const invoiceLineData =
invoices
bills
.map((i) =>
i.invoicelines.map((il) => {
i.billlines.map((il) => {
return { ...il, is_credit_memo: i.is_credit_memo };
})
)
@@ -28,9 +28,9 @@ export default function JobReconciliationModalComponent({ job, invoices }) {
/>
</Col>
<Col span={12}>
<JobReconciliationInvoicesTable
<JobReconciliationBillsTable
invoiceLineData={invoiceLineData}
invoiceLineState={invoiceLineState}
billLineState={billLineState}
/>
</Col>
</Row>

View File

@@ -5,27 +5,22 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectReconciliation } from "../../redux/modals/modals.selectors";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import JobReconciliationModalComponent from "./job-reconciliation-modal.component";
const mapStateToProps = createStructuredSelector({
reconciliationModal: selectReconciliation,
bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("reconciliation")),
});
function InvoiceEnterModalContainer({
function JobReconciliationModalContainer({
reconciliationModal,
toggleModalVisible,
bodyshop,
currentUser,
}) {
const { t } = useTranslation();
const { context, visible } = reconciliationModal;
const { job, invoices } = context;
const { job, bills } = context;
const handleCancel = () => {
toggleModalVisible();
@@ -39,8 +34,9 @@ function InvoiceEnterModalContainer({
okText={t("general.actions.save")}
onOk={handleCancel}
onCancel={handleCancel}
destroyOnClose>
<JobReconciliationModalComponent job={job} invoices={invoices} />
destroyOnClose
>
<JobReconciliationModalComponent job={job} bills={bills} />
</Modal>
);
}
@@ -48,4 +44,4 @@ function InvoiceEnterModalContainer({
export default connect(
mapStateToProps,
mapDispatchToProps
)(InvoiceEnterModalContainer);
)(JobReconciliationModalContainer);

View File

@@ -20,8 +20,8 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({
setScheduleContext: (context) =>
dispatch(setModalContext({ context: context, modal: "schedule" })),
setInvoiceEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "invoiceEnter" })),
setBillEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "billEnter" })),
setPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "payment" })),
setJobCostingContext: (context) =>
@@ -33,7 +33,7 @@ export function JobsDetailHeaderActions({
bodyshop,
refetch,
setScheduleContext,
setInvoiceEnterContext,
setBillEnterContext,
setPaymentContext,
setJobCostingContext,
}) {
@@ -147,11 +147,11 @@ export function JobsDetailHeaderActions({
</Popconfirm>
</Menu.Item>
<Menu.Item
key="postinvoices"
key="postbills"
onClick={() => {
logImEXEvent("job_header_enter_invoice");
logImEXEvent("job_header_enter_bills");
setInvoiceEnterContext({
setBillEnterContext({
actions: { refetch: refetch },
context: {
job: job,
@@ -159,7 +159,7 @@ export function JobsDetailHeaderActions({
});
}}
>
{t("jobs.actions.postInvoices")}
{t("jobs.actions.postbills")}
</Menu.Item>
<Menu.Item
disabled={!!job.date_invoiced || !jobInPostProduction}

View File

@@ -1,8 +1,8 @@
import { Col, Row } from "antd";
import React from "react";
import AlertComponent from "../alert/alert.component";
import InvoicesListTableComponent from "../invoices-list-table/invoices-list-table.component";
import JobInvoicesTotalsComponent from "../job-invoices-total/job-invoices-total.component";
import BillsListTable from "../bills-list-table/bills-list-table.component";
import JobBillsTotal from "../job-bills-total/job-bills-total.component";
import PartsOrderModal from "../parts-order-modal/parts-order-modal.container";
import PartsOrderListTableComponent from "../parts-order-list-table/parts-order-list-table.component";
const tableCol = {
@@ -25,37 +25,33 @@ const totalsCol = {
export default function JobsDetailPliComponent({
job,
invoicesQuery,
handleInvoiceOnRowClick,
billsQuery,
handleBillOnRowClick,
handlePartsOrderOnRowClick,
selectedInvoice,
}) {
return (
<div>
<PartsOrderModal />
{invoicesQuery.error ? (
<AlertComponent message={invoicesQuery.error.message} type="error" />
{billsQuery.error ? (
<AlertComponent message={billsQuery.error.message} type="error" />
) : null}
<Row>
<Col {...tableCol}>
<PartsOrderListTableComponent
job={job}
loading={invoicesQuery.loading}
handleOnRowClick={handlePartsOrderOnRowClick}
selectedInvoice={selectedInvoice}
invoicesQuery={invoicesQuery}
billsQuery={billsQuery}
/>
<InvoicesListTableComponent
<BillsListTable
job={job}
loading={invoicesQuery.loading}
handleOnRowClick={handleInvoiceOnRowClick}
invoicesQuery={invoicesQuery}
handleOnRowClick={handleBillOnRowClick}
billsQuery={billsQuery}
/>
</Col>
<Col {...totalsCol}>
<JobInvoicesTotalsComponent
invoices={invoicesQuery.data ? invoicesQuery.data.invoices : []}
loading={invoicesQuery.loading}
<JobBillsTotal
bills={billsQuery.data ? billsQuery.data.bills : []}
loading={billsQuery.loading}
jobTotals={job.job_totals}
/>
</Col>

View File

@@ -2,25 +2,25 @@ import { useQuery } from "@apollo/react-hooks";
import queryString from "query-string";
import React from "react";
import { useHistory, useLocation } from "react-router-dom";
import { QUERY_INVOICES_BY_JOBID } from "../../graphql/invoices.queries";
import { QUERY_BILLS_BY_JOBID } from "../../graphql/bills.queries";
import JobsDetailPliComponent from "./jobs-detail-pli.component";
export default function JobsDetailPliContainer({ job }) {
const invoicesQuery = useQuery(QUERY_INVOICES_BY_JOBID, {
const billsQuery = useQuery(QUERY_BILLS_BY_JOBID, {
variables: { jobid: job.id },
});
const search = queryString.parse(useLocation().search);
const history = useHistory();
const handleInvoiceOnRowClick = (record) => {
const handleBillOnRowClick = (record) => {
if (record) {
if (record.id) {
search.invoiceid = record.id;
search.billid = record.id;
history.push({ search: queryString.stringify(search) });
}
} else {
delete search.invoiceid;
delete search.billid;
history.push({ search: queryString.stringify(search) });
}
};
@@ -40,8 +40,8 @@ export default function JobsDetailPliContainer({ job }) {
return (
<JobsDetailPliComponent
job={job}
invoicesQuery={invoicesQuery}
handleInvoiceOnRowClick={handleInvoiceOnRowClick}
billsQuery={billsQuery}
handleBillOnRowClick={handleBillOnRowClick}
handlePartsOrderOnRowClick={handlePartsOrderOnRowClick}
/>
);

View File

@@ -8,8 +8,8 @@ function JobsDocumentsComponent({
data,
jobId,
refetch,
invoiceId,
invoicesCallback,
billId,
billsCallback,
}) {
const [galleryImages, setgalleryImages] = useState([]);
@@ -34,18 +34,18 @@ function JobsDocumentsComponent({
}, [data, setgalleryImages]);
return (
<div className='clearfix'>
<div className="clearfix">
<DocumentsUploadContainer
jobId={jobId}
invoiceId={invoiceId}
callbackAfterUpload={invoicesCallback || refetch}
billId={billId}
callbackAfterUpload={billsCallback || refetch}
tagsArray={["test"]}
/>
<JobsDocumentsDownloadButton galleryImages={galleryImages} />
<JobsDocumentsDeleteButton
galleryImages={galleryImages}
deletionCallback={invoicesCallback || refetch}
deletionCallback={billsCallback || refetch}
/>
<Gallery

View File

@@ -7,14 +7,14 @@ import JobDocuments from "./jobs-documents-gallery.component";
export default function JobsDocumentsContainer({
jobId,
invoiceId,
billId,
documentsList,
invoicesCallback,
billsCallback,
}) {
const { loading, error, data, refetch } = useQuery(GET_DOCUMENTS_BY_JOB, {
variables: { jobId: jobId },
fetchPolicy: "network-only",
skip: !!invoiceId,
skip: !!billId,
});
if (loading) return <LoadingSpinner />;
@@ -23,10 +23,10 @@ export default function JobsDocumentsContainer({
return (
<JobDocuments
data={(data && data.documents) || documentsList || []}
invoiceId={invoiceId}
billId={billId}
jobId={jobId}
refetch={refetch}
invoicesCallback={invoicesCallback}
billsCallback={billsCallback}
/>
);
}

View File

@@ -110,7 +110,6 @@ export function JobsExportAllButton({
});
}
}
//Set the list of selected invoices to be nothing.
if (!!completedCallback) completedCallback([]);
if (!!loadingCallback) loadingCallback(false);
setLoading(false);

View File

@@ -20,16 +20,16 @@ const mapStateToProps = createStructuredSelector({
});
const mapDispatchToProps = (dispatch) => ({
setInvoiceEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "invoiceEnter" })),
setBillEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "billEnter" })),
});
export function PartsOrderListTableComponent({
setInvoiceEnterContext,
setBillEnterContext,
bodyshop,
job,
loading,
invoicesQuery,
billsQuery,
handleOnRowClick,
}) {
const responsibilityCenters = bodyshop.md_responsibility_centers;
@@ -41,10 +41,8 @@ export function PartsOrderListTableComponent({
const search = queryString.parse(useLocation().search);
const selectedpartsorder = search.partsorderid;
const parts_orders = invoicesQuery.data
? invoicesQuery.data.parts_orders
: [];
const { refetch } = invoicesQuery;
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
const { refetch } = billsQuery;
const columns = [
{
title: t("vendors.fields.name"),
@@ -93,15 +91,15 @@ export function PartsOrderListTableComponent({
render: (text, record) => (
<Button
onClick={() => {
logImEXEvent("parts_order_receive_invoice");
logImEXEvent("parts_order_receive_bill");
setInvoiceEnterContext({
setBillEnterContext({
actions: { refetch: refetch },
context: {
job: job,
invoice: {
bill: {
vendorid: record.vendor.id,
invoicelines: record.parts_order_lines.map((pol) => {
billlines: record.parts_order_lines.map((pol) => {
return {
joblineid: pol.job_line_id,
line_desc: pol.line_desc,
@@ -119,7 +117,7 @@ export function PartsOrderListTableComponent({
});
}}
>
{t("parts_orders.actions.receiveinvoice")}
{t("parts_orders.actions.receivebill")}
</Button>
),
},
@@ -227,7 +225,7 @@ export function PartsOrderListTableComponent({
{t("parts_orders.labels.parts_orders")}
</Typography.Title>
<Table
loading={loading}
loading={billsQuery.loading}
size="small"
title={() => (
<div className="imex-table-header">

View File

@@ -4,7 +4,7 @@ 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 { INSERT_NEW_BILL } from "../../graphql/bills.queries";
import { UPDATE_JOB_LINE_STATUS } from "../../graphql/jobs-lines.queries";
import { INSERT_NEW_PARTS_ORDERS } from "../../graphql/parts-orders.queries";
import { QUERY_ALL_VENDORS_FOR_ORDER } from "../../graphql/vendors.queries";
@@ -68,7 +68,7 @@ export function PartsOrderModalContainer({
const [insertPartOrder] = useMutation(INSERT_NEW_PARTS_ORDERS);
const [updateJobLines] = useMutation(UPDATE_JOB_LINE_STATUS);
const [insertInvoice] = useMutation(INSERT_NEW_INVOICE);
const [insertBill] = useMutation(INSERT_NEW_BILL);
const handleFinish = async (values) => {
logImEXEvent("parts_order_insert");
@@ -120,9 +120,9 @@ export function PartsOrderModalContainer({
jobid: jobId,
total: 0,
invoice_number: `${jobId}`,
federal_tax_rate: bodyshop.invoice_tax_rates.federal_tax_rate || 0,
state_tax_rate: bodyshop.invoice_tax_rates.state_tax_rate || 0,
local_tax_rate: bodyshop.invoice_tax_rates.local_tax_rate || 0,
federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate || 0,
state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate || 0,
local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate || 0,
invoicelines: {
data: values.parts_order_lines.data.map((p) => {
return {
@@ -138,8 +138,8 @@ export function PartsOrderModalContainer({
},
};
await insertInvoice({
variables: { invoice: invoiceToPost },
await insertBill({
variables: { bill: invoiceToPost },
});
}

View File

@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { auth } from "../../firebase/firebase.utils";
import { UPDATE_INVOICES } from "../../graphql/invoices.queries";
import { UPDATE_BILLS } from "../../graphql/bills.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils";
@@ -14,15 +14,15 @@ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
export function InvoiceExportAllButton({
export function PayableExportAll({
bodyshop,
invoiceIds,
billids,
disabled,
loadingCallback,
completedCallback,
}) {
const { t } = useTranslation();
const [updateInvoice] = useMutation(UPDATE_INVOICES);
const [updateBill] = useMutation(UPDATE_BILLS);
const [loading, setLoading] = useState(false);
const handleQbxml = async () => {
@@ -35,7 +35,7 @@ export function InvoiceExportAllButton({
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/payables",
{ invoices: invoiceIds },
{ bills: billids },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken(true)}`,
@@ -45,7 +45,7 @@ export function InvoiceExportAllButton({
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("invoices.errors.exporting", {
message: t("bills.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
@@ -64,7 +64,7 @@ export function InvoiceExportAllButton({
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("invoices.errors.exporting-partner"),
message: t("bills.errors.exporting-partner"),
});
if (!!loadingCallback) loadingCallback(false);
setLoading(false);
@@ -80,36 +80,35 @@ export function InvoiceExportAllButton({
//Uh oh. At least one was no good.
failedTransactions.map((ft) =>
notification["error"]({
message: t("invoices.errors.exporting", {
message: t("bills.errors.exporting", {
error: ft.errorMessage || "",
}),
})
);
}
if (successfulTransactions.length > 0) {
const invoiceUpdateResponse = await updateInvoice({
const billUpdateResponse = await updateBill({
variables: {
invoiceIdList: successfulTransactions.map((st) => st.id),
invoice: {
billIdList: successfulTransactions.map((st) => st.id),
bill: {
exported: true,
exported_at: new Date(),
},
},
});
if (!!!invoiceUpdateResponse.errors) {
if (!!!billUpdateResponse.errors) {
notification["success"]({
message: t("jobs.successes.exported"),
});
} else {
notification["error"]({
message: t("jobs.errors.exporting", {
error: JSON.stringify(invoiceUpdateResponse.error),
error: JSON.stringify(billUpdateResponse.error),
}),
});
}
}
//Set the list of selected invoices to be nothing.
if (!!completedCallback) completedCallback([]);
if (!!loadingCallback) loadingCallback(false);
setLoading(false);
@@ -127,4 +126,4 @@ export function InvoiceExportAllButton({
);
}
export default connect(mapStateToProps, null)(InvoiceExportAllButton);
export default connect(mapStateToProps, null)(PayableExportAll);

View File

@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { auth } from "../../firebase/firebase.utils";
import { UPDATE_INVOICES } from "../../graphql/invoices.queries";
import { UPDATE_BILLS } from "../../graphql/bills.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils";
@@ -14,14 +14,14 @@ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
export function InvoiceExportButton({
export function PayableExportButton({
bodyshop,
invoiceId,
billId,
disabled,
loadingCallback,
}) {
const { t } = useTranslation();
const [updateInvoice] = useMutation(UPDATE_INVOICES);
const [updateBill] = useMutation(UPDATE_BILLS);
const [loading, setLoading] = useState(false);
const handleQbxml = async () => {
@@ -34,7 +34,7 @@ export function InvoiceExportButton({
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/payables",
{ invoices: [invoiceId] },
{ bills: [billId] },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken(true)}`,
@@ -44,7 +44,7 @@ export function InvoiceExportButton({
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("invoices.errors.exporting", {
message: t("bills.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
@@ -63,7 +63,7 @@ export function InvoiceExportButton({
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("invoices.errors.exporting-partner"),
message: t("bills.errors.exporting-partner"),
});
if (!!loadingCallback) loadingCallback(false);
setLoading(false);
@@ -79,30 +79,30 @@ export function InvoiceExportButton({
//Uh oh. At least one was no good.
failedTransactions.map((ft) =>
notification["error"]({
message: t("invoices.errors.exporting", {
message: t("bills.errors.exporting", {
error: ft.errorMessage || "",
}),
})
);
}
if (successfulTransactions.length > 0) {
const invoiceUpdateResponse = await updateInvoice({
const billUpdateResponse = await updateBill({
variables: {
invoiceIdList: successfulTransactions.map((st) => st.id),
invoice: {
billIdList: successfulTransactions.map((st) => st.id),
bill: {
exported: true,
exported_at: new Date(),
},
},
});
if (!!!invoiceUpdateResponse.errors) {
if (!!!billUpdateResponse.errors) {
notification["success"]({
message: t("jobs.successes.exported"),
});
} else {
notification["error"]({
message: t("jobs.errors.exporting", {
error: JSON.stringify(invoiceUpdateResponse.error),
error: JSON.stringify(billUpdateResponse.error),
}),
});
}
@@ -124,4 +124,4 @@ export function InvoiceExportButton({
);
}
export default connect(mapStateToProps, null)(InvoiceExportButton);
export default connect(mapStateToProps, null)(PayableExportButton);

View File

@@ -25,7 +25,7 @@ export function PaymentFormComponent({ form, stripeStateArr, bodyshop }) {
<div>
<Form.Item
name="jobid"
label={t("invoices.fields.ro_number")}
label={t("bills.fields.ro_number")}
rules={[
{
required: true,

View File

@@ -32,7 +32,7 @@ const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("payment")),
});
function InvoiceEnterModalContainer({
function BillEnterModalContainer({
paymentModal,
toggleModalVisible,
bodyshop,
@@ -189,7 +189,7 @@ function InvoiceEnterModalContainer({
export default connect(
mapStateToProps,
mapDispatchToProps
)(InvoiceEnterModalContainer);
)(BillEnterModalContainer);
// const pr = stripe.paymentRequest({
// country: "CA",

View File

@@ -22,9 +22,9 @@ export default {
"jobs:close": 5,
"jobs:detail": 1,
"invoices:enter": 2,
"invoices:view": 2,
"invoices:list": 2,
"bills:enter": 2,
"bills:view": 2,
"bills:list": 2,
"employees:page": 5,

View File

@@ -137,7 +137,7 @@ export default function ShopInfoComponent({ form, saveLoading }) {
<LayoutFormRow>
<Form.Item
label={t("bodyshop.fields.invoice_federal_tax_rate")}
name={["invoice_tax_rates", "federal_tax_rate"]}
name={["bill_tax_rates", "federal_tax_rate"]}
rules={[
{
required: true,
@@ -149,7 +149,7 @@ export default function ShopInfoComponent({ form, saveLoading }) {
</Form.Item>
<Form.Item
label={t("bodyshop.fields.invoice_state_tax_rate")}
name={["invoice_tax_rates", "state_tax_rate"]}
name={["bill_tax_rates", "state_tax_rate"]}
rules={[
{
required: true,
@@ -161,7 +161,7 @@ export default function ShopInfoComponent({ form, saveLoading }) {
</Form.Item>
<Form.Item
label={t("bodyshop.fields.invoice_local_tax_rate")}
name={["invoice_tax_rates", "local_tax_rate"]}
name={["bill_tax_rates", "local_tax_rate"]}
rules={[
{
required: true,

View File

@@ -238,38 +238,38 @@ export default function ShopInfoRbacComponent({ form }) {
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.invoices.enter")}
label={t("bodyshop.fields.rbac.bills.enter")}
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
name={["md_rbac", "invoices:enter"]}
name={["md_rbac", "bills:enter"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.invoices.view")}
label={t("bodyshop.fields.rbac.bills.view")}
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
name={["md_rbac", "invoices:view"]}
name={["md_rbac", "bills:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.invoices.list")}
label={t("bodyshop.fields.rbac.bills.list")}
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
name={["md_rbac", "invoices:list"]}
name={["md_rbac", "bills:list"]}
>
<InputNumber />
</Form.Item>

View File

@@ -58,7 +58,7 @@ export default function VendorsFormComponent({
}}
style={{ width: "100%" }}
>
{t("invoicelines.actions.newline")}
{t("billlines.actions.newline")}
</Button>
</Form.Item>
</div>

View File

@@ -28,9 +28,9 @@ export const QUERY_JOBS_FOR_EXPORT = gql`
}
`;
export const QUERY_INVOICES_FOR_EXPORT = gql`
query QUERY_INVOICES_FOR_EXPORT {
invoices(where: { exported: { _eq: false } }) {
export const QUERY_BILLS_FOR_EXPORT = gql`
query QUERY_BILLS_FOR_EXPORT {
bills(where: { exported: { _eq: false } }) {
id
exported
date

View File

@@ -0,0 +1,14 @@
import gql from "graphql-tag";
export const UPDATE_BILL_LINE = gql`
mutation UPDATE_BILL_LINE(
$billLineId: uuid!
$billLine: billlines_set_input!
) {
update_billlines(where: { id: { _eq: $billLineId } }, _set: $billLine) {
returning {
id
}
}
}
`;

View File

@@ -1,8 +1,8 @@
import gql from "graphql-tag";
export const INSERT_NEW_INVOICE = gql`
mutation INSERT_NEW_INVOICE($invoice: [invoices_insert_input!]!) {
insert_invoices(objects: $invoice) {
export const INSERT_NEW_BILL = gql`
mutation INSERT_NEW_BILL($bill: [bills_insert_input!]!) {
insert_bills(objects: $bill) {
returning {
id
}
@@ -10,14 +10,14 @@ export const INSERT_NEW_INVOICE = gql`
}
`;
export const QUERY_ALL_INVOICES_PAGINATED = gql`
query QUERY_ALL_INVOICES_PAGINATED(
export const QUERY_ALL_BILLS_PAGINATED = gql`
query QUERY_ALL_BILLS_PAGINATED(
$search: String
$offset: Int
$limit: Int
$order: [invoices_order_by!]!
$order: [bills_order_by!]!
) {
search_invoices(
search_bills(
args: { search: $search }
offset: $offset
limit: $limit
@@ -39,7 +39,7 @@ export const QUERY_ALL_INVOICES_PAGINATED = gql`
id
ro_number
}
invoicelines {
billlines {
actual_price
quantity
actual_cost
@@ -48,7 +48,7 @@ export const QUERY_ALL_INVOICES_PAGINATED = gql`
line_desc
}
}
search_invoices_aggregate(args: { search: $search }) {
search_bills_aggregate(args: { search: $search }) {
aggregate {
count(distinct: true)
}
@@ -56,8 +56,8 @@ export const QUERY_ALL_INVOICES_PAGINATED = gql`
}
`;
export const QUERY_INVOICES_BY_JOBID = gql`
query QUERY_PARTS_INVOICES_BY_JOBID($jobid: uuid!) {
export const QUERY_BILLS_BY_JOBID = gql`
query QUERY_PARTS_BILLS_BY_JOBID($jobid: uuid!) {
parts_orders(
where: { jobid: { _eq: $jobid } }
order_by: { order_date: desc }
@@ -89,7 +89,7 @@ export const QUERY_INVOICES_BY_JOBID = gql`
order_number
user_email
}
invoices(where: { jobid: { _eq: $jobid } }, order_by: { date: desc }) {
bills(where: { jobid: { _eq: $jobid } }, order_by: { date: desc }) {
id
vendorid
vendor {
@@ -103,7 +103,7 @@ export const QUERY_INVOICES_BY_JOBID = gql`
state_tax_rate
local_tax_rate
is_credit_memo
invoicelines {
billlines {
actual_price
quantity
actual_cost
@@ -117,57 +117,39 @@ export const QUERY_INVOICES_BY_JOBID = gql`
}
`;
export const QUERY_INVOICES_BY_VENDOR = gql`
query QUERY_INVOICES_BY_VENDOR($vendorId: uuid!) {
invoices(
where: { vendorid: { _eq: $vendorId } }
order_by: { date: desc }
) {
id
job {
id
ro_number
}
total
invoice_number
date
}
}
`;
// export const QUERY_INVOICES_BY_VENDOR_PAGINATED = gql`
// query QUERY_INVOICES_BY_VENDOR_PAGINATED(
// $vendorId: uuid!
// $offset: Int
// $limit: Int
// $order: [invoices_order_by!]!
// ) {
// invoices(
// where: { vendorid: { _eq: $vendorId } }
// offset: $offset
// limit: $limit
// order_by: $order
// ) {
// id
// job {
// id
// ro_number
// }
// total
// invoice_number
// date
// }
// invoices_aggregate(where: { vendorid: { _eq: $vendorId } }) {
// aggregate {
// count(distinct: true)
// }
// }
// }
// `;
export const QUERY_INVOICES_BY_VENDOR_PAGINATED = gql`
query QUERY_INVOICES_BY_VENDOR_PAGINATED(
$vendorId: uuid!
$offset: Int
$limit: Int
$order: [invoices_order_by!]!
) {
invoices(
where: { vendorid: { _eq: $vendorId } }
offset: $offset
limit: $limit
order_by: $order
) {
id
job {
id
ro_number
}
total
invoice_number
date
}
invoices_aggregate(where: { vendorid: { _eq: $vendorId } }) {
aggregate {
count(distinct: true)
}
}
}
`;
export const QUERY_INVOICE_BY_PK = gql`
query QUERY_INVOICE_BY_PK($invoiceid: uuid!) {
invoices_by_pk(id: $invoiceid) {
export const QUERY_BILL_BY_PK = gql`
query QUERY_BILL_BY_PK($billid: uuid!) {
bills_by_pk(id: $billid) {
due_date
exported
exported_at
@@ -187,7 +169,7 @@ export const QUERY_INVOICE_BY_PK = gql`
name
discount
}
invoicelines {
billlines {
id
line_desc
actual_price
@@ -207,9 +189,9 @@ export const QUERY_INVOICE_BY_PK = gql`
}
`;
export const UPDATE_INVOICE = gql`
mutation UPDATE_INVOICE($invoiceId: uuid!, $invoice: invoices_set_input!) {
update_invoices(where: { id: { _eq: $invoiceId } }, _set: $invoice) {
export const UPDATE_BILL = gql`
mutation UPDATE_BILL($billId: uuid!, $bill: bills_set_input!) {
update_bills(where: { id: { _eq: $billId } }, _set: $bill) {
returning {
id
exported
@@ -219,12 +201,9 @@ export const UPDATE_INVOICE = gql`
}
`;
export const UPDATE_INVOICES = gql`
mutation UPDATE_INVOICES(
$invoiceIdList: [uuid!]!
$invoice: invoices_set_input!
) {
update_invoices(where: { id: { _in: $invoiceIdList } }, _set: $invoice) {
export const UPDATE_BILLS = gql`
mutation UPDATE_BILLS($billIdList: [uuid!]!, $bill: bills_set_input!) {
update_bills(where: { id: { _in: $billIdList } }, _set: $bill) {
returning {
id
exported

View File

@@ -48,7 +48,7 @@ export const QUERY_BODYSHOP = gql`
template_header
textid
production_config
invoice_tax_rates
bill_tax_rates
inhousevendorid
accountingconfig
appt_length
@@ -118,7 +118,7 @@ export const UPDATE_SHOP = gql`
template_header
textid
production_config
invoice_tax_rates
bill_tax_rates
appt_length
stripe_acct_id
ssbuckets

View File

@@ -1,17 +0,0 @@
import gql from "graphql-tag";
export const UPDATE_INVOICE_LINE = gql`
mutation UPDATE_INVOICE_LINE(
$invoicelineId: uuid!
$invoiceLine: invoicelines_set_input!
) {
update_invoicelines(
where: { id: { _eq: $invoicelineId } }
_set: $invoiceLine
) {
returning {
id
}
}
}
`;

View File

@@ -124,8 +124,8 @@ export const UPDATE_JOB_LINE = gql`
}
`;
export const GET_JOB_LINES_TO_ENTER_INVOICE = gql`
query GET_JOB_LINES_TO_ENTER_INVOICE($id: uuid!) {
export const GET_JOB_LINES_TO_ENTER_BILL = gql`
query GET_JOB_LINES_TO_ENTER_BILL($id: uuid!) {
joblines(where: { jobid: { _eq: $id } }) {
id
line_desc

View File

@@ -215,13 +215,13 @@ export const QUERY_JOB_COSTING_DETAILS = gql`
lbr_amt
op_code_desc
}
invoices {
bills {
id
federal_tax_rate
local_tax_rate
state_tax_rate
is_credit_memo
invoicelines {
billlines {
actual_cost
cost_center
id

View File

@@ -42,7 +42,7 @@ export const GLOBAL_SEARCH_QUERY = gql`
memo
transactionid
}
search_invoices(args: { search: $search }) {
search_bills(args: { search: $search }) {
id
date
invoice_number

View File

@@ -5,13 +5,14 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import AccountingPayablesTable from "../../components/accounting-payables-table/accounting-payables-table.component";
import AlertComponent from "../../components/alert/alert.component";
import { QUERY_INVOICES_FOR_EXPORT } from "../../graphql/accounting.queries";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_BILLS_FOR_EXPORT } from "../../graphql/accounting.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
@@ -20,6 +21,7 @@ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function AccountingPayablesContainer({
bodyshop,
setBreadcrumbs,
@@ -38,7 +40,7 @@ export function AccountingPayablesContainer({
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
const { loading, error, data } = useQuery(QUERY_INVOICES_FOR_EXPORT);
const { loading, error, data } = useQuery(QUERY_BILLS_FOR_EXPORT);
if (error) return <AlertComponent message={error.message} type="error" />;
@@ -47,7 +49,7 @@ export function AccountingPayablesContainer({
<RbacWrapper action="accounting:payables">
<AccountingPayablesTable
loadaing={loading}
invoices={data ? data.invoices : []}
bills={data ? data.bills : []}
/>
</RbacWrapper>
</div>

View File

@@ -13,17 +13,17 @@ import { alphaSort } from "../../utils/sorters";
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
setInvoiceEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "invoiceEnter" })),
setBillEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "billEnter" })),
});
export function InvoicesListPage({
export function BillsListPage({
loading,
data,
refetch,
total,
setPartsOrderContext,
setInvoiceEnterContext,
setBillEnterContext,
}) {
const { t } = useTranslation();
const [state, setState] = useState({
@@ -35,7 +35,7 @@ export function InvoicesListPage({
const columns = [
{
title: t("invoices.fields.vendorname"),
title: t("bills.fields.vendorname"),
dataIndex: "vendorname",
key: "vendorname",
// sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
@@ -44,7 +44,7 @@ export function InvoicesListPage({
render: (text, record) => <span>{record.vendor.name}</span>,
},
{
title: t("invoices.fields.invoice_number"),
title: t("bills.fields.invoice_number"),
dataIndex: "invoice_number",
key: "invoice_number",
sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number),
@@ -53,7 +53,7 @@ export function InvoicesListPage({
state.sortedInfo.order,
},
{
title: t("invoices.fields.date"),
title: t("bills.fields.date"),
dataIndex: "date",
key: "date",
sorter: (a, b) => a.date - b.date,
@@ -62,7 +62,7 @@ export function InvoicesListPage({
render: (text, record) => <DateFormatter>{record.date}</DateFormatter>,
},
{
title: t("invoices.fields.total"),
title: t("bills.fields.total"),
dataIndex: "total",
key: "total",
sorter: (a, b) => a.total - b.total,
@@ -73,7 +73,7 @@ export function InvoicesListPage({
),
},
{
title: t("invoices.fields.is_credit_memo"),
title: t("bills.fields.is_credit_memo"),
dataIndex: "is_credit_memo",
key: "is_credit_memo",
sorter: (a, b) => a.is_credit_memo - b.is_credit_memo,
@@ -88,8 +88,8 @@ export function InvoicesListPage({
key: "actions",
render: (text, record) => (
<div>
<Link to={`/manage/invoices?invoiceid=${record.id}`}>
<Button>{t("invoices.actions.edit")}</Button>
<Link to={`/manage/bills?billid=${record.id}`}>
<Button>{t("bills.actions.edit")}</Button>
</Link>
<Button
disabled={record.is_credit_memo}
@@ -99,9 +99,9 @@ export function InvoicesListPage({
context: {
jobId: record.jobid,
vendorId: record.vendorid,
returnFromInvoice: record.id,
returnFromBill: record.id,
invoiceNumber: record.invoice_number,
linesToOrder: record.invoicelines.map((i) => {
linesToOrder: record.billlines.map((i) => {
return {
line_desc: i.line_desc,
db_price: i.actual_price,
@@ -115,7 +115,7 @@ export function InvoicesListPage({
})
}
>
{t("invoices.actions.return")}
{t("bills.actions.return")}
</Button>
</div>
),
@@ -132,9 +132,7 @@ export function InvoicesListPage({
return (
<div>
<Typography.Title level={4}>
{t("invoices.labels.invoices")}
</Typography.Title>
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
<Table
loading={loading}
size="small"
@@ -145,13 +143,13 @@ export function InvoicesListPage({
</Button>
<Button
onClick={() => {
setInvoiceEnterContext({
setBillEnterContext({
actions: { refetch: refetch },
context: {},
});
}}
>
{t("jobs.actions.postInvoices")}
{t("jobs.actions.postbills")}
</Button>
<div className="imex-table-header__search">
<Input.Search
@@ -179,4 +177,4 @@ export function InvoicesListPage({
</div>
);
}
export default connect(null, mapDispatchToProps)(InvoicesListPage);
export default connect(null, mapDispatchToProps)(BillsListPage);

View File

@@ -4,13 +4,13 @@ import { useQuery } from "react-apollo";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import InvoiceDetailEditContainer from "../../components/invoice-detail-edit/invoice-detail-edit.container";
import { QUERY_ALL_INVOICES_PAGINATED } from "../../graphql/invoices.queries";
import BillDetailEditContainer from "../../components/bill-detail-edit/bill-detail-edit.container";
import { QUERY_ALL_BILLS_PAGINATED } from "../../graphql/bills.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import InvoicesPageComponent from "./invoices.page.component";
import BillsPageComponent from "./bills.page.component";
import AlertComponent from "../../components/alert/alert.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
@@ -19,21 +19,21 @@ const mapDispatchToProps = (dispatch) => ({
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function InvoicesPageContainer({ setBreadcrumbs, setSelectedHeader }) {
export function BillsPageContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder, search } = searchParams;
useEffect(() => {
document.title = t("titles.invoices-list");
setSelectedHeader("invoices");
document.title = t("titles.bills-list");
setSelectedHeader("bills");
setBreadcrumbs([
{ link: "/manage/invoices", label: t("titles.bc.invoices-list") },
{ link: "/manage/bills", label: t("titles.bc.bills-list") },
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
const { loading, error, data, refetch } = useQuery(
QUERY_ALL_INVOICES_PAGINATED,
QUERY_ALL_BILLS_PAGINATED,
{
variables: {
search: search || "",
@@ -54,18 +54,18 @@ export function InvoicesPageContainer({ setBreadcrumbs, setSelectedHeader }) {
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<RbacWrapper action="invoices:list">
<RbacWrapper action="bills:list">
<div>
<InvoicesPageComponent
data={data ? data.search_invoices : []}
<BillsPageComponent
data={data ? data.search_bills : []}
loading={loading}
refetch={refetch}
total={data ? data.search_invoices_aggregate.aggregate.count : 0}
total={data ? data.search_bills_aggregate.aggregate.count : 0}
/>
<InvoiceDetailEditContainer />
<BillDetailEditContainer />
</div>
</RbacWrapper>
);
}
export default connect(null, mapDispatchToProps)(InvoicesPageContainer);
export default connect(null, mapDispatchToProps)(BillsPageContainer);

View File

@@ -74,15 +74,13 @@ const ContractDetailPage = lazy(() =>
const ContractsList = lazy(() =>
import("../contracts/contracts.page.container")
);
const InvoicesListPage = lazy(() =>
import("../invoices/invoices.page.container")
);
const BillsListPage = lazy(() => import("../bills/bills.page.container"));
const JobCostingModal = lazy(() =>
import("../../components/job-costing-modal/job-costing-modal.container")
);
const EnterInvoiceModalContainer = lazy(() =>
import("../../components/invoice-enter-modal/invoice-enter-modal.container")
const BillEnterModalContainer = lazy(() =>
import("../../components/bill-enter-modal/bill-enter-modal.container")
);
const TimeTicketModalContainer = lazy(() =>
import("../../components/time-ticket-modal/time-ticket-modal.container")
@@ -174,7 +172,7 @@ export function Manage({ match, conflict }) {
<PaymentModalContainer />
</Elements>
<BreadCrumbs />
<EnterInvoiceModalContainer />
<BillEnterModalContainer />
<JobCostingModal />
<EmailOverlayContainer />
<TimeTicketModalContainer />
@@ -280,8 +278,8 @@ export function Manage({ match, conflict }) {
/>
<Route
exact
path={`${match.path}/invoices`}
component={InvoicesListPage}
path={`${match.path}/bills`}
component={BillsListPage}
/>
<Route
exact

View File

@@ -10,7 +10,7 @@ const baseModal = {
const INITIAL_STATE = {
jobLineEdit: { ...baseModal },
invoiceEnter: { ...baseModal },
billEnter: { ...baseModal },
courtesyCarReturn: { ...baseModal },
noteUpsert: { ...baseModal },
schedule: { ...baseModal },

View File

@@ -7,9 +7,9 @@ export const selectJobLineEditModal = createSelector(
(modals) => modals.jobLineEdit
);
export const selectInvoiceEnterModal = createSelector(
export const selectBillEnterModal = createSelector(
[selectModals],
(modals) => modals.invoiceEnter
(modals) => modals.billEnter
);
export const selectCourtesyCarReturn = createSelector(

View File

@@ -73,6 +73,76 @@
"values": "Values"
}
},
"billlines": {
"actions": {
"newline": "New Line"
},
"fields": {
"actual": "Actual",
"actual_cost": "Actual Cost",
"cost_center": "Cost Center",
"federal_tax_applicable": "Fed. Tax?",
"jobline": "Job Line",
"line_desc": "Line Description",
"local_tax_applicable": "Loc. Tax?",
"quantity": "Quantity",
"retail": "Retail",
"state_tax_applicable": "St. Tax?"
},
"labels": {
"entered": "Entered",
"other": "--Not On Estimate--",
"reconciled": "Reconciled!",
"unreconciled": "Unreconciled"
}
},
"bills": {
"actions": {
"edit": "Edit",
"receive": "Receive Part",
"return": "Return Items"
},
"errors": {
"creating": "Error adding bill.",
"exporting": "Error exporting payable(s). {{error}}",
"exporting-partner": "Unable to connect to ImEX Partner. Please ensure it is running and logged in.",
"invalidro": "Not a valid RO.",
"invalidvendor": "Not a valid vendor.",
"validation": "Please ensure all fields are entered correctly. "
},
"fields": {
"allpartslocation": "Parts Bin",
"date": "Bill Date",
"federal_tax_rate": "Federal Tax Rate",
"invoice_number": "Invoice Number",
"is_credit_memo": "Credit Memo?",
"local_tax_rate": "Local Tax Rate",
"ro_number": "RO Number",
"state_tax_rate": "State Tax Rate",
"total": "Bill Total",
"vendor": "Vendor",
"vendorname": "Vendor Name"
},
"labels": {
"actions": "Actions",
"bill_lines": "Bill Lines",
"bill_total": "Bill Total Amount",
"bills": "Bills",
"discrepancy": "Discrepancy",
"entered_total": "Total of Entered Lines",
"enteringcreditmemo": "You are entering a credit memo. Please ensure you are also entering positive values.",
"federal_tax": "Federal Tax",
"local_tax": "Local Tax",
"new": "New Bill",
"noneselected": "No bill selected.",
"retailtotal": "Retail Total of Bill (Ex. Taxes)",
"state_tax": "State Tax",
"subtotal": "Subtotal"
},
"successes": {
"created": "Invoice added successfully."
}
},
"bodyshop": {
"actions": {
"addbucket": "Add Bucket",
@@ -91,6 +161,9 @@
"address1": "Address 1",
"address2": "Address 2",
"appt_length": "Default Appointment Length",
"bill_federal_tax_rate": "Bills - Federal Tax Rate %",
"bill_local_tax_rate": "Bill - State Tax Rate %",
"bill_state_tax_rate": "Bill - State Tax Rate %",
"city": "City",
"country": "Country",
"dailybodytarget": "Scoreboard - Daily Body Target",
@@ -99,9 +172,6 @@
"enforce_class": "Enforce Class on Conversion?",
"federal_tax_id": "Federal Tax ID (GST/HST)",
"insurance_vendor_id": "Insurance Vendor ID",
"invoice_federal_tax_rate": "Invoices - Federal Tax Rate %",
"invoice_local_tax_rate": "Invoices - State Tax Rate %",
"invoice_state_tax_rate": "Invoices - State Tax Rate %",
"lastnumberworkingdays": "Scoreboard - Last Number of Working Days",
"logo_img_path": "Shop Logo",
"md_categories": "Categories",
@@ -121,6 +191,11 @@
"payments": "Accounting -> Payments",
"receivables": "Accounting -> Receivables"
},
"bills": {
"enter": "Bills -> Enter",
"list": "Bills -> List",
"view": "Bills -> View"
},
"contracts": {
"create": "Contracts -> Create",
"detail": "Contracts -> Detail",
@@ -138,11 +213,6 @@
"employees": {
"page": "Employees -> List"
},
"invoices": {
"enter": "Invoices -> Enter",
"list": "Invoices -> List",
"view": "Invoices -> View"
},
"jobs": {
"available-list": "Jobs -> Available List",
"close": "Jobs -> Close",
@@ -614,76 +684,6 @@
"rescuetitle": "Rescue Me!"
}
},
"invoicelines": {
"actions": {
"newline": "New Line"
},
"fields": {
"actual": "Actual",
"actual_cost": "Actual Cost",
"cost_center": "Cost Center",
"federal_tax_applicable": "Fed. Tax?",
"jobline": "Job Line",
"line_desc": "Invoice Line Description",
"local_tax_applicable": "Loc. Tax?",
"quantity": "Quantity",
"retail": "Retail",
"state_tax_applicable": "St. Tax?"
},
"labels": {
"entered": "Entered",
"other": "--Not On Estimate--",
"reconciled": "Reconciled!",
"unreconciled": "Unreconciled"
}
},
"invoices": {
"actions": {
"edit": "Edit",
"receive": "Receive Part",
"return": "Return Items"
},
"errors": {
"creating": "Error adding invoice.",
"exporting": "Error exporting invoice(s). {{error}}",
"exporting-partner": "Unable to connect to ImEX Partner. Please ensure it is running and logged in.",
"invalidro": "Not a valid RO.",
"invalidvendor": "Not a valid vendor.",
"validation": "Please ensure all fields are entered correctly. "
},
"fields": {
"allpartslocation": "Parts Bin",
"date": "Invoice Date",
"federal_tax_rate": "Federal Tax Rate",
"invoice_number": "Invoice Number",
"is_credit_memo": "Credit Memo?",
"local_tax_rate": "Local Tax Rate",
"ro_number": "RO Number",
"state_tax_rate": "State Tax Rate",
"total": "Invoice Total",
"vendor": "Vendor",
"vendorname": "Vendor Name"
},
"labels": {
"actions": "Actions",
"discrepancy": "Discrepancy",
"entered_total": "Total of Entered Lines",
"enteringcreditmemo": "You are entering a credit memo. Please ensure you are also entering positive values.",
"federal_tax": "Federal Tax",
"invoice_lines": "Invoice Lines",
"invoice_total": "Invoice Total Amount",
"invoices": "Invoices",
"local_tax": "Local Tax",
"new": "New Invoice",
"noneselected": "No invoice selected.",
"retailtotal": "Retail Total of Invoices (Ex. Taxes)",
"state_tax": "State Tax",
"subtotal": "Subtotal"
},
"successes": {
"created": "Invoice added successfully."
}
},
"joblines": {
"actions": {
"new": "New Line"
@@ -778,7 +778,7 @@
"intake": "Intake",
"manualnew": "Create New Job Manually",
"mark": "Mark",
"postInvoices": "Post Invoices",
"postbills": "Post Bills",
"printCenter": "Print Center",
"recalculate": "Recalculate",
"reconcile": "Reconcile",
@@ -1071,17 +1071,17 @@
"alljobs": "All Jobs",
"allpayments": "All Payments",
"availablejobs": "Available Jobs",
"bills": "Bills",
"courtesycars": "Courtesy Cars",
"courtesycars-all": "All Courtesy Cars",
"courtesycars-contracts": "Contracts",
"courtesycars-newcontract": "New Contract",
"customers": "Customers",
"enterinvoices": "Enter Invoices",
"enterbills": "Enter Bills",
"enterpayment": "Enter Payments",
"entertimeticket": "Enter Time Tickets",
"export": "Export",
"home": "Home",
"invoices": "Invoices",
"jobs": "Jobs",
"owners": "Owners",
"productionboard": "Production Board - Visual",
@@ -1221,7 +1221,7 @@
"actions": {
"backordered": "Backordered",
"receive": "Receive",
"receiveinvoice": "Receive Invoice"
"receivebill": "Receive Bill"
},
"errors": {
"backordering": "Error backordering part {{message}}.",
@@ -1451,13 +1451,13 @@
"accounting-payments": "Payments",
"accounting-receivables": "Receivables",
"availablejobs": "Available Jobs",
"bills-list": "Bills",
"contracts": "Contracts",
"contracts-create": "New Contract",
"contracts-detail": "Contract #{{number}}",
"courtesycars": "Courtesy Cars",
"courtesycars-detail": "Courtesy Car {{number}}",
"courtesycars-new": "New Courtesy Car",
"invoices-list": "Invoices",
"jobs": "Jobs",
"jobs-active": "Active Jobs",
"jobs-all": "All Jobs",
@@ -1481,13 +1481,13 @@
"vehicle-details": "Vehicle: {{vehicle}}",
"vehicles": "Vehicles"
},
"bills-list": "Bills | $t(titles.app)",
"contracts": "Courtesy Car Contracts | $t(titles.app)",
"contracts-create": "New Contract | $t(titles.app)",
"contracts-detail": "Contract {{id}} | $t(titles.app)",
"courtesycars": "Courtesy Cars | $t(titles.app)",
"courtesycars-create": "New Courtesy Car | $t(titles.app)",
"courtesycars-detail": "Courtesy Car {{id}} | $t(titles.app)",
"invoices-list": "Invoices | $t(titles.app)",
"jobs": "Active Jobs | $t(titles.app)",
"jobs-all": "All Jobs | $t(titles.app)",
"jobs-close": "Close Job {{number}} | $t(titles.app)",

View File

@@ -73,6 +73,76 @@
"values": ""
}
},
"billlines": {
"actions": {
"newline": ""
},
"fields": {
"actual": "",
"actual_cost": "",
"cost_center": "",
"federal_tax_applicable": "",
"jobline": "",
"line_desc": "",
"local_tax_applicable": "",
"quantity": "",
"retail": "",
"state_tax_applicable": ""
},
"labels": {
"entered": "",
"other": "",
"reconciled": "",
"unreconciled": ""
}
},
"bills": {
"actions": {
"edit": "",
"receive": "",
"return": ""
},
"errors": {
"creating": "",
"exporting": "",
"exporting-partner": "",
"invalidro": "",
"invalidvendor": "",
"validation": ""
},
"fields": {
"allpartslocation": "",
"date": "",
"federal_tax_rate": "",
"invoice_number": "",
"is_credit_memo": "",
"local_tax_rate": "",
"ro_number": "",
"state_tax_rate": "",
"total": "",
"vendor": "",
"vendorname": ""
},
"labels": {
"actions": "",
"bill_lines": "",
"bill_total": "",
"bills": "",
"discrepancy": "",
"entered_total": "",
"enteringcreditmemo": "",
"federal_tax": "",
"local_tax": "",
"new": "",
"noneselected": "",
"retailtotal": "",
"state_tax": "",
"subtotal": ""
},
"successes": {
"created": ""
}
},
"bodyshop": {
"actions": {
"addbucket": "",
@@ -91,6 +161,9 @@
"address1": "",
"address2": "",
"appt_length": "",
"bill_federal_tax_rate": "",
"bill_local_tax_rate": "",
"bill_state_tax_rate": "",
"city": "",
"country": "",
"dailybodytarget": "",
@@ -99,9 +172,6 @@
"enforce_class": "",
"federal_tax_id": "",
"insurance_vendor_id": "",
"invoice_federal_tax_rate": "",
"invoice_local_tax_rate": "",
"invoice_state_tax_rate": "",
"lastnumberworkingdays": "",
"logo_img_path": "",
"md_categories": "",
@@ -121,6 +191,11 @@
"payments": "",
"receivables": ""
},
"bills": {
"enter": "",
"list": "",
"view": ""
},
"contracts": {
"create": "",
"detail": "",
@@ -138,11 +213,6 @@
"employees": {
"page": ""
},
"invoices": {
"enter": "",
"list": "",
"view": ""
},
"jobs": {
"available-list": "",
"close": "",
@@ -614,76 +684,6 @@
"rescuetitle": ""
}
},
"invoicelines": {
"actions": {
"newline": ""
},
"fields": {
"actual": "",
"actual_cost": "",
"cost_center": "",
"federal_tax_applicable": "",
"jobline": "",
"line_desc": "",
"local_tax_applicable": "",
"quantity": "",
"retail": "",
"state_tax_applicable": ""
},
"labels": {
"entered": "",
"other": "",
"reconciled": "",
"unreconciled": ""
}
},
"invoices": {
"actions": {
"edit": "",
"receive": "",
"return": ""
},
"errors": {
"creating": "",
"exporting": "",
"exporting-partner": "",
"invalidro": "",
"invalidvendor": "",
"validation": ""
},
"fields": {
"allpartslocation": "",
"date": "",
"federal_tax_rate": "",
"invoice_number": "",
"is_credit_memo": "",
"local_tax_rate": "",
"ro_number": "",
"state_tax_rate": "",
"total": "",
"vendor": "",
"vendorname": ""
},
"labels": {
"actions": "",
"discrepancy": "",
"entered_total": "",
"enteringcreditmemo": "",
"federal_tax": "",
"invoice_lines": "",
"invoice_total": "",
"invoices": "",
"local_tax": "",
"new": "",
"noneselected": "",
"retailtotal": "",
"state_tax": "",
"subtotal": ""
},
"successes": {
"created": ""
}
},
"joblines": {
"actions": {
"new": ""
@@ -778,7 +778,7 @@
"intake": "",
"manualnew": "",
"mark": "",
"postInvoices": "Contabilizar facturas",
"postbills": "Contabilizar facturas",
"printCenter": "Centro de impresión",
"recalculate": "",
"reconcile": "",
@@ -1071,17 +1071,17 @@
"alljobs": "",
"allpayments": "",
"availablejobs": "Trabajos disponibles",
"bills": "",
"courtesycars": "",
"courtesycars-all": "",
"courtesycars-contracts": "",
"courtesycars-newcontract": "",
"customers": "Clientes",
"enterinvoices": "",
"enterbills": "",
"enterpayment": "",
"entertimeticket": "",
"export": "",
"home": "Casa",
"invoices": "",
"jobs": "Trabajos",
"owners": "propietarios",
"productionboard": "",
@@ -1221,7 +1221,7 @@
"actions": {
"backordered": "",
"receive": "",
"receiveinvoice": ""
"receivebill": ""
},
"errors": {
"backordering": "",
@@ -1451,13 +1451,13 @@
"accounting-payments": "",
"accounting-receivables": "",
"availablejobs": "",
"bills-list": "",
"contracts": "",
"contracts-create": "",
"contracts-detail": "",
"courtesycars": "",
"courtesycars-detail": "",
"courtesycars-new": "",
"invoices-list": "",
"jobs": "",
"jobs-active": "",
"jobs-all": "",
@@ -1481,13 +1481,13 @@
"vehicle-details": "",
"vehicles": ""
},
"bills-list": "",
"contracts": "",
"contracts-create": "",
"contracts-detail": "",
"courtesycars": "",
"courtesycars-create": "",
"courtesycars-detail": "",
"invoices-list": "",
"jobs": "Todos los trabajos | $t(titles.app)",
"jobs-all": "",
"jobs-close": "",

View File

@@ -73,6 +73,76 @@
"values": ""
}
},
"billlines": {
"actions": {
"newline": ""
},
"fields": {
"actual": "",
"actual_cost": "",
"cost_center": "",
"federal_tax_applicable": "",
"jobline": "",
"line_desc": "",
"local_tax_applicable": "",
"quantity": "",
"retail": "",
"state_tax_applicable": ""
},
"labels": {
"entered": "",
"other": "",
"reconciled": "",
"unreconciled": ""
}
},
"bills": {
"actions": {
"edit": "",
"receive": "",
"return": ""
},
"errors": {
"creating": "",
"exporting": "",
"exporting-partner": "",
"invalidro": "",
"invalidvendor": "",
"validation": ""
},
"fields": {
"allpartslocation": "",
"date": "",
"federal_tax_rate": "",
"invoice_number": "",
"is_credit_memo": "",
"local_tax_rate": "",
"ro_number": "",
"state_tax_rate": "",
"total": "",
"vendor": "",
"vendorname": ""
},
"labels": {
"actions": "",
"bill_lines": "",
"bill_total": "",
"bills": "",
"discrepancy": "",
"entered_total": "",
"enteringcreditmemo": "",
"federal_tax": "",
"local_tax": "",
"new": "",
"noneselected": "",
"retailtotal": "",
"state_tax": "",
"subtotal": ""
},
"successes": {
"created": ""
}
},
"bodyshop": {
"actions": {
"addbucket": "",
@@ -91,6 +161,9 @@
"address1": "",
"address2": "",
"appt_length": "",
"bill_federal_tax_rate": "",
"bill_local_tax_rate": "",
"bill_state_tax_rate": "",
"city": "",
"country": "",
"dailybodytarget": "",
@@ -99,9 +172,6 @@
"enforce_class": "",
"federal_tax_id": "",
"insurance_vendor_id": "",
"invoice_federal_tax_rate": "",
"invoice_local_tax_rate": "",
"invoice_state_tax_rate": "",
"lastnumberworkingdays": "",
"logo_img_path": "",
"md_categories": "",
@@ -121,6 +191,11 @@
"payments": "",
"receivables": ""
},
"bills": {
"enter": "",
"list": "",
"view": ""
},
"contracts": {
"create": "",
"detail": "",
@@ -138,11 +213,6 @@
"employees": {
"page": ""
},
"invoices": {
"enter": "",
"list": "",
"view": ""
},
"jobs": {
"available-list": "",
"close": "",
@@ -614,76 +684,6 @@
"rescuetitle": ""
}
},
"invoicelines": {
"actions": {
"newline": ""
},
"fields": {
"actual": "",
"actual_cost": "",
"cost_center": "",
"federal_tax_applicable": "",
"jobline": "",
"line_desc": "",
"local_tax_applicable": "",
"quantity": "",
"retail": "",
"state_tax_applicable": ""
},
"labels": {
"entered": "",
"other": "",
"reconciled": "",
"unreconciled": ""
}
},
"invoices": {
"actions": {
"edit": "",
"receive": "",
"return": ""
},
"errors": {
"creating": "",
"exporting": "",
"exporting-partner": "",
"invalidro": "",
"invalidvendor": "",
"validation": ""
},
"fields": {
"allpartslocation": "",
"date": "",
"federal_tax_rate": "",
"invoice_number": "",
"is_credit_memo": "",
"local_tax_rate": "",
"ro_number": "",
"state_tax_rate": "",
"total": "",
"vendor": "",
"vendorname": ""
},
"labels": {
"actions": "",
"discrepancy": "",
"entered_total": "",
"enteringcreditmemo": "",
"federal_tax": "",
"invoice_lines": "",
"invoice_total": "",
"invoices": "",
"local_tax": "",
"new": "",
"noneselected": "",
"retailtotal": "",
"state_tax": "",
"subtotal": ""
},
"successes": {
"created": ""
}
},
"joblines": {
"actions": {
"new": ""
@@ -778,7 +778,7 @@
"intake": "",
"manualnew": "",
"mark": "",
"postInvoices": "Poster des factures",
"postbills": "Poster des factures",
"printCenter": "Centre d'impression",
"recalculate": "",
"reconcile": "",
@@ -1071,17 +1071,17 @@
"alljobs": "",
"allpayments": "",
"availablejobs": "Emplois disponibles",
"bills": "",
"courtesycars": "",
"courtesycars-all": "",
"courtesycars-contracts": "",
"courtesycars-newcontract": "",
"customers": "Les clients",
"enterinvoices": "",
"enterbills": "",
"enterpayment": "",
"entertimeticket": "",
"export": "",
"home": "Accueil",
"invoices": "",
"jobs": "Emplois",
"owners": "Propriétaires",
"productionboard": "",
@@ -1221,7 +1221,7 @@
"actions": {
"backordered": "",
"receive": "",
"receiveinvoice": ""
"receivebill": ""
},
"errors": {
"backordering": "",
@@ -1451,13 +1451,13 @@
"accounting-payments": "",
"accounting-receivables": "",
"availablejobs": "",
"bills-list": "",
"contracts": "",
"contracts-create": "",
"contracts-detail": "",
"courtesycars": "",
"courtesycars-detail": "",
"courtesycars-new": "",
"invoices-list": "",
"jobs": "",
"jobs-active": "",
"jobs-all": "",
@@ -1481,13 +1481,13 @@
"vehicle-details": "",
"vehicles": ""
},
"bills-list": "",
"contracts": "",
"contracts-create": "",
"contracts-detail": "",
"courtesycars": "",
"courtesycars-create": "",
"courtesycars-detail": "",
"invoices-list": "",
"jobs": "Tous les emplois | $t(titles.app)",
"jobs-all": "",
"jobs-close": "",

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,5 @@
- args:
cascade: true
read_only: false
sql: DROP FUNCTION IF EXISTS search_invoices;
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: alter table "public"."bills" rename to "invoices";
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: alter table "public"."invoices" rename to "bills";
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: alter table "public"."billlines" rename to "invoicelines";
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: alter table "public"."invoicelines" rename to "billlines";
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: alter table "public"."billlines" rename column "billid" to "invoiceid";
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: alter table "public"."billlines" rename column "invoiceid" to "billid";
type: run_sql

View File

@@ -0,0 +1,7 @@
- args:
name: bill
new_name: invoice
table:
name: billlines
schema: public
type: rename_relationship

View File

@@ -0,0 +1,7 @@
- args:
name: invoice
new_name: bill
table:
name: billlines
schema: public
type: rename_relationship

View File

@@ -0,0 +1,7 @@
- args:
name: bill
new_name: invoice
table:
name: jobs
schema: public
type: rename_relationship

View File

@@ -0,0 +1,7 @@
- args:
name: invoice
new_name: bill
table:
name: jobs
schema: public
type: rename_relationship

View File

@@ -0,0 +1,7 @@
- args:
name: bills
new_name: invoices
table:
name: jobs
schema: public
type: rename_relationship

View File

@@ -0,0 +1,7 @@
- args:
name: invoices
new_name: bills
table:
name: jobs
schema: public
type: rename_relationship

View File

@@ -0,0 +1,7 @@
- args:
name: bill
new_name: invoice
table:
name: documents
schema: public
type: rename_relationship

View File

@@ -0,0 +1,7 @@
- args:
name: invoice
new_name: bill
table:
name: documents
schema: public
type: rename_relationship

View File

@@ -0,0 +1,7 @@
- args:
name: bill
new_name: invoice
table:
name: parts_orders
schema: public
type: rename_relationship

View File

@@ -0,0 +1,7 @@
- args:
name: invoice
new_name: bill
table:
name: parts_orders
schema: public
type: rename_relationship

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,8 @@
- args:
cascade: true
read_only: false
sql: "CREATE OR REPLACE FUNCTION public.search_bills(search text)\n RETURNS SETOF
bills\n LANGUAGE plpgsql\n STABLE\nAS $function$\n\nBEGIN\n if search = ''
then\n return query select * from bills ;\n else \n return query SELECT\n
\ *\nFROM\n bills\nWHERE\n search <% (invoice_number);\n end if;\n\n\tEND\n$function$;"
type: run_sql

View File

@@ -0,0 +1,7 @@
- args:
name: billlines
new_name: invoicelines
table:
name: bills
schema: public
type: rename_relationship

View File

@@ -0,0 +1,7 @@
- args:
name: invoicelines
new_name: billlines
table:
name: bills
schema: public
type: rename_relationship

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,8 @@
- args:
cascade: true
read_only: false
sql: "CREATE OR REPLACE FUNCTION public.search_bills(search text)\n RETURNS SETOF
bills\n LANGUAGE plpgsql\n STABLE\nAS $function$\n\nBEGIN\n if search = ''
then\n return query select * from bills ;\n else \n return query SELECT\n
\ *\nFROM\n bills\nWHERE\n search <% (invoice_number);\n end if;\n\n\tEND\n$function$;"
type: run_sql

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,12 @@
- args:
cascade: true
read_only: false
sql: "CREATE OR REPLACE FUNCTION public.search_bills(search text)\n RETURNS SETOF
bills\n LANGUAGE plpgsql\n STABLE\nAS $function$\n\nBEGIN\n if search = ''
then\n return query select * from bills ;\n else \n return query SELECT\n
\ *\nFROM\n bills\nWHERE\n search <% (invoice_number);\n end if;\n\n\tEND\n$function$;"
type: run_sql
- args:
name: search_bills
schema: public
type: track_function

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: alter table "public"."documents" rename column "billid" to "invoiceid";
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: alter table "public"."documents" rename column "invoiceid" to "billid";
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: alter table "public"."bodyshops" rename column "bill_tax_rates" to "invoice_tax_rates";
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: alter table "public"."bodyshops" rename column "invoice_tax_rates" to "bill_tax_rates";
type: run_sql

View File

@@ -348,6 +348,227 @@ tables:
_eq: X-Hasura-User-Id
- active:
_eq: true
- table:
schema: public
name: billlines
object_relationships:
- name: bill
using:
foreign_key_constraint_on: billid
insert_permissions:
- role: user
permission:
check:
bill:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- actual_cost
- actual_price
- applicable_taxes
- cost_center
- created_at
- id
- billid
- joblineid
- line_desc
- quantity
- updated_at
select_permissions:
- role: user
permission:
columns:
- actual_cost
- actual_price
- applicable_taxes
- cost_center
- created_at
- id
- billid
- joblineid
- line_desc
- quantity
- updated_at
filter:
bill:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
update_permissions:
- role: user
permission:
columns:
- actual_cost
- actual_price
- applicable_taxes
- cost_center
- created_at
- id
- billid
- joblineid
- line_desc
- quantity
- updated_at
filter:
bill:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
check: null
delete_permissions:
- role: user
permission:
filter:
bill:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
- table:
schema: public
name: bills
object_relationships:
- name: job
using:
foreign_key_constraint_on: jobid
- name: vendor
using:
foreign_key_constraint_on: vendorid
array_relationships:
- name: billlines
using:
foreign_key_constraint_on:
column: billid
table:
schema: public
name: billlines
- name: documents
using:
foreign_key_constraint_on:
column: billid
table:
schema: public
name: documents
- name: parts_orders
using:
foreign_key_constraint_on:
column: returnfrominvoice
table:
schema: public
name: parts_orders
insert_permissions:
- role: user
permission:
check:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- created_at
- date
- due_date
- exported
- exported_at
- federal_tax_rate
- id
- invoice_number
- is_credit_memo
- jobid
- local_tax_rate
- state_tax_rate
- total
- updated_at
- vendorid
select_permissions:
- role: user
permission:
columns:
- created_at
- date
- due_date
- exported
- exported_at
- federal_tax_rate
- id
- invoice_number
- is_credit_memo
- jobid
- local_tax_rate
- state_tax_rate
- total
- updated_at
- vendorid
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
allow_aggregations: true
update_permissions:
- role: user
permission:
columns:
- created_at
- date
- due_date
- exported
- exported_at
- federal_tax_rate
- id
- invoice_number
- is_credit_memo
- jobid
- local_tax_rate
- state_tax_rate
- total
- updated_at
- vendorid
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
check: null
- table:
schema: public
name: bodyshops
@@ -483,7 +704,7 @@ tables:
- inhousevendorid
- insurance_vendor_id
- intakechecklist
- invoice_tax_rates
- bill_tax_rates
- logo_img_path
- md_categories
- md_classes
@@ -541,7 +762,7 @@ tables:
- inhousevendorid
- insurance_vendor_id
- intakechecklist
- invoice_tax_rates
- bill_tax_rates
- logo_img_path
- md_categories
- md_classes
@@ -1170,9 +1391,9 @@ tables:
schema: public
name: documents
object_relationships:
- name: invoice
- name: bill
using:
foreign_key_constraint_on: invoiceid
foreign_key_constraint_on: billid
- name: job
using:
foreign_key_constraint_on: jobid
@@ -1197,7 +1418,7 @@ tables:
- jobid
- name
- key
- invoiceid
- billid
- type
select_permissions:
- role: user
@@ -1210,7 +1431,7 @@ tables:
- created_at
- updated_at
- id
- invoiceid
- billid
- jobid
filter:
job:
@@ -1393,227 +1614,6 @@ tables:
_eq: X-Hasura-User-Id
- active:
_eq: true
- table:
schema: public
name: invoicelines
object_relationships:
- name: invoice
using:
foreign_key_constraint_on: invoiceid
insert_permissions:
- role: user
permission:
check:
invoice:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- actual_cost
- actual_price
- applicable_taxes
- cost_center
- created_at
- id
- invoiceid
- joblineid
- line_desc
- quantity
- updated_at
select_permissions:
- role: user
permission:
columns:
- actual_cost
- actual_price
- applicable_taxes
- cost_center
- created_at
- id
- invoiceid
- joblineid
- line_desc
- quantity
- updated_at
filter:
invoice:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
update_permissions:
- role: user
permission:
columns:
- actual_cost
- actual_price
- applicable_taxes
- cost_center
- created_at
- id
- invoiceid
- joblineid
- line_desc
- quantity
- updated_at
filter:
invoice:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
check: null
delete_permissions:
- role: user
permission:
filter:
invoice:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
- table:
schema: public
name: invoices
object_relationships:
- name: job
using:
foreign_key_constraint_on: jobid
- name: vendor
using:
foreign_key_constraint_on: vendorid
array_relationships:
- name: documents
using:
foreign_key_constraint_on:
column: invoiceid
table:
schema: public
name: documents
- name: invoicelines
using:
foreign_key_constraint_on:
column: invoiceid
table:
schema: public
name: invoicelines
- name: parts_orders
using:
foreign_key_constraint_on:
column: returnfrominvoice
table:
schema: public
name: parts_orders
insert_permissions:
- role: user
permission:
check:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- created_at
- date
- due_date
- exported
- exported_at
- federal_tax_rate
- id
- invoice_number
- is_credit_memo
- jobid
- local_tax_rate
- state_tax_rate
- total
- updated_at
- vendorid
select_permissions:
- role: user
permission:
columns:
- created_at
- date
- due_date
- exported
- exported_at
- federal_tax_rate
- id
- invoice_number
- is_credit_memo
- jobid
- local_tax_rate
- state_tax_rate
- total
- updated_at
- vendorid
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
allow_aggregations: true
update_permissions:
- role: user
permission:
columns:
- created_at
- date
- due_date
- exported
- exported_at
- federal_tax_rate
- id
- invoice_number
- is_credit_memo
- jobid
- local_tax_rate
- state_tax_rate
- total
- updated_at
- vendorid
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
check: null
- table:
schema: public
name: job_conversations
@@ -1961,6 +1961,14 @@ tables:
schema: public
name: jobs
object_relationships:
- name: bill
using:
manual_configuration:
remote_table:
schema: public
name: bills
column_mapping:
id: jobid
- name: bodyshop
using:
foreign_key_constraint_on: shopid
@@ -1973,14 +1981,6 @@ tables:
- name: employee_refinish_rel
using:
foreign_key_constraint_on: employee_refinish
- name: invoice
using:
manual_configuration:
remote_table:
schema: public
name: invoices
column_mapping:
id: jobid
- name: owner
using:
foreign_key_constraint_on: ownerid
@@ -2002,6 +2002,13 @@ tables:
table:
schema: public
name: available_jobs
- name: bills
using:
foreign_key_constraint_on:
column: jobid
table:
schema: public
name: bills
- name: cccontracts
using:
foreign_key_constraint_on:
@@ -2023,13 +2030,6 @@ tables:
table:
schema: public
name: documents
- name: invoices
using:
foreign_key_constraint_on:
column: jobid
table:
schema: public
name: invoices
- name: job_conversations
using:
foreign_key_constraint_on:
@@ -3298,7 +3298,7 @@ tables:
schema: public
name: parts_orders
object_relationships:
- name: invoice
- name: bill
using:
foreign_key_constraint_on: returnfrominvoice
- name: job
@@ -4019,7 +4019,7 @@ tables:
column: vendorid
table:
schema: public
name: invoices
name: bills
- name: parts_orders
using:
foreign_key_constraint_on:
@@ -4142,7 +4142,7 @@ tables:
functions:
- function:
schema: public
name: search_invoices
name: search_bills
- function:
schema: public
name: search_jobs

View File

@@ -14,7 +14,7 @@ require("dotenv").config({
exports.default = async (req, res) => {
const BearerToken = req.headers.authorization;
const { invoices: invoicesToQuery } = req.body;
const { bills: billsToQuery } = req.body;
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
headers: {
@@ -25,13 +25,13 @@ exports.default = async (req, res) => {
try {
const result = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.QUERY_INVOICES_FOR_PAYABLES_EXPORT, {
invoices: invoicesToQuery,
.request(queries.QUERY_BILLS_FOR_PAYABLES_EXPORT, {
bills: billsToQuery,
});
const { invoices } = result;
const { bills } = result;
const QbXmlToExecute = [];
invoices.map((i) => {
bills.map((i) => {
QbXmlToExecute.push({
id: i.id,
okStatusCodes: ["0"],
@@ -48,27 +48,24 @@ exports.default = async (req, res) => {
}
};
const generateBill = (invoice) => {
const generateBill = (bill) => {
const billQbxmlObj = {
QBXML: {
QBXMLMsgsRq: {
"@onError": "continueOnError",
[`${invoice.is_credit_memo ? "VendorCreditAddRq" : "BillAddRq"}`]: {
[`${invoice.is_credit_memo ? "VendorCreditAdd" : "BillAdd"}`]: {
[`${bill.is_credit_memo ? "VendorCreditAddRq" : "BillAddRq"}`]: {
[`${bill.is_credit_memo ? "VendorCreditAdd" : "BillAdd"}`]: {
VendorRef: {
FullName: invoice.vendor.name,
FullName: bill.vendor.name,
},
TxnDate: invoice.date,
DueDate: invoice.due_date,
RefNumber: invoice.invoice_number,
Memo: `RO ${invoice.job.ro_number || ""} OWNER ${
invoice.job.ownr_fn || ""
} ${invoice.job.ownr_ln || ""} ${invoice.job.ownr_co_nm || ""}`,
ExpenseLineAdd: invoice.invoicelines.map((il) =>
generateBillLine(
il,
invoice.job.bodyshop.md_responsibility_centers
)
TxnDate: bill.date,
DueDate: bill.due_date,
RefNumber: bill.bill_number,
Memo: `RO ${bill.job.ro_number || ""} OWNER ${
bill.job.ownr_fn || ""
} ${bill.job.ownr_ln || ""} ${bill.job.ownr_co_nm || ""}`,
ExpenseLineAdd: bill.billlines.map((il) =>
generateBillLine(il, bill.job.bodyshop.md_responsibility_centers)
),
},
},
@@ -90,15 +87,15 @@ const generateBill = (invoice) => {
return billQbxml_Full;
};
const generateBillLine = (invoiceLine, responsibilityCenters) => {
const generateBillLine = (billLine, responsibilityCenters) => {
return {
AccountRef: {
FullName: responsibilityCenters.costs.find(
(c) => c.name === invoiceLine.cost_center
(c) => c.name === billLine.cost_center
).accountname,
},
Amount: Dinero({
amount: Math.round(invoiceLine.actual_cost * 100),
amount: Math.round(billLine.actual_cost * 100),
}).toFormat(DineroQbFormat),
};
};

View File

@@ -108,9 +108,9 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
}
`;
exports.QUERY_INVOICES_FOR_PAYABLES_EXPORT = `
query QUERY_INVOICES_FOR_PAYABLES_EXPORT($invoices: [uuid!]!) {
invoices(where: {id: {_in: $invoices}}) {
exports.QUERY_BILLS_FOR_PAYABLES_EXPORT = `
query QUERY_BILLS_FOR_PAYABLES_EXPORT($bills: [uuid!]!) {
bills(where: {id: {_in: $bills}}) {
id
date
due_date
@@ -128,7 +128,7 @@ query QUERY_INVOICES_FOR_PAYABLES_EXPORT($invoices: [uuid!]!) {
md_responsibility_centers
}
}
invoicelines{
billlines{
id
cost_center
actual_cost