Started refactoring parts order reconciliation BOD-406

This commit is contained in:
Patrick Fic
2020-10-01 09:21:22 -07:00
parent c68835153f
commit 88951da11d
7 changed files with 117 additions and 98 deletions

View File

@@ -68,5 +68,5 @@
.ant-table-cell { .ant-table-cell {
// background-color: red; // background-color: red;
padding: 0.2rem !important; //padding: 0.2rem !important;
} }

View File

@@ -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>
); );
} }

View File

@@ -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>
); );
} }

View File

@@ -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} />

View File

@@ -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>
); );
} }

View File

@@ -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>
);
}

View File

@@ -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