Files
bodyshop/client/src/components/bill-form/bill-form.lines.component.jsx
2022-06-07 14:16:54 -07:00

650 lines
20 KiB
JavaScript

import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
import {
Button,
Form,
Input,
InputNumber,
Select,
Space,
Switch,
Table,
Tooltip,
} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CiecaSelect from "../../utils/Ciecaselect";
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function BillEnterModalLinesComponent({
bodyshop,
disabled,
lineData,
discount,
form,
responsibilityCenters,
billEdit,
billid,
}) {
const { t } = useTranslation();
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
const { Simple_Inventory } = useTreatments(
["Simple_Inventory"],
{},
bodyshop && bodyshop.imexshopid
);
const columns = (remove) => {
return [
{
title: t("billlines.fields.jobline"),
dataIndex: "joblineid",
editable: true,
width: "20rem",
formItemProps: (field) => {
return {
key: `${field.index}joblinename`,
name: [field.name, "joblineid"],
label: t("billlines.fields.jobline"),
rules: [
{
required: true,
//message: t("general.validation.required"),
},
],
};
},
wrapper: (props) => (
<Form.Item
noStyle
shouldUpdate={(prev, cur) =>
prev.is_credit_memo !== cur.is_credit_memo
}
>
{() => {
return props.children;
}}
</Form.Item>
),
formInput: (record, index) => (
<BillLineSearchSelect
disabled={disabled}
options={lineData}
style={{ width: "100%", minWidth: "10rem" }}
allowRemoved={form.getFieldValue("is_credit_memo") || false}
onSelect={(value, opt) => {
setFieldsValue({
billlines: getFieldsValue(["billlines"]).billlines.map(
(item, idx) => {
if (idx === index) {
return {
...item,
line_desc: opt.line_desc,
quantity: opt.part_qty || 1,
actual_price: opt.cost,
cost_center: opt.part_type
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
? opt.part_type !== "PAE"
? opt.part_type
: null
: responsibilityCenters.defaults &&
(responsibilityCenters.defaults.costs[
opt.part_type
] ||
null)
: null,
};
}
return item;
}
),
});
}}
/>
),
},
{
title: t("billlines.fields.line_desc"),
dataIndex: "line_desc",
editable: true,
formItemProps: (field) => {
return {
key: `${field.index}line_desc`,
name: [field.name, "line_desc"],
label: t("billlines.fields.line_desc"),
rules: [
{
required: true,
//message: t("general.validation.required"),
},
],
};
},
formInput: (record, index) => <Input disabled={disabled} />,
},
{
title: t("billlines.fields.quantity"),
dataIndex: "quantity",
editable: true,
width: "4rem",
formItemProps: (field) => {
return {
key: `${field.index}quantity`,
name: [field.name, "quantity"],
label: t("billlines.fields.quantity"),
rules: [
{
required: true,
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
validator(rule, value) {
if (
value &&
getFieldValue("billlines")[field.fieldKey]?.inventories
?.length > value
) {
return Promise.reject(
t("bills.validation.inventoryquantity", {
number:
getFieldValue("billlines")[field.fieldKey]
?.inventories?.length,
})
);
}
return Promise.resolve();
},
}),
],
};
},
formInput: (record, index) => (
<InputNumber precision={0} min={0} disabled={disabled} />
),
},
{
title: t("billlines.fields.actual_price"),
dataIndex: "actual_price",
width: "8rem",
editable: true,
formItemProps: (field) => {
return {
key: `${field.index}actual_price`,
name: [field.name, "actual_price"],
label: t("billlines.fields.actual_price"),
rules: [
{
required: true,
//message: t("general.validation.required"),
},
],
};
},
formInput: (record, index) => (
<CurrencyInput
min={0}
disabled={disabled}
onBlur={(e) => {
setFieldsValue({
billlines: getFieldsValue("billlines").billlines.map(
(item, idx) => {
if (idx === index) {
return {
...item,
actual_cost: !!item.actual_cost
? item.actual_cost
: Math.round(
(parseFloat(e.target.value) * (1 - discount) +
Number.EPSILON) *
100
) / 100,
};
}
return item;
}
),
});
}}
/>
),
},
{
title: t("billlines.fields.actual_cost"),
dataIndex: "actual_cost",
editable: true,
width: "8rem",
formItemProps: (field) => {
return {
key: `${field.index}actual_cost`,
name: [field.name, "actual_cost"],
label: t("billlines.fields.actual_cost"),
rules: [
{
required: true,
//message: t("general.validation.required"),
},
],
};
},
formInput: (record, index) => (
<CurrencyInput
min={0}
disabled={disabled}
controls={false}
addonAfter={
<Form.Item shouldUpdate noStyle>
{() => {
const line = getFieldsValue(["billlines"]).billlines[index];
if (!!!line) return null;
let lineDiscount = 1 - line.actual_cost / line.actual_price;
if (isNaN(lineDiscount)) lineDiscount = 0;
return (
<Tooltip title={`${(lineDiscount * 100).toFixed(2) || 0}%`}>
<DollarCircleFilled
style={{
color:
Math.abs(lineDiscount - discount) > 0.005
? lineDiscount > discount
? "orange"
: "red"
: "green",
}}
/>
</Tooltip>
);
}}
</Form.Item>
}
/>
),
// additional: (record, index) => (
// <Form.Item shouldUpdate>
// {() => {
// const line = getFieldsValue(["billlines"]).billlines[index];
// if (!!!line) return null;
// const lineDiscount = (
// 1 -
// Math.round((line.actual_cost / line.actual_price) * 100) / 100
// ).toPrecision(2);
// return (
// <Tooltip title={`${(lineDiscount * 100).toFixed(0) || 0}%`}>
// <DollarCircleFilled
// style={{
// color: lineDiscount - discount !== 0 ? "red" : "green",
// }}
// />
// </Tooltip>
// );
// }}
// </Form.Item>
// ),
},
{
title: t("billlines.fields.cost_center"),
dataIndex: "cost_center",
editable: true,
formItemProps: (field) => {
return {
key: `${field.index}cost_center`,
name: [field.name, "cost_center"],
label: t("billlines.fields.cost_center"),
valuePropName: "value",
rules: [
{
required: true,
//message: t("general.validation.required"),
},
],
};
},
formInput: (record, index) => (
<Select showSearch style={{ minWidth: "3rem" }} disabled={disabled}>
{bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? CiecaSelect(true, false)
: responsibilityCenters.costs.map((item) => (
<Select.Option key={item.name}>{item.name}</Select.Option>
))}
</Select>
),
},
{
title: t("billlines.fields.location"),
dataIndex: "location",
editable: true,
label: t("billlines.fields.location"),
formItemProps: (field) => {
return {
key: `${field.index}location`,
name: [field.name, "location"],
};
},
formInput: (record, index) => (
<Select disabled={disabled}>
{bodyshop.md_parts_locations.map((loc, idx) => (
<Select.Option key={idx} value={loc}>
{loc}
</Select.Option>
))}
</Select>
),
},
{
title: t("billlines.labels.deductedfromlbr"),
dataIndex: "deductedfromlbr",
editable: true,
formItemProps: (field) => {
return {
valuePropName: "checked",
key: `${field.index}deductedfromlbr`,
name: [field.name, "deductedfromlbr"],
};
},
formInput: (record, index) => <Switch disabled={disabled} />,
additional: (record, index) => (
<Form.Item shouldUpdate style={{ display: "inline-block" }}>
{() => {
const price = getFieldValue([
"billlines",
record.name,
"actual_price",
]);
const adjustmentRate = getFieldValue([
"billlines",
record.name,
"lbr_adjustment",
"rate",
]);
if (getFieldValue(["billlines", record.name, "deductedfromlbr"]))
return (
<div>
<Form.Item
label={t("joblines.fields.mod_lbr_ty")}
key={`${index}modlbrty`}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={[record.name, "lbr_adjustment", "mod_lbr_ty"]}
>
<Select allowClear>
<Select.Option value="LAA">
{t("joblines.fields.lbr_types.LAA")}
</Select.Option>
<Select.Option value="LAB">
{t("joblines.fields.lbr_types.LAB")}
</Select.Option>
<Select.Option value="LAD">
{t("joblines.fields.lbr_types.LAD")}
</Select.Option>
<Select.Option value="LAE">
{t("joblines.fields.lbr_types.LAE")}
</Select.Option>
<Select.Option value="LAF">
{t("joblines.fields.lbr_types.LAF")}
</Select.Option>
<Select.Option value="LAG">
{t("joblines.fields.lbr_types.LAG")}
</Select.Option>
<Select.Option value="LAM">
{t("joblines.fields.lbr_types.LAM")}
</Select.Option>
<Select.Option value="LAR">
{t("joblines.fields.lbr_types.LAR")}
</Select.Option>
<Select.Option value="LAS">
{t("joblines.fields.lbr_types.LAS")}
</Select.Option>
<Select.Option value="LAU">
{t("joblines.fields.lbr_types.LAU")}
</Select.Option>
<Select.Option value="LA1">
{t("joblines.fields.lbr_types.LA1")}
</Select.Option>
<Select.Option value="LA2">
{t("joblines.fields.lbr_types.LA2")}
</Select.Option>
<Select.Option value="LA3">
{t("joblines.fields.lbr_types.LA3")}
</Select.Option>
<Select.Option value="LA4">
{t("joblines.fields.lbr_types.LA4")}
</Select.Option>
</Select>
</Form.Item>
<Form.Item
label={t("jobs.labels.adjustmentrate")}
name={[record.name, "lbr_adjustment", "rate"]}
initialValue={bodyshop.default_adjustment_rate}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber precision={2} min={0.01} />
</Form.Item>
{price &&
adjustmentRate &&
`${(price / adjustmentRate).toFixed(1)} hrs`}
</div>
);
return <></>;
}}
</Form.Item>
),
},
{
title: t("billlines.fields.federal_tax_applicable"),
dataIndex: "applicable_taxes.federal",
editable: true,
formItemProps: (field) => {
return {
key: `${field.index}fedtax`,
valuePropName: "checked",
initialValue: true,
name: [field.name, "applicable_taxes", "federal"],
};
},
formInput: (record, index) => <Switch disabled={disabled} />,
},
{
title: t("billlines.fields.state_tax_applicable"),
dataIndex: "applicable_taxes.state",
editable: true,
formItemProps: (field) => {
return {
key: `${field.index}statetax`,
valuePropName: "checked",
name: [field.name, "applicable_taxes", "state"],
};
},
formInput: (record, index) => <Switch disabled={disabled} />,
},
{
title: t("billlines.fields.local_tax_applicable"),
dataIndex: "applicable_taxes.local",
editable: true,
formItemProps: (field) => {
return {
key: `${field.index}localtax`,
valuePropName: "checked",
name: [field.name, "applicable_taxes", "local"],
};
},
formInput: (record, index) => <Switch disabled={disabled} />,
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
render: (text, record) => (
<Form.Item shouldUpdate noStyle>
{() => (
<Space wrap>
<Button
disabled={
disabled ||
getFieldValue("billlines")[record.fieldKey]?.inventories
?.length > 0
}
onClick={() => remove(record.name)}
>
<DeleteFilled />
</Button>
{Simple_Inventory.treatment === "on" && (
<BilllineAddInventory
disabled={
!billEdit ||
form.isFieldsTouched() ||
form.getFieldValue("is_credit_memo")
}
billline={getFieldValue("billlines")[record.fieldKey]}
jobid={getFieldValue("jobid")}
/>
)}
</Space>
)}
</Form.Item>
),
},
];
};
const mergedColumns = (remove) =>
columns(remove).map((col) => {
if (!col.editable) return col;
return {
...col,
onCell: (record) => ({
record,
formItemProps: col.formItemProps,
formInput: col.formInput,
additional: col.additional,
dataIndex: col.dataIndex,
title: col.title,
}),
};
});
return (
<Form.List name="billlines">
{(fields, { add, remove, move }) => {
return (
<>
<Table
components={{
body: {
cell: EditableCell,
},
}}
size="small"
bordered
dataSource={fields}
columns={mergedColumns(remove)}
scroll={{ x: true }}
pagination={false}
rowClassName="editable-row"
/>
<Form.Item>
<Button
disabled={disabled}
onClick={() => {
add();
}}
style={{ width: "100%" }}
>
{t("billlines.actions.newline")}
</Button>
</Form.Item>
</>
);
}}
</Form.List>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(BillEnterModalLinesComponent);
const EditableCell = ({
dataIndex,
title,
inputType,
record,
index,
children,
formInput,
formItemProps,
additional,
wrapper,
...restProps
}) => {
if (additional)
return (
<td {...restProps}>
<Space size="small">
<Form.Item
name={dataIndex}
labelCol={{ span: 0 }}
{...(formItemProps && formItemProps(record))}
>
{(formInput && formInput(record, record.name)) || children}
</Form.Item>
{additional && additional(record, record.name)}
</Space>
</td>
);
if (wrapper)
return (
<wrapper>
<td {...restProps}>
<Form.Item
labelCol={{ span: 0 }}
name={dataIndex}
{...(formItemProps && formItemProps(record))}
>
{(formInput && formInput(record, record.name)) || children}
</Form.Item>
</td>
</wrapper>
);
return (
<td {...restProps}>
<Form.Item
labelCol={{ span: 0 }}
name={dataIndex}
{...(formItemProps && formItemProps(record))}
>
{(formInput && formInput(record, record.name)) || children}
</Form.Item>
</td>
);
};