328 lines
10 KiB
JavaScript
328 lines
10 KiB
JavaScript
import { useApolloClient } from "@apollo/client";
|
|
import {
|
|
Button,
|
|
Divider,
|
|
Form,
|
|
Input,
|
|
Select,
|
|
Space,
|
|
Statistic,
|
|
Switch,
|
|
Upload,
|
|
} from "antd";
|
|
import React, { useEffect, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { connect } from "react-redux";
|
|
import { createStructuredSelector } from "reselect";
|
|
import { CHECK_BILL_INVOICE_NUMBER } from "../../graphql/bills.queries";
|
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
import AlertComponent from "../alert/alert.component";
|
|
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
|
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
|
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
|
import BillFormLines from "./bill-form.lines.component";
|
|
import { CalculateBillTotal } from "./bill-form.totals.utility";
|
|
|
|
const mapStateToProps = createStructuredSelector({
|
|
bodyshop: selectBodyshop,
|
|
});
|
|
const mapDispatchToProps = (dispatch) => ({});
|
|
|
|
export function BillFormComponent({
|
|
bodyshop,
|
|
disabled,
|
|
form,
|
|
vendorAutoCompleteOptions,
|
|
lineData,
|
|
responsibilityCenters,
|
|
loadLines,
|
|
billEdit,
|
|
disableInvNumber,
|
|
}) {
|
|
const { t } = useTranslation();
|
|
const client = useApolloClient();
|
|
const [discount, setDiscount] = useState(0);
|
|
|
|
const handleVendorSelect = (props, opt) => {
|
|
setDiscount(opt.discount);
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (form.getFieldValue("vendorid") && vendorAutoCompleteOptions) {
|
|
const vendorId = form.getFieldValue("vendorid");
|
|
const matchingVendors = vendorAutoCompleteOptions.filter(
|
|
(v) => v.id === vendorId
|
|
);
|
|
if (matchingVendors.length === 1) {
|
|
setDiscount(matchingVendors[0].discount);
|
|
}
|
|
}
|
|
if (form.getFieldValue("jobid")) {
|
|
loadLines({ variables: { id: form.getFieldValue("jobid") } });
|
|
}
|
|
}, [form, setDiscount, vendorAutoCompleteOptions, loadLines]);
|
|
|
|
return (
|
|
<div>
|
|
<FormFieldsChanged form={form} />
|
|
<Form.Item
|
|
style={{ display: "none" }}
|
|
name="isinhouse"
|
|
valuePropName="checked"
|
|
>
|
|
<Switch />
|
|
</Form.Item>
|
|
<LayoutFormRow grow>
|
|
<Form.Item
|
|
name="jobid"
|
|
label={t("bills.fields.ro_number")}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: t("general.validation.required"),
|
|
},
|
|
]}
|
|
>
|
|
<JobSearchSelect
|
|
disabled={billEdit || disabled}
|
|
convertedOnly
|
|
// notExported={false}
|
|
onBlur={() => {
|
|
if (form.getFieldValue("jobid") !== null) {
|
|
loadLines({ variables: { id: form.getFieldValue("jobid") } });
|
|
}
|
|
}}
|
|
/>
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t("bills.fields.vendor")}
|
|
name="vendorid"
|
|
style={{ display: billEdit ? "none" : null }}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: t("general.validation.required"),
|
|
},
|
|
]}
|
|
>
|
|
<VendorSearchSelect
|
|
disabled={disabled}
|
|
options={vendorAutoCompleteOptions}
|
|
onSelect={handleVendorSelect}
|
|
/>
|
|
</Form.Item>
|
|
</LayoutFormRow>
|
|
<LayoutFormRow>
|
|
<Form.Item
|
|
label={t("bills.fields.invoice_number")}
|
|
name="invoice_number"
|
|
validateTrigger="onBlur"
|
|
hasFeedback
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: t("general.validation.required"),
|
|
},
|
|
({ getFieldValue }) => ({
|
|
async validator(rule, value) {
|
|
const vendorid = getFieldValue("vendorid");
|
|
if (vendorid && value) {
|
|
const response = await client.query({
|
|
query: CHECK_BILL_INVOICE_NUMBER,
|
|
variables: {
|
|
invoice_number: value,
|
|
vendorid: vendorid,
|
|
},
|
|
});
|
|
|
|
if (response.data.bills_aggregate.aggregate.count === 0) {
|
|
return Promise.resolve();
|
|
} else if (
|
|
response.data.bills_aggregate.nodes.length === 1 &&
|
|
response.data.bills_aggregate.nodes[0].id ===
|
|
form.getFieldValue("id")
|
|
) {
|
|
return Promise.resolve();
|
|
}
|
|
return Promise.reject(
|
|
t("bills.validation.unique_invoice_number")
|
|
);
|
|
} else {
|
|
return Promise.resolve();
|
|
}
|
|
},
|
|
}),
|
|
]}
|
|
>
|
|
<Input disabled={disabled || disableInvNumber} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t("bills.fields.date")}
|
|
name="date"
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: t("general.validation.required"),
|
|
},
|
|
]}
|
|
>
|
|
<FormDatePicker disabled={disabled} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t("bills.fields.is_credit_memo")}
|
|
name="is_credit_memo"
|
|
valuePropName="checked"
|
|
>
|
|
<Switch />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t("bills.fields.total")}
|
|
name="total"
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: t("general.validation.required"),
|
|
},
|
|
]}
|
|
>
|
|
<CurrencyInput min={0} disabled={disabled} />
|
|
</Form.Item>
|
|
<Form.Item label={t("bills.fields.allpartslocation")} name="location">
|
|
<Select style={{ width: "10rem" }} disabled={disabled} allowClear>
|
|
{bodyshop.md_parts_locations.map((loc, idx) => (
|
|
<Select.Option key={idx} value={loc}>
|
|
{loc}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Form.Item>
|
|
</LayoutFormRow>
|
|
<LayoutFormRow>
|
|
<Form.Item
|
|
span={3}
|
|
label={t("bills.fields.federal_tax_rate")}
|
|
name="federal_tax_rate"
|
|
>
|
|
<CurrencyInput min={0} disabled={disabled} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
span={3}
|
|
label={t("bills.fields.state_tax_rate")}
|
|
name="state_tax_rate"
|
|
>
|
|
<CurrencyInput min={0} disabled={disabled} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
span={3}
|
|
label={t("bills.fields.local_tax_rate")}
|
|
name="local_tax_rate"
|
|
>
|
|
<CurrencyInput min={0} />
|
|
</Form.Item>
|
|
<Form.Item shouldUpdate span={15}>
|
|
{() => {
|
|
const values = form.getFieldsValue([
|
|
"billlines",
|
|
"total",
|
|
"federal_tax_rate",
|
|
"state_tax_rate",
|
|
"local_tax_rate",
|
|
]);
|
|
let totals;
|
|
if (
|
|
!!values.total &&
|
|
!!values.billlines &&
|
|
values.billlines.length > 0
|
|
)
|
|
totals = CalculateBillTotal(values);
|
|
if (!!totals)
|
|
return (
|
|
<div>
|
|
<Space wrap>
|
|
<Statistic
|
|
title={t("bills.labels.subtotal")}
|
|
value={totals.subtotal.toFormat()}
|
|
precision={2}
|
|
/>
|
|
<Statistic
|
|
title={t("bills.labels.federal_tax")}
|
|
value={totals.federalTax.toFormat()}
|
|
precision={2}
|
|
/>
|
|
<Statistic
|
|
title={t("bills.labels.state_tax")}
|
|
value={totals.stateTax.toFormat()}
|
|
precision={2}
|
|
/>
|
|
<Statistic
|
|
title={t("bills.labels.local_tax")}
|
|
value={totals.localTax.toFormat()}
|
|
precision={2}
|
|
/>
|
|
<Statistic
|
|
title={t("bills.labels.entered_total")}
|
|
value={totals.enteredTotal.toFormat()}
|
|
precision={2}
|
|
/>
|
|
<Statistic
|
|
title={t("bills.labels.bill_total")}
|
|
value={totals.invoiceTotal.toFormat()}
|
|
precision={2}
|
|
/>
|
|
<Statistic
|
|
title={t("bills.labels.discrepancy")}
|
|
valueStyle={{
|
|
color:
|
|
totals.discrepancy.getAmount() === 0
|
|
? "green"
|
|
: "red",
|
|
}}
|
|
value={totals.discrepancy.toFormat()}
|
|
precision={2}
|
|
/>
|
|
</Space>
|
|
{form.getFieldValue("is_credit_memo") ? (
|
|
<AlertComponent
|
|
type="warning"
|
|
message={t("bills.labels.enteringcreditmemo")}
|
|
/>
|
|
) : null}
|
|
</div>
|
|
);
|
|
return null;
|
|
}}
|
|
</Form.Item>
|
|
</LayoutFormRow>
|
|
<Divider orientation="left">{t("bills.labels.bill_lines")}</Divider>
|
|
<BillFormLines
|
|
lineData={lineData}
|
|
discount={discount}
|
|
form={form}
|
|
responsibilityCenters={responsibilityCenters}
|
|
disabled={disabled}
|
|
/>
|
|
<Form.Item
|
|
name="upload"
|
|
label="Upload"
|
|
style={{ display: billEdit ? "none" : null }}
|
|
valuePropName="fileList"
|
|
getValueFromEvent={(e) => {
|
|
if (Array.isArray(e)) {
|
|
return e;
|
|
}
|
|
return e && e.fileList;
|
|
}}
|
|
>
|
|
<Upload name="logo" beforeUpload={() => false} listType="picture">
|
|
<Button>Click to upload</Button>
|
|
</Upload>
|
|
</Form.Item>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default connect(mapStateToProps, mapDispatchToProps)(BillFormComponent);
|