250 lines
9.7 KiB
JavaScript
250 lines
9.7 KiB
JavaScript
import { DeleteFilled } from "@ant-design/icons";
|
|
import { Button, Col, Form, Input, Row, Select, Tag } from "antd";
|
|
import React, { useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
|
|
|
export default function InvoiceEnterModalLinesComponent({
|
|
lineData,
|
|
discount,
|
|
form,
|
|
responsibilityCenters,
|
|
}) {
|
|
const { t } = useTranslation();
|
|
const { setFieldsValue, getFieldsValue } = form;
|
|
|
|
const [amounts, setAmounts] = useState({ invoiceTotal: 0, enteredAmount: 0 });
|
|
|
|
const calculateTotals = () => {
|
|
setAmounts({
|
|
invoiceTotal: getFieldsValue().total,
|
|
enteredTotal: getFieldsValue("invoicelines").invoicelines
|
|
? getFieldsValue("invoicelines").invoicelines.reduce(
|
|
(acc, value) =>
|
|
acc + (value && value.actual_cost ? value.actual_cost : 0),
|
|
0
|
|
)
|
|
: 0,
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
<Form.List name="invoicelines">
|
|
{(fields, { add, remove }) => {
|
|
console.log("fields", fields);
|
|
return (
|
|
<div>
|
|
{fields.map((field, index) => (
|
|
<Form.Item required={false} key={field.key}>
|
|
<div style={{ display: "flex" }}>
|
|
<Form.Item
|
|
label={t("invoicelines.fields.line_desc")}
|
|
key={`${index}joblinename`}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: t("general.validation.required"),
|
|
},
|
|
]}
|
|
>
|
|
<Select
|
|
autoFocus
|
|
name={`le${index}`}
|
|
style={{ width: "450px" }}
|
|
onSelect={(value, opt) => {
|
|
setFieldsValue({
|
|
invoicelines: getFieldsValue([
|
|
"invoicelines",
|
|
]).invoicelines.map((item, idx) => {
|
|
if (idx === index) {
|
|
return {
|
|
...item,
|
|
joblineid: opt.key.includes("noline")
|
|
? null
|
|
: opt.key,
|
|
line_desc: opt.key.includes("noline")
|
|
? ""
|
|
: opt.value,
|
|
actual_price: opt.cost ? opt.cost : 0,
|
|
cost_center: opt.part_type
|
|
? responsibilityCenters.defaults[
|
|
opt.part_type
|
|
] || null
|
|
: null,
|
|
};
|
|
}
|
|
return item;
|
|
}),
|
|
});
|
|
}}
|
|
showSearch
|
|
>
|
|
<Select.Option
|
|
key={`${index}noline`}
|
|
value={t("invoicelines.labels.other")}
|
|
cost={0}
|
|
>
|
|
{t("invoicelines.labels.other")}
|
|
</Select.Option>
|
|
{lineData
|
|
? lineData.map((item) => (
|
|
<Select.Option
|
|
key={item.id}
|
|
value={item.line_desc}
|
|
cost={item.act_price ? item.act_price : 0}
|
|
part_type={item.part_type}
|
|
>
|
|
<Row justify="center" align="middle">
|
|
<Col span={12}> {item.line_desc}</Col>
|
|
<Col span={8}>
|
|
<Tag color="blue">{item.oem_partno}</Tag>
|
|
</Col>
|
|
<Col span={4}>
|
|
<Tag color="green">
|
|
<CurrencyFormatter>
|
|
{item.act_price || 0}
|
|
</CurrencyFormatter>
|
|
</Tag>
|
|
</Col>
|
|
</Row>
|
|
</Select.Option>
|
|
))
|
|
: null}
|
|
</Select>
|
|
</Form.Item>
|
|
|
|
{
|
|
//TODO Will need to refactor this to use proper form components in the search select above.
|
|
getFieldsValue("invoicelines")[index] &&
|
|
// getFieldsValue("invoicelines")[index].joblinename &&
|
|
!getFieldsValue("invoicelines").invoicelines[index]
|
|
.joblineid ? (
|
|
<Form.Item
|
|
label={t("invoicelines.fields.line_desc")}
|
|
key={`${index}line_desc`}
|
|
name={[field.name, "line_desc"]}
|
|
rules={[
|
|
{
|
|
required: !getFieldsValue("invoicelines")[index]
|
|
.joblineid,
|
|
message: t("general.validation.required"),
|
|
},
|
|
]}
|
|
>
|
|
<Input />
|
|
</Form.Item>
|
|
) : null
|
|
}
|
|
|
|
<Form.Item
|
|
label={t("invoicelines.fields.actual")}
|
|
key={`${index}actual_price`}
|
|
name={[field.name, "actual_price"]}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: t("general.validation.required"),
|
|
},
|
|
]}
|
|
>
|
|
<CurrencyInput
|
|
onBlur={(e) => {
|
|
setFieldsValue({
|
|
invoicelines: getFieldsValue(
|
|
"invoicelines"
|
|
).invoicelines.map((item, idx) => {
|
|
if (idx === index) {
|
|
return {
|
|
...item,
|
|
actual_cost: !!item.actual_cost
|
|
? item.actual_cost
|
|
: parseFloat(e.target.value) *
|
|
(1 - discount),
|
|
};
|
|
}
|
|
return item;
|
|
}),
|
|
});
|
|
}}
|
|
/>
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t("invoicelines.fields.actual_cost")}
|
|
key={`${index}actual_cost`}
|
|
name={[field.name, "actual_cost"]}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: t("general.validation.required"),
|
|
},
|
|
]}
|
|
>
|
|
<CurrencyInput onBlur={() => calculateTotals()} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t("invoicelines.fields.cost_center")}
|
|
key={`${index}cost_center`}
|
|
name={[field.name, "cost_center"]}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: t("general.validation.required"),
|
|
},
|
|
]}
|
|
>
|
|
<Select style={{ width: "150px" }}>
|
|
{responsibilityCenters.costs.map((item) => (
|
|
<Select.Option key={item}>{item}</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Form.Item>
|
|
|
|
<DeleteFilled
|
|
onClick={() => {
|
|
remove(field.name);
|
|
calculateTotals();
|
|
}}
|
|
/>
|
|
</div>
|
|
</Form.Item>
|
|
))}
|
|
<Form.Item>
|
|
<Button
|
|
type="dashed"
|
|
onClick={() => {
|
|
add();
|
|
}}
|
|
style={{ width: "100%" }}
|
|
>
|
|
{t("invoicelines.actions.newline")}
|
|
</Button>
|
|
</Form.Item>
|
|
</div>
|
|
);
|
|
}}
|
|
</Form.List>
|
|
<Row>
|
|
<Col span={4}>
|
|
{t("invoicelines.labels.entered")}
|
|
<CurrencyFormatter>{amounts.enteredTotal || 0}</CurrencyFormatter>
|
|
</Col>
|
|
|
|
<Col span={4}>
|
|
{amounts.invoiceTotal - amounts.enteredTotal === 0 ? (
|
|
<Tag color="green">{t("invoicelines.labels.reconciled")}</Tag>
|
|
) : (
|
|
<Tag color="red">
|
|
{t("invoicelines.labels.unreconciled")}:
|
|
<CurrencyFormatter>
|
|
{amounts.invoiceTotal - amounts.enteredTotal}
|
|
</CurrencyFormatter>
|
|
</Tag>
|
|
)}
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
);
|
|
}
|