Added invoice totals + high level reconciliation on PLI jobs tab. BOD-26
This commit is contained in:
@@ -5902,6 +5902,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>retailtotal</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>state_tax</name>
|
<name>state_tax</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -9374,6 +9395,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>difference</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>documents</name>
|
<name>documents</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
|
|||||||
@@ -44,21 +44,23 @@ export default function InvoiceFormComponent({
|
|||||||
setDiscount(matchingVendors[0].discount);
|
setDiscount(matchingVendors[0].discount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [form, setDiscount, vendorAutoCompleteOptions]);
|
if (form.getFieldValue("jobid")) {
|
||||||
|
loadLines({ variables: { id: form.getFieldValue("jobid") } });
|
||||||
|
}
|
||||||
|
}, [form, setDiscount, vendorAutoCompleteOptions, loadLines]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="invoice-form-wrapper">
|
<div className='invoice-form-wrapper'>
|
||||||
<div className="invoice-form-invoice-details">
|
<div className='invoice-form-invoice-details'>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="jobid"
|
name='jobid'
|
||||||
label={t("invoices.fields.ro_number")}
|
label={t("invoices.fields.ro_number")}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: t("general.validation.required"),
|
message: t("general.validation.required"),
|
||||||
},
|
},
|
||||||
]}
|
]}>
|
||||||
>
|
|
||||||
<JobSearchSelect
|
<JobSearchSelect
|
||||||
options={roAutoCompleteOptions}
|
options={roAutoCompleteOptions}
|
||||||
disabled={invoiceEdit}
|
disabled={invoiceEdit}
|
||||||
@@ -71,81 +73,73 @@ export default function InvoiceFormComponent({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("invoices.fields.vendor")}
|
label={t("invoices.fields.vendor")}
|
||||||
name="vendorid"
|
name='vendorid'
|
||||||
style={{ display: invoiceEdit ? "none" : null }}
|
style={{ display: invoiceEdit ? "none" : null }}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: t("general.validation.required"),
|
message: t("general.validation.required"),
|
||||||
},
|
},
|
||||||
]}
|
]}>
|
||||||
>
|
|
||||||
<VendorSearchSelect
|
<VendorSearchSelect
|
||||||
options={vendorAutoCompleteOptions}
|
options={vendorAutoCompleteOptions}
|
||||||
onSelect={handleVendorSelect}
|
onSelect={handleVendorSelect}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
<div className="invoice-form-invoice-details">
|
<div className='invoice-form-invoice-details'>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("invoices.fields.invoice_number")}
|
label={t("invoices.fields.invoice_number")}
|
||||||
name="invoice_number"
|
name='invoice_number'
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: t("general.validation.required"),
|
message: t("general.validation.required"),
|
||||||
},
|
},
|
||||||
]}
|
]}>
|
||||||
>
|
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("invoices.fields.date")}
|
label={t("invoices.fields.date")}
|
||||||
name="date"
|
name='date'
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: t("general.validation.required"),
|
message: t("general.validation.required"),
|
||||||
},
|
},
|
||||||
]}
|
]}>
|
||||||
>
|
|
||||||
<DatePicker />
|
<DatePicker />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("invoices.fields.is_credit_memo")}
|
label={t("invoices.fields.is_credit_memo")}
|
||||||
name="is_credit_memo"
|
name='is_credit_memo'
|
||||||
valuePropName="checked"
|
valuePropName='checked'>
|
||||||
>
|
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("invoices.fields.total")}
|
label={t("invoices.fields.total")}
|
||||||
name="total"
|
name='total'
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: t("general.validation.required"),
|
message: t("general.validation.required"),
|
||||||
},
|
},
|
||||||
]}
|
]}>
|
||||||
>
|
|
||||||
<CurrencyInput />
|
<CurrencyInput />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("invoices.fields.federal_tax_rate")}
|
label={t("invoices.fields.federal_tax_rate")}
|
||||||
name="federal_tax_rate"
|
name='federal_tax_rate'>
|
||||||
>
|
|
||||||
<CurrencyInput />
|
<CurrencyInput />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("invoices.fields.state_tax_rate")}
|
label={t("invoices.fields.state_tax_rate")}
|
||||||
name="state_tax_rate"
|
name='state_tax_rate'>
|
||||||
>
|
|
||||||
<CurrencyInput />
|
<CurrencyInput />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("invoices.fields.local_tax_rate")}
|
label={t("invoices.fields.local_tax_rate")}
|
||||||
name="local_tax_rate"
|
name='local_tax_rate'>
|
||||||
>
|
|
||||||
<CurrencyInput />
|
<CurrencyInput />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
@@ -161,19 +155,18 @@ export default function InvoiceFormComponent({
|
|||||||
// </Form.Item>
|
// </Form.Item>
|
||||||
}
|
}
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="upload"
|
name='upload'
|
||||||
label="Upload"
|
label='Upload'
|
||||||
style={{ display: invoiceEdit ? "none" : null }}
|
style={{ display: invoiceEdit ? "none" : null }}
|
||||||
valuePropName="fileList"
|
valuePropName='fileList'
|
||||||
getValueFromEvent={(e) => {
|
getValueFromEvent={(e) => {
|
||||||
console.log("Upload event:", e);
|
console.log("Upload event:", e);
|
||||||
if (Array.isArray(e)) {
|
if (Array.isArray(e)) {
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
return e && e.fileList;
|
return e && e.fileList;
|
||||||
}}
|
}}>
|
||||||
>
|
<Upload name='logo' beforeUpload={() => false} listType='picture'>
|
||||||
<Upload name="logo" beforeUpload={() => false} listType="picture">
|
|
||||||
<Button>Click to upload</Button>
|
<Button>Click to upload</Button>
|
||||||
</Upload>
|
</Upload>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -196,7 +189,7 @@ export default function InvoiceFormComponent({
|
|||||||
totals = CalculateInvoiceTotal(values);
|
totals = CalculateInvoiceTotal(values);
|
||||||
if (!!totals)
|
if (!!totals)
|
||||||
return (
|
return (
|
||||||
<div className="invoice-form-totals">
|
<div className='invoice-form-totals'>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t("invoices.labels.subtotal")}
|
title={t("invoices.labels.subtotal")}
|
||||||
value={totals.subtotal.toFormat()}
|
value={totals.subtotal.toFormat()}
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import { Button, Descriptions, Table } from "antd";
|
import { Button, Descriptions, Table, Checkbox } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
|
|
||||||
export default function InvoicesListTableComponent({
|
export default function InvoicesListTableComponent({
|
||||||
loading,
|
loading,
|
||||||
invoices,
|
invoices,
|
||||||
selectedInvoice,
|
selectedInvoice,
|
||||||
handleOnRowClick,
|
handleOnRowClick,
|
||||||
|
refetch,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -65,8 +67,7 @@ export default function InvoicesListTableComponent({
|
|||||||
key: "actions",
|
key: "actions",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Link
|
<Link
|
||||||
to={`/manage/invoices?invoiceid=${record.id}&vendorid=${record.vendorid}`}
|
to={`/manage/invoices?invoiceid=${record.id}&vendorid=${record.vendorid}`}>
|
||||||
>
|
|
||||||
<Button>{t("invoices.actions.edit")}</Button>
|
<Button>{t("invoices.actions.edit")}</Button>
|
||||||
</Link>
|
</Link>
|
||||||
),
|
),
|
||||||
@@ -120,24 +121,74 @@ export default function InvoicesListTableComponent({
|
|||||||
state.sortedInfo.columnKey === "cost_center" &&
|
state.sortedInfo.columnKey === "cost_center" &&
|
||||||
state.sortedInfo.order,
|
state.sortedInfo.order,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t("invoicelines.fields.federal_tax_applicable"),
|
||||||
|
dataIndex: "applicable_taxes.federal",
|
||||||
|
key: "applicable_taxes.federal",
|
||||||
|
render: (text, record) => (
|
||||||
|
<Checkbox
|
||||||
|
disabled
|
||||||
|
checked={
|
||||||
|
(record.applicable_taxes && record.applicable_taxes.federal) ||
|
||||||
|
false
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("invoicelines.fields.state_tax_applicable"),
|
||||||
|
dataIndex: "applicable_taxes.state",
|
||||||
|
key: "applicable_taxes.state",
|
||||||
|
render: (text, record) => (
|
||||||
|
<Checkbox
|
||||||
|
disabled
|
||||||
|
checked={
|
||||||
|
(record.applicable_taxes && record.applicable_taxes.state) ||
|
||||||
|
false
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("invoicelines.fields.local_tax_applicable"),
|
||||||
|
dataIndex: "applicable_taxes.local",
|
||||||
|
key: "applicable_taxes.local",
|
||||||
|
render: (text, record) => (
|
||||||
|
<Checkbox
|
||||||
|
disabled
|
||||||
|
checked={
|
||||||
|
(record.applicable_taxes && record.applicable_taxes.local) ||
|
||||||
|
false
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Descriptions title="User Info">
|
<Descriptions
|
||||||
<Descriptions.Item label="UserName">Zhou Maomao</Descriptions.Item>
|
title={`${t("invoices.fields.invoice_number")} ${
|
||||||
<Descriptions.Item label="Telephone">1810000000</Descriptions.Item>
|
record.invoice_number
|
||||||
<Descriptions.Item label="Live">Hangzhou, Zhejiang</Descriptions.Item>
|
}`}>
|
||||||
<Descriptions.Item label="Remark">empty</Descriptions.Item>
|
<Descriptions.Item label={t("invoices.fields.federal_tax_rate")}>
|
||||||
<Descriptions.Item label="Address">
|
{record.federal_tax_rate || ""}
|
||||||
No. 18, Wantang Road, Xihu District, Hangzhou, Zhejiang, China
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label={t("invoices.fields.state_tax_rate")}>
|
||||||
|
{record.state_tax_rate || ""}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label={t("invoices.fields.local_tax_rate")}>
|
||||||
|
{record.local_tax_rate || ""}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label={t("invoices.fields.is_credit_memo")}>
|
||||||
|
<Checkbox disabled checked={record.is_credit_memo || false} />
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
<Table
|
<Table
|
||||||
size="small"
|
size='small'
|
||||||
pagination={{ position: "top", defaultPageSize: 25 }}
|
pagination={{ position: "top", defaultPageSize: 25 }}
|
||||||
columns={columns.map((item) => ({ ...item }))}
|
columns={columns.map((item) => ({ ...item }))}
|
||||||
rowKey="id"
|
rowKey='id'
|
||||||
dataSource={record.invoicelines}
|
dataSource={record.invoicelines}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -147,11 +198,18 @@ export default function InvoicesListTableComponent({
|
|||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
size="small"
|
size='small'
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
<Button onClick={() => refetch()}>
|
||||||
|
<SyncOutlined />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
expandedRowRender={rowExpander}
|
expandedRowRender={rowExpander}
|
||||||
pagination={{ position: "top", defaultPageSize: 25 }}
|
pagination={{ position: "top", defaultPageSize: 25 }}
|
||||||
columns={columns.map((item) => ({ ...item }))}
|
columns={columns.map((item) => ({ ...item }))}
|
||||||
rowKey="id"
|
rowKey='id'
|
||||||
dataSource={invoices}
|
dataSource={invoices}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
expandable={{
|
expandable={{
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import React from "react";
|
||||||
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
|
import { Statistic, Descriptions } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Dinero from "dinero.js";
|
||||||
|
|
||||||
|
export default function JobInvoiceTotals({ loading, invoices, jobTotals }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
if (loading) return <LoadingSkeleton />;
|
||||||
|
const totals = JSON.parse(jobTotals);
|
||||||
|
|
||||||
|
let invoiceTotals = Dinero({ amount: 0 });
|
||||||
|
invoices.forEach((i) =>
|
||||||
|
i.invoicelines.forEach((il) => {
|
||||||
|
invoiceTotals = invoiceTotals.add(
|
||||||
|
Dinero({
|
||||||
|
amount:
|
||||||
|
((il.actual_price || 0) * i.is_credit_memo ? -1 : 1 || 0) * 100,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const discrepancy = Dinero(totals.parts.parts.total).subtract(invoiceTotals);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Descriptions
|
||||||
|
bordered
|
||||||
|
size='small'
|
||||||
|
column={1}
|
||||||
|
title={t("jobs.labels.partssubletstotal")}>
|
||||||
|
<Descriptions.Item label={t("jobs.labels.partstotal")}>
|
||||||
|
<Statistic
|
||||||
|
value={Dinero(totals.parts.parts.total).toFormat()}
|
||||||
|
suffix={`(${Dinero(
|
||||||
|
totals.parts.parts.subtotal
|
||||||
|
).toFormat()} ± ${Dinero(
|
||||||
|
totals.parts.parts.adjustments
|
||||||
|
).toFormat()})`}
|
||||||
|
/>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label={t("jobs.labels.subletstotal")}>
|
||||||
|
<Statistic
|
||||||
|
value={Dinero(totals.parts.sublets.total).toFormat()}
|
||||||
|
suffix={`(${Dinero(
|
||||||
|
totals.parts.sublets.subtotal
|
||||||
|
).toFormat()} ± ${Dinero(
|
||||||
|
totals.parts.sublets.adjustments
|
||||||
|
).toFormat()})`}
|
||||||
|
/>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label={t("invoices.labels.retailtotal")}>
|
||||||
|
<Statistic value={invoiceTotals.toFormat()} />
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label={t("invoices.labels.discrepancy")}>
|
||||||
|
<Statistic
|
||||||
|
valueStyle={{
|
||||||
|
color: discrepancy.getAmount === 0 ? "green" : "red",
|
||||||
|
}}
|
||||||
|
value={discrepancy.toFormat()}
|
||||||
|
/>
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { connect } from "react-redux";
|
|||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import InvoicesListTableComponent from "../invoices-list-table/invoices-list-table.component";
|
import InvoicesListTableComponent from "../invoices-list-table/invoices-list-table.component";
|
||||||
|
import JobInvoicesTotalsComponent from "../job-invoices-total/job-invoices-total.component";
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setInvoiceEnterContext: (context) =>
|
setInvoiceEnterContext: (context) =>
|
||||||
@@ -27,13 +28,19 @@ export function JobsDetailPliComponent({
|
|||||||
job,
|
job,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
Enter Invoice
|
Enter Invoice
|
||||||
</Button>
|
</Button>
|
||||||
{invoicesQuery.error ? (
|
{invoicesQuery.error ? (
|
||||||
<AlertComponent message={invoicesQuery.error.message} type="error" />
|
<AlertComponent message={invoicesQuery.error.message} type='error' />
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
<JobInvoicesTotalsComponent
|
||||||
|
invoices={invoicesQuery.data ? invoicesQuery.data.invoices : null}
|
||||||
|
loading={invoicesQuery.loading}
|
||||||
|
jobTotals={job.job_totals}
|
||||||
|
/>
|
||||||
|
|
||||||
<InvoicesListTableComponent
|
<InvoicesListTableComponent
|
||||||
loading={invoicesQuery.loading}
|
loading={invoicesQuery.loading}
|
||||||
handleOnRowClick={handleOnRowClick}
|
handleOnRowClick={handleOnRowClick}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ function JobsDocumentsComponent({
|
|||||||
invoicesCallback,
|
invoicesCallback,
|
||||||
}) {
|
}) {
|
||||||
const [galleryImages, setgalleryImages] = useState([]);
|
const [galleryImages, setgalleryImages] = useState([]);
|
||||||
console.log("Gallery Data", data);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setgalleryImages(
|
setgalleryImages(
|
||||||
data.reduce((acc, value) => {
|
data.reduce((acc, value) => {
|
||||||
@@ -34,7 +34,7 @@ function JobsDocumentsComponent({
|
|||||||
}, [data, setgalleryImages]);
|
}, [data, setgalleryImages]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="clearfix">
|
<div className='clearfix'>
|
||||||
<DocumentsUploadContainer
|
<DocumentsUploadContainer
|
||||||
jobId={jobId}
|
jobId={jobId}
|
||||||
invoiceId={invoiceId}
|
invoiceId={invoiceId}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export function LaborAllocationsTable({ joblines, timetickets, bodyshop }) {
|
|||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col span={12}>
|
<Col span={6}>
|
||||||
<strong>{t("timetickets.fields.cost_center")}</strong>
|
<strong>{t("timetickets.fields.cost_center")}</strong>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
@@ -40,12 +40,23 @@ export function LaborAllocationsTable({ joblines, timetickets, bodyshop }) {
|
|||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<strong>{t("jobs.labels.hrs_claimed")}</strong>
|
<strong>{t("jobs.labels.hrs_claimed")}</strong>
|
||||||
</Col>
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<strong>{t("jobs.labels.difference")}</strong>
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{totals.map((t, idx) => (
|
{totals.map((t, idx) => (
|
||||||
<Row key={idx}>
|
<Row key={idx}>
|
||||||
<Col span={12}>{t.cost_center}</Col>
|
<Col span={6}>{t.cost_center}</Col>
|
||||||
<Col span={6}>{t.total}</Col>
|
<Col span={6}>{t.total.toFixed(2)}</Col>
|
||||||
<Col span={6}>{t.claimed}</Col>
|
<Col span={6}>{t.claimed.toFixed(2)}</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<strong
|
||||||
|
style={{
|
||||||
|
color: Math.round(t.total - t.claimed) > 0 ? "green" : "red",
|
||||||
|
}}>
|
||||||
|
{(t.total - t.claimed).toFixed(2)}
|
||||||
|
</strong>
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ export const QUERY_INVOICES_BY_JOBID = gql`
|
|||||||
total
|
total
|
||||||
invoice_number
|
invoice_number
|
||||||
date
|
date
|
||||||
|
federal_tax_rate
|
||||||
|
state_tax_rate
|
||||||
|
local_tax_rate
|
||||||
invoicelines {
|
invoicelines {
|
||||||
actual_price
|
actual_price
|
||||||
quantity
|
quantity
|
||||||
@@ -56,6 +59,7 @@ export const QUERY_INVOICES_BY_JOBID = gql`
|
|||||||
cost_center
|
cost_center
|
||||||
id
|
id
|
||||||
line_desc
|
line_desc
|
||||||
|
applicable_taxes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
export default function InvoiceDetailPageComponent() {
|
|
||||||
return <div>Invoice Detail Page Component</div>;
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import InvoiceDetailPageComponent from "./invoice-detail.page.component";
|
|
||||||
import { useQuery } from "@apollo/react-hooks";
|
|
||||||
import { QUERY_INVOICE_BY_PK } from "../../graphql/invoices.queries";
|
|
||||||
import AlertComponent from "../../components/alert/alert.component";
|
|
||||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
|
||||||
import { Form } from "antd";
|
|
||||||
|
|
||||||
export default function InvoiceDetailPageContainer() {
|
|
||||||
const { invoiceId } = useParams();
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
const { loading, error, data } = useQuery(QUERY_INVOICE_BY_PK, {
|
|
||||||
variables: { invoiceid: invoiceId },
|
|
||||||
skip: !!!invoiceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (loading) return <LoadingSpinner />;
|
|
||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form form={form} initialValues={data ? data.invoices_by_pk : {}}>
|
|
||||||
<InvoiceDetailPageComponent />
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,28 +1,16 @@
|
|||||||
import Icon, {
|
import Icon, { BarsOutlined, CalendarFilled, DollarCircleOutlined, FileImageFilled, ToolFilled } from "@ant-design/icons";
|
||||||
BarsOutlined,
|
|
||||||
CalendarFilled,
|
|
||||||
DollarCircleOutlined,
|
|
||||||
FileImageFilled,
|
|
||||||
ToolFilled,
|
|
||||||
} from "@ant-design/icons";
|
|
||||||
import { Form, notification, Tabs } from "antd";
|
import { Form, notification, Tabs } from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
import queryString from "query-string";
|
||||||
import React, { lazy, Suspense } from "react";
|
import React, { lazy, Suspense } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import { FaHardHat, FaHistory, FaInfo, FaRegStickyNote, FaShieldAlt } from "react-icons/fa";
|
||||||
FaHardHat,
|
|
||||||
FaHistory,
|
|
||||||
FaInfo,
|
|
||||||
FaRegStickyNote,
|
|
||||||
FaShieldAlt,
|
|
||||||
} from "react-icons/fa";
|
|
||||||
import { useHistory, useLocation } from "react-router-dom";
|
|
||||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
|
||||||
import queryString from "query-string";
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
import { useHistory, useLocation } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import { CalculateJob } from "../../components/job-totals-table/job-totals.utility";
|
import { CalculateJob } from "../../components/job-totals-table/job-totals.utility";
|
||||||
|
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
|
||||||
const JobsLinesContainer = lazy(() =>
|
const JobsLinesContainer = lazy(() =>
|
||||||
import("../../components/job-detail-lines/job-lines.container")
|
import("../../components/job-detail-lines/job-lines.container")
|
||||||
@@ -127,14 +115,6 @@ export function JobsDetailPage({
|
|||||||
fallback={<LoadingSpinner message={t("general.labels.loadingapp")} />}>
|
fallback={<LoadingSpinner message={t("general.labels.loadingapp")} />}>
|
||||||
<ScheduleJobModalContainer />
|
<ScheduleJobModalContainer />
|
||||||
<JobLineUpsertModalContainer />
|
<JobLineUpsertModalContainer />
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
let r = CalculateJob(job, bodyshop.shoprates);
|
|
||||||
console.log("r", r);
|
|
||||||
}}>
|
|
||||||
Calculate
|
|
||||||
</button>
|
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
//onFieldsChange={(a, b) => console.log("a,b", a, b)}
|
//onFieldsChange={(a, b) => console.log("a,b", a, b)}
|
||||||
|
|||||||
@@ -67,9 +67,7 @@ const ContractsList = lazy(() =>
|
|||||||
const InvoicesListPage = lazy(() =>
|
const InvoicesListPage = lazy(() =>
|
||||||
import("../invoices/invoices.page.container")
|
import("../invoices/invoices.page.container")
|
||||||
);
|
);
|
||||||
const InvoiceDetailPage = lazy(() =>
|
|
||||||
import("../invoice-detail/invoice-detail.page.container")
|
|
||||||
);
|
|
||||||
const EnterInvoiceModalContainer = lazy(() =>
|
const EnterInvoiceModalContainer = lazy(() =>
|
||||||
import("../../components/invoice-enter-modal/invoice-enter-modal.container")
|
import("../../components/invoice-enter-modal/invoice-enter-modal.container")
|
||||||
);
|
);
|
||||||
@@ -201,11 +199,6 @@ export default function Manage({ match }) {
|
|||||||
path={`${match.path}/invoices`}
|
path={`${match.path}/invoices`}
|
||||||
component={InvoicesListPage}
|
component={InvoicesListPage}
|
||||||
/>
|
/>
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path={`${match.path}/invoices/:invoiceId`}
|
|
||||||
component={InvoiceDetailPage}
|
|
||||||
/>
|
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path={`${match.path}/owners`}
|
path={`${match.path}/owners`}
|
||||||
|
|||||||
@@ -407,6 +407,7 @@
|
|||||||
"local_tax": "Local Tax",
|
"local_tax": "Local Tax",
|
||||||
"new": "New Invoice",
|
"new": "New Invoice",
|
||||||
"noneselected": "No invoice selected.",
|
"noneselected": "No invoice selected.",
|
||||||
|
"retailtotal": "Retail Total of Invoices (Ex. Taxes)",
|
||||||
"state_tax": "State Tax",
|
"state_tax": "State Tax",
|
||||||
"subtotal": "Subtotal"
|
"subtotal": "Subtotal"
|
||||||
},
|
},
|
||||||
@@ -597,6 +598,7 @@
|
|||||||
"vehicleinfo": "Vehicle Info"
|
"vehicleinfo": "Vehicle Info"
|
||||||
},
|
},
|
||||||
"creating_new_job": "Creating new job...",
|
"creating_new_job": "Creating new job...",
|
||||||
|
"difference": "Difference",
|
||||||
"documents": "Documents",
|
"documents": "Documents",
|
||||||
"duplicateconfirm": "Are you sure you want to duplicate this job? Some elements of this job will not be duplicated.",
|
"duplicateconfirm": "Are you sure you want to duplicate this job? Some elements of this job will not be duplicated.",
|
||||||
"existing_jobs": "Existing Jobs",
|
"existing_jobs": "Existing Jobs",
|
||||||
|
|||||||
@@ -407,6 +407,7 @@
|
|||||||
"local_tax": "",
|
"local_tax": "",
|
||||||
"new": "",
|
"new": "",
|
||||||
"noneselected": "",
|
"noneselected": "",
|
||||||
|
"retailtotal": "",
|
||||||
"state_tax": "",
|
"state_tax": "",
|
||||||
"subtotal": ""
|
"subtotal": ""
|
||||||
},
|
},
|
||||||
@@ -597,6 +598,7 @@
|
|||||||
"vehicleinfo": ""
|
"vehicleinfo": ""
|
||||||
},
|
},
|
||||||
"creating_new_job": "Creando nuevo trabajo ...",
|
"creating_new_job": "Creando nuevo trabajo ...",
|
||||||
|
"difference": "",
|
||||||
"documents": "documentos",
|
"documents": "documentos",
|
||||||
"duplicateconfirm": "",
|
"duplicateconfirm": "",
|
||||||
"existing_jobs": "Empleos existentes",
|
"existing_jobs": "Empleos existentes",
|
||||||
|
|||||||
@@ -407,6 +407,7 @@
|
|||||||
"local_tax": "",
|
"local_tax": "",
|
||||||
"new": "",
|
"new": "",
|
||||||
"noneselected": "",
|
"noneselected": "",
|
||||||
|
"retailtotal": "",
|
||||||
"state_tax": "",
|
"state_tax": "",
|
||||||
"subtotal": ""
|
"subtotal": ""
|
||||||
},
|
},
|
||||||
@@ -597,6 +598,7 @@
|
|||||||
"vehicleinfo": ""
|
"vehicleinfo": ""
|
||||||
},
|
},
|
||||||
"creating_new_job": "Création d'un nouvel emploi ...",
|
"creating_new_job": "Création d'un nouvel emploi ...",
|
||||||
|
"difference": "",
|
||||||
"documents": "Les documents",
|
"documents": "Les documents",
|
||||||
"duplicateconfirm": "",
|
"duplicateconfirm": "",
|
||||||
"existing_jobs": "Emplois existants",
|
"existing_jobs": "Emplois existants",
|
||||||
|
|||||||
Reference in New Issue
Block a user