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