Started refactoring parts order reconciliation BOD-406
This commit is contained in:
@@ -68,5 +68,5 @@
|
|||||||
|
|
||||||
.ant-table-cell {
|
.ant-table-cell {
|
||||||
// background-color: red;
|
// background-color: red;
|
||||||
padding: 0.2rem !important;
|
//padding: 0.2rem !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Checkbox, Statistic, Table } from "antd";
|
import { Checkbox, Table } from "antd";
|
||||||
import Dinero from "dinero.js";
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
@@ -16,7 +15,6 @@ export default function JobReconciliationBillsTable({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [selectedLines, setSelectedLines] = billLineState;
|
const [selectedLines, setSelectedLines] = billLineState;
|
||||||
const [total, setTotal] = useState(Dinero({ amount: 0 }).toFormat());
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@@ -27,6 +25,15 @@ export default function JobReconciliationBillsTable({
|
|||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t("billlines.labels.from"),
|
||||||
|
dataIndex: "from",
|
||||||
|
key: "from",
|
||||||
|
render: (text, record) =>
|
||||||
|
`${record.bill.vendor && record.bill.vendor.name} / ${
|
||||||
|
record.bill.invoice_number
|
||||||
|
}`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t("billlines.fields.retail"),
|
title: t("billlines.fields.retail"),
|
||||||
dataIndex: "actual_price",
|
dataIndex: "actual_price",
|
||||||
@@ -61,11 +68,13 @@ export default function JobReconciliationBillsTable({
|
|||||||
title: t("bills.fields.is_credit_memo"),
|
title: t("bills.fields.is_credit_memo"),
|
||||||
dataIndex: "is_credit_memo",
|
dataIndex: "is_credit_memo",
|
||||||
key: "is_credit_memo",
|
key: "is_credit_memo",
|
||||||
sorter: (a, b) => a.is_credit_memo - b.is_credit_memo,
|
sorter: (a, b) => a.bill.is_credit_memo - b.bill.is_credit_memo,
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "is_credit_memo" &&
|
state.sortedInfo.columnKey === "is_credit_memo" &&
|
||||||
state.sortedInfo.order,
|
state.sortedInfo.order,
|
||||||
render: (text, record) => <Checkbox checked={record.is_credit_memo} />,
|
render: (text, record) => (
|
||||||
|
<Checkbox disabled checked={record.bill.is_credit_memo} />
|
||||||
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -74,30 +83,14 @@ export default function JobReconciliationBillsTable({
|
|||||||
};
|
};
|
||||||
const handleOnRowClick = (selectedRecordKeys, selectedRecords) => {
|
const handleOnRowClick = (selectedRecordKeys, selectedRecords) => {
|
||||||
setSelectedLines(selectedRecordKeys);
|
setSelectedLines(selectedRecordKeys);
|
||||||
calculateTotal(selectedRecords);
|
|
||||||
};
|
|
||||||
|
|
||||||
const calculateTotal = (selectedRecords) => {
|
|
||||||
let total = Dinero({ amount: 0 });
|
|
||||||
selectedRecords.forEach(
|
|
||||||
(record) =>
|
|
||||||
(total = total.add(
|
|
||||||
Dinero({
|
|
||||||
amount:
|
|
||||||
record.actual_price * 100 * (record.is_credit_memo ? -1 : 1),
|
|
||||||
}).multiply(record.quantity)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
setTotal(total.toFormat());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Table
|
<Table
|
||||||
size="small"
|
size="small"
|
||||||
title={() => <div></div>}
|
pagination={false}
|
||||||
pagination={{ position: "top", defaultPageSize: 25 }}
|
scroll={{ y: "40vh", x: true }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={invoiceLineData}
|
dataSource={invoiceLineData}
|
||||||
@@ -107,7 +100,6 @@ export default function JobReconciliationBillsTable({
|
|||||||
selectedRowKeys: selectedLines,
|
selectedRowKeys: selectedLines,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Statistic value={total} title="total" />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Col, Row } from "antd";
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import JobReconciliationBillsTable from "../job-reconciliation-bills-table/job-reconciliation-bills-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";
|
import JobReconciliationPartsTable from "../job-reconciliation-parts-table/job-reconciliation-parts-table.component";
|
||||||
|
import JobReconciliationTotals from "../job-reconciliation-totals/job-reconciliation-totals.component";
|
||||||
|
|
||||||
export default function JobReconciliationModalComponent({ job, bills }) {
|
export default function JobReconciliationModalComponent({ job, bills }) {
|
||||||
const jobLineState = useState([]);
|
const jobLineState = useState([]);
|
||||||
@@ -11,16 +12,18 @@ export default function JobReconciliationModalComponent({ job, bills }) {
|
|||||||
bills
|
bills
|
||||||
.map((i) =>
|
.map((i) =>
|
||||||
i.billlines.map((il) => {
|
i.billlines.map((il) => {
|
||||||
return { ...il, is_credit_memo: i.is_credit_memo };
|
return { ...il, bill: i };
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.flat() || [];
|
.flat() || [];
|
||||||
|
|
||||||
const jobLineData = job.joblines.filter((j) => j.part_type !== null);
|
const jobLineData = job.joblines.filter(
|
||||||
|
(j) => j.part_type !== null && j.part_type !== "PAE"
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Row>
|
<Row gutter={[16, 16]}>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<JobReconciliationPartsTable
|
<JobReconciliationPartsTable
|
||||||
jobLineData={jobLineData}
|
jobLineData={jobLineData}
|
||||||
@@ -34,6 +37,14 @@ export default function JobReconciliationModalComponent({ job, bills }) {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<JobReconciliationTotals
|
||||||
|
jobLines={jobLineData}
|
||||||
|
selectedJobLines={jobLineState[0]}
|
||||||
|
billLines={invoiceLineData}
|
||||||
|
selectedBillLines={billLineState[0]}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,10 @@ function JobReconciliationModalContainer({
|
|||||||
title={t("jobs.labels.reconciliationheader")}
|
title={t("jobs.labels.reconciliationheader")}
|
||||||
width={"90%"}
|
width={"90%"}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
okText={t("general.actions.save")}
|
okText={t("general.actions.close")}
|
||||||
onOk={handleCancel}
|
onOk={handleCancel}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
|
cancelButtonProps={{ display: "none" }}
|
||||||
destroyOnClose
|
destroyOnClose
|
||||||
>
|
>
|
||||||
<JobReconciliationModalComponent job={job} bills={bills} />
|
<JobReconciliationModalComponent job={job} bills={bills} />
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Statistic, Table } from "antd";
|
import { Table } from "antd";
|
||||||
import Dinero from "dinero.js";
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
@@ -16,20 +15,8 @@ export default function JobReconcilitionPartsTable({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [selectedLines, setSelectedLines] = jobLineState;
|
const [selectedLines, setSelectedLines] = jobLineState;
|
||||||
const [total, setTotal] = useState(Dinero({ amount: 0 }).toFormat());
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
// {
|
|
||||||
// title: t("joblines.fields.line_no"),
|
|
||||||
// dataIndex: "line_no",
|
|
||||||
// key: "line_no",
|
|
||||||
// sorter: (a, b) => a.line_no - b.line_no,
|
|
||||||
// sortOrder:
|
|
||||||
// state.sortedInfo.columnKey === "line_no" && state.sortedInfo.order,
|
|
||||||
// //ellipsis: true,
|
|
||||||
// editable: true,
|
|
||||||
// width: 75,
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
title: t("joblines.fields.line_desc"),
|
title: t("joblines.fields.line_desc"),
|
||||||
dataIndex: "line_desc",
|
dataIndex: "line_desc",
|
||||||
@@ -38,32 +25,32 @@ export default function JobReconcilitionPartsTable({
|
|||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
title: t("joblines.fields.oem_partno"),
|
// title: t("joblines.fields.oem_partno"),
|
||||||
dataIndex: "oem_partno",
|
// dataIndex: "oem_partno",
|
||||||
key: "oem_partno",
|
// key: "oem_partno",
|
||||||
sorter: (a, b) =>
|
// sorter: (a, b) =>
|
||||||
alphaSort(
|
// alphaSort(
|
||||||
a.oem_partno ? a.oem_partno : a.op_code_desc,
|
// a.oem_partno ? a.oem_partno : a.op_code_desc,
|
||||||
b.oem_partno ? b.oem_partno : b.op_code_desc
|
// b.oem_partno ? b.oem_partno : b.op_code_desc
|
||||||
),
|
// ),
|
||||||
sortOrder:
|
// sortOrder:
|
||||||
state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order,
|
// state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order,
|
||||||
|
|
||||||
render: (text, record) => (
|
// render: (text, record) => (
|
||||||
<span>
|
// <span>
|
||||||
{record.oem_partno ? record.oem_partno : record.op_code_desc}
|
// {record.oem_partno ? record.oem_partno : record.op_code_desc}
|
||||||
</span>
|
// </span>
|
||||||
),
|
// ),
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
title: t("joblines.fields.part_type"),
|
// title: t("joblines.fields.part_type"),
|
||||||
dataIndex: "part_type",
|
// dataIndex: "part_type",
|
||||||
key: "part_type",
|
// key: "part_type",
|
||||||
sorter: (a, b) => alphaSort(a.part_type, b.part_type),
|
// sorter: (a, b) => alphaSort(a.part_type, b.part_type),
|
||||||
sortOrder:
|
// sortOrder:
|
||||||
state.sortedInfo.columnKey === "part_type" && state.sortedInfo.order,
|
// state.sortedInfo.columnKey === "part_type" && state.sortedInfo.order,
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
title: t("joblines.fields.act_price"),
|
title: t("joblines.fields.act_price"),
|
||||||
dataIndex: "act_price",
|
dataIndex: "act_price",
|
||||||
@@ -95,14 +82,7 @@ export default function JobReconcilitionPartsTable({
|
|||||||
</CurrencyFormatter>
|
</CurrencyFormatter>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: t("joblines.fields.mod_lb_hrs"),
|
|
||||||
dataIndex: "mod_lb_hrs",
|
|
||||||
key: "mod_lb_hrs",
|
|
||||||
sorter: (a, b) => a.mod_lb_hrs - b.mod_lb_hrs,
|
|
||||||
sortOrder:
|
|
||||||
state.sortedInfo.columnKey === "mod_lb_hrs" && state.sortedInfo.order,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: t("joblines.fields.status"),
|
title: t("joblines.fields.status"),
|
||||||
dataIndex: "status",
|
dataIndex: "status",
|
||||||
@@ -118,32 +98,16 @@ export default function JobReconcilitionPartsTable({
|
|||||||
};
|
};
|
||||||
const handleOnRowClick = (selectedRecordKeys, selectedRecords) => {
|
const handleOnRowClick = (selectedRecordKeys, selectedRecords) => {
|
||||||
setSelectedLines(selectedRecordKeys);
|
setSelectedLines(selectedRecordKeys);
|
||||||
calculateTotal(selectedRecords);
|
|
||||||
};
|
|
||||||
|
|
||||||
const calculateTotal = (selectedRecords) => {
|
|
||||||
let total = Dinero({ amount: 0 });
|
|
||||||
selectedRecords.forEach(
|
|
||||||
(record) =>
|
|
||||||
(total = total.add(
|
|
||||||
Dinero({ amount: record.act_price * 100 }).multiply(record.part_qty)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
setTotal(total.toFormat());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Table
|
<Table
|
||||||
size='small'
|
size="small"
|
||||||
title={() => (
|
pagination={false}
|
||||||
<div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
pagination={{ position: "top", defaultPageSize: 25 }}
|
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey='id'
|
scroll={{ y: "40vh", x: true }}
|
||||||
|
rowKey="id"
|
||||||
dataSource={jobLineData}
|
dataSource={jobLineData}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
@@ -151,7 +115,6 @@ export default function JobReconcilitionPartsTable({
|
|||||||
selectedRowKeys: selectedLines,
|
selectedRowKeys: selectedLines,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Statistic value={total} title='total' />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import React, { useMemo } from "react";
|
||||||
|
import Dinero from "dinero.js";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { Space, Statistic } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
export default function JobReconciliationTotals({
|
||||||
|
billLines,
|
||||||
|
jobLines,
|
||||||
|
selectedBillLines,
|
||||||
|
selectedJobLines,
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const totals = useMemo(() => {
|
||||||
|
const jlLookup = _.keyBy(selectedJobLines, (i) => i);
|
||||||
|
const billLookup = _.keyBy(selectedBillLines, (i) => i);
|
||||||
|
|
||||||
|
return {
|
||||||
|
joblinesTotal: jobLines
|
||||||
|
.filter((jl) => !!jlLookup[jl.id])
|
||||||
|
.reduce((acc, val) => {
|
||||||
|
console.log("acc :>> ", val);
|
||||||
|
return acc.add(
|
||||||
|
Dinero({ amount: val.act_price * 100 }).multiply(val.part_qty || 1)
|
||||||
|
);
|
||||||
|
}, Dinero()),
|
||||||
|
billLinesTotal: billLines
|
||||||
|
.filter((bl) => !!billLookup[bl.id])
|
||||||
|
.reduce((acc, val) => {
|
||||||
|
return acc.add(
|
||||||
|
Dinero({ amount: val.actual_price * 100 }).multiply(
|
||||||
|
val.quantity || 1
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, Dinero()),
|
||||||
|
};
|
||||||
|
}, [billLines, jobLines, selectedBillLines, selectedJobLines]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space direction="horizontal">
|
||||||
|
<Statistic
|
||||||
|
title={t("jobs.labels.reconciliation.joblinestotal")}
|
||||||
|
value={totals.joblinesTotal.toFormat()}
|
||||||
|
/>
|
||||||
|
<Statistic
|
||||||
|
title={t("jobs.labels.reconciliation.billlinestotal")}
|
||||||
|
value={totals.billLinesTotal.toFormat()}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -70,7 +70,6 @@ export const QUERY_BILLS_BY_JOBID = gql`
|
|||||||
}
|
}
|
||||||
order_date
|
order_date
|
||||||
deliver_by
|
deliver_by
|
||||||
exported
|
|
||||||
parts_order_lines {
|
parts_order_lines {
|
||||||
id
|
id
|
||||||
act_price
|
act_price
|
||||||
@@ -105,6 +104,7 @@ export const QUERY_BILLS_BY_JOBID = gql`
|
|||||||
state_tax_rate
|
state_tax_rate
|
||||||
local_tax_rate
|
local_tax_rate
|
||||||
is_credit_memo
|
is_credit_memo
|
||||||
|
exported
|
||||||
billlines {
|
billlines {
|
||||||
actual_price
|
actual_price
|
||||||
quantity
|
quantity
|
||||||
|
|||||||
Reference in New Issue
Block a user