@@ -1,26 +1,16 @@
|
|||||||
import Icon, { UploadOutlined } from "@ant-design/icons";
|
import Icon, {UploadOutlined} from "@ant-design/icons";
|
||||||
import { useApolloClient } from "@apollo/client";
|
import {useApolloClient} from "@apollo/client";
|
||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
import {useTreatments} from "@splitsoftware/splitio-react";
|
||||||
import {
|
import {Alert, Divider, Form, Input, Select, Space, Statistic, Switch, Upload,} from "antd";
|
||||||
Alert,
|
|
||||||
Divider,
|
|
||||||
Form,
|
|
||||||
Input,
|
|
||||||
Select,
|
|
||||||
Space,
|
|
||||||
Statistic,
|
|
||||||
Switch,
|
|
||||||
Upload,
|
|
||||||
} from "antd";
|
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import { MdOpenInNew } from "react-icons/md";
|
import {MdOpenInNew} from "react-icons/md";
|
||||||
import { connect } from "react-redux";
|
import {connect} from "react-redux";
|
||||||
import { Link } from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import {createStructuredSelector} from "reselect";
|
||||||
import { CHECK_BILL_INVOICE_NUMBER } from "../../graphql/bills.queries";
|
import {CHECK_BILL_INVOICE_NUMBER} from "../../graphql/bills.queries";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||||
@@ -30,488 +20,488 @@ import JobSearchSelect from "../job-search-select/job-search-select.component";
|
|||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||||
import BillFormLines from "./bill-form.lines.component";
|
import BillFormLines from "./bill-form.lines.component";
|
||||||
import { CalculateBillTotal } from "./bill-form.totals.utility";
|
import {CalculateBillTotal} from "./bill-form.totals.utility";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({});
|
const mapDispatchToProps = (dispatch) => ({});
|
||||||
|
|
||||||
export function BillFormComponent({
|
export function BillFormComponent({
|
||||||
bodyshop,
|
bodyshop,
|
||||||
disabled,
|
disabled,
|
||||||
form,
|
form,
|
||||||
vendorAutoCompleteOptions,
|
vendorAutoCompleteOptions,
|
||||||
lineData,
|
lineData,
|
||||||
responsibilityCenters,
|
responsibilityCenters,
|
||||||
loadLines,
|
loadLines,
|
||||||
billEdit,
|
billEdit,
|
||||||
disableInvNumber,
|
disableInvNumber,
|
||||||
job,
|
job,
|
||||||
loadOutstandingReturns,
|
loadOutstandingReturns,
|
||||||
loadInventory,
|
loadInventory,
|
||||||
preferredMake,
|
preferredMake,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const {t} = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const [discount, setDiscount] = useState(0);
|
const [discount, setDiscount] = useState(0);
|
||||||
const { Extended_Bill_Posting } = useTreatments(
|
const {Extended_Bill_Posting} = useTreatments(
|
||||||
["Extended_Bill_Posting"],
|
["Extended_Bill_Posting"],
|
||||||
{},
|
{},
|
||||||
bodyshop.imexshopid
|
bodyshop.imexshopid
|
||||||
);
|
);
|
||||||
const { ClosingPeriod } = useTreatments(
|
const {ClosingPeriod} = useTreatments(
|
||||||
["ClosingPeriod"],
|
["ClosingPeriod"],
|
||||||
{},
|
{},
|
||||||
bodyshop.imexshopid
|
bodyshop.imexshopid
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleVendorSelect = (props, opt) => {
|
const handleVendorSelect = (props, opt) => {
|
||||||
setDiscount(opt.discount);
|
setDiscount(opt.discount);
|
||||||
|
|
||||||
opt &&
|
opt &&
|
||||||
!billEdit &&
|
!billEdit &&
|
||||||
loadOutstandingReturns({
|
|
||||||
variables: {
|
|
||||||
jobId: form.getFieldValue("jobid"),
|
|
||||||
vendorId: opt.value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (job) form.validateFields(["is_credit_memo"]);
|
|
||||||
}, [job, form]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const vendorId = form.getFieldValue("vendorid");
|
|
||||||
if (vendorId && vendorAutoCompleteOptions) {
|
|
||||||
const matchingVendors = vendorAutoCompleteOptions.filter(
|
|
||||||
(v) => v.id === vendorId
|
|
||||||
);
|
|
||||||
if (matchingVendors.length === 1) {
|
|
||||||
setDiscount(matchingVendors[0].discount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const jobId = form.getFieldValue("jobid");
|
|
||||||
if (jobId) {
|
|
||||||
loadLines({ variables: { id: jobId } });
|
|
||||||
if (form.getFieldValue("is_credit_memo") && vendorId && !billEdit) {
|
|
||||||
loadOutstandingReturns({
|
loadOutstandingReturns({
|
||||||
variables: {
|
variables: {
|
||||||
jobId: jobId,
|
jobId: form.getFieldValue("jobid"),
|
||||||
vendorId: vendorId,
|
vendorId: opt.value,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
if (vendorId === bodyshop.inhousevendorid && !billEdit) {
|
useEffect(() => {
|
||||||
loadInventory();
|
if (job) form.validateFields(["is_credit_memo"]);
|
||||||
}
|
}, [job, form]);
|
||||||
}, [
|
|
||||||
form,
|
|
||||||
billEdit,
|
|
||||||
loadOutstandingReturns,
|
|
||||||
loadInventory,
|
|
||||||
setDiscount,
|
|
||||||
vendorAutoCompleteOptions,
|
|
||||||
loadLines,
|
|
||||||
bodyshop.inhousevendorid,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<div>
|
const vendorId = form.getFieldValue("vendorid");
|
||||||
<FormFieldsChanged form={form} />
|
if (vendorId && vendorAutoCompleteOptions) {
|
||||||
<Form.Item
|
const matchingVendors = vendorAutoCompleteOptions.filter(
|
||||||
style={{ display: "none" }}
|
(v) => v.id === vendorId
|
||||||
name="isinhouse"
|
);
|
||||||
valuePropName="checked"
|
if (matchingVendors.length === 1) {
|
||||||
>
|
setDiscount(matchingVendors[0].discount);
|
||||||
<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") } });
|
|
||||||
if (form.getFieldValue("vendorid") !== null) {
|
|
||||||
loadOutstandingReturns({
|
|
||||||
variables: {
|
|
||||||
jobId: form.getFieldValue("jobid"),
|
|
||||||
vendorId: form.getFieldValue("vendorid"),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t("bills.fields.vendor")}
|
|
||||||
name="vendorid"
|
|
||||||
// style={{ display: billEdit ? "none" : null }}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
({ getFieldValue }) => ({
|
|
||||||
validator(rule, value) {
|
|
||||||
if (
|
|
||||||
value &&
|
|
||||||
!getFieldValue(["isinhouse"]) &&
|
|
||||||
value === bodyshop.inhousevendorid
|
|
||||||
) {
|
|
||||||
return Promise.reject(t("bills.validation.manualinhouse"));
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<VendorSearchSelect
|
|
||||||
disabled={disabled}
|
|
||||||
options={vendorAutoCompleteOptions}
|
|
||||||
preferredMake={preferredMake}
|
|
||||||
onSelect={handleVendorSelect}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</LayoutFormRow>
|
|
||||||
{job &&
|
|
||||||
job.ious &&
|
|
||||||
job.ious.length > 0 &&
|
|
||||||
job.ious.map((iou) => (
|
|
||||||
<Alert
|
|
||||||
key={iou.id}
|
|
||||||
type="warning"
|
|
||||||
message={
|
|
||||||
<Space>
|
|
||||||
{t("bills.labels.iouexists")}
|
|
||||||
<Link
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
to={`/manage/jobs/${iou.id}?tab=repairdata`}
|
|
||||||
>
|
|
||||||
<Space>
|
|
||||||
{iou.ro_number}
|
|
||||||
<Icon component={MdOpenInNew} />
|
|
||||||
</Space>
|
|
||||||
</Link>
|
|
||||||
</Space>
|
|
||||||
}
|
}
|
||||||
/>
|
}
|
||||||
))}
|
const jobId = form.getFieldValue("jobid");
|
||||||
<LayoutFormRow>
|
if (jobId) {
|
||||||
<Form.Item
|
loadLines({variables: {id: jobId}});
|
||||||
label={t("bills.fields.invoice_number")}
|
if (form.getFieldValue("is_credit_memo") && vendorId && !billEdit) {
|
||||||
name="invoice_number"
|
loadOutstandingReturns({
|
||||||
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: {
|
variables: {
|
||||||
invoice_number: value,
|
jobId: jobId,
|
||||||
vendorid: vendorid,
|
vendorId: vendorId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (response.data.bills_aggregate.aggregate.count === 0) {
|
if (vendorId === bodyshop.inhousevendorid && !billEdit) {
|
||||||
return Promise.resolve();
|
loadInventory();
|
||||||
} else if (
|
}
|
||||||
response.data.bills_aggregate.nodes.length === 1 &&
|
}, [
|
||||||
response.data.bills_aggregate.nodes[0].id ===
|
form,
|
||||||
form.getFieldValue("id")
|
billEdit,
|
||||||
) {
|
loadOutstandingReturns,
|
||||||
return Promise.resolve();
|
loadInventory,
|
||||||
}
|
setDiscount,
|
||||||
return Promise.reject(
|
vendorAutoCompleteOptions,
|
||||||
t("bills.validation.unique_invoice_number")
|
loadLines,
|
||||||
);
|
bodyshop.inhousevendorid,
|
||||||
} 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"),
|
|
||||||
},
|
|
||||||
({ getFieldValue }) => ({
|
|
||||||
validator(rule, value) {
|
|
||||||
if (
|
|
||||||
ClosingPeriod.treatment === "on" &&
|
|
||||||
bodyshop.accountingconfig.ClosingPeriod
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
dayjs(value)
|
|
||||||
.startOf("day")
|
|
||||||
.isSameOrAfter(
|
|
||||||
dayjs(
|
|
||||||
bodyshop.accountingconfig.ClosingPeriod[0]
|
|
||||||
).startOf("day")
|
|
||||||
) &&
|
|
||||||
dayjs(value)
|
|
||||||
.startOf("day")
|
|
||||||
.isSameOrBefore(
|
|
||||||
dayjs(
|
|
||||||
bodyshop.accountingconfig.ClosingPeriod[1]
|
|
||||||
).endOf("day")
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return Promise.resolve();
|
|
||||||
} else {
|
|
||||||
return Promise.reject(t("bills.validation.closingperiod"));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<FormDatePicker disabled={disabled} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t("bills.fields.is_credit_memo")}
|
|
||||||
name="is_credit_memo"
|
|
||||||
valuePropName="checked"
|
|
||||||
rules={[
|
|
||||||
({ getFieldValue }) => ({
|
|
||||||
validator(rule, value) {
|
|
||||||
if (
|
|
||||||
value === true &&
|
|
||||||
getFieldValue("jobid") &&
|
|
||||||
getFieldValue("vendorid")
|
|
||||||
) {
|
|
||||||
//Removed as this would cause an additional reload when validating the form on submit and clear the values.
|
|
||||||
// loadOutstandingReturns({
|
|
||||||
// variables: {
|
|
||||||
// jobId: form.getFieldValue("jobid"),
|
|
||||||
// vendorId: form.getFieldValue("vendorid"),
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
return (
|
||||||
!bodyshop.bill_allow_post_to_closed &&
|
<div>
|
||||||
job &&
|
<FormFieldsChanged form={form}/>
|
||||||
(job.status === bodyshop.md_ro_statuses.default_invoiced ||
|
<Form.Item
|
||||||
job.status === bodyshop.md_ro_statuses.default_exported ||
|
style={{display: "none"}}
|
||||||
job.status === bodyshop.md_ro_statuses.default_void) &&
|
name="isinhouse"
|
||||||
(value === false || !value)
|
valuePropName="checked"
|
||||||
) {
|
>
|
||||||
return Promise.reject(t("bills.labels.onlycmforinvoiced"));
|
<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")}});
|
||||||
|
if (form.getFieldValue("vendorid") !== null) {
|
||||||
|
loadOutstandingReturns({
|
||||||
|
variables: {
|
||||||
|
jobId: form.getFieldValue("jobid"),
|
||||||
|
vendorId: form.getFieldValue("vendorid"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("bills.fields.vendor")}
|
||||||
|
name="vendorid"
|
||||||
|
// style={{ display: billEdit ? "none" : null }}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
({getFieldValue}) => ({
|
||||||
|
validator(rule, value) {
|
||||||
|
if (
|
||||||
|
value &&
|
||||||
|
!getFieldValue(["isinhouse"]) &&
|
||||||
|
value === bodyshop.inhousevendorid
|
||||||
|
) {
|
||||||
|
return Promise.reject(t("bills.validation.manualinhouse"));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<VendorSearchSelect
|
||||||
|
disabled={disabled}
|
||||||
|
options={vendorAutoCompleteOptions}
|
||||||
|
preferredMake={preferredMake}
|
||||||
|
onSelect={handleVendorSelect}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</LayoutFormRow>
|
||||||
|
{job &&
|
||||||
|
job.ious &&
|
||||||
|
job.ious.length > 0 &&
|
||||||
|
job.ious.map((iou) => (
|
||||||
|
<Alert
|
||||||
|
key={iou.id}
|
||||||
|
type="warning"
|
||||||
|
message={
|
||||||
|
<Space>
|
||||||
|
{t("bills.labels.iouexists")}
|
||||||
|
<Link
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
to={`/manage/jobs/${iou.id}?tab=repairdata`}
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
{iou.ro_number}
|
||||||
|
<Icon component={MdOpenInNew}/>
|
||||||
|
</Space>
|
||||||
|
</Link>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return Promise.resolve();
|
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 ===
|
||||||
<Switch />
|
form.getFieldValue("id")
|
||||||
</Form.Item>
|
) {
|
||||||
<Form.Item
|
return Promise.resolve();
|
||||||
label={t("bills.fields.total")}
|
}
|
||||||
name="total"
|
return Promise.reject(
|
||||||
rules={[
|
t("bills.validation.unique_invoice_number")
|
||||||
{
|
);
|
||||||
required: true,
|
} else {
|
||||||
//message: t("general.validation.required"),
|
return Promise.resolve();
|
||||||
},
|
}
|
||||||
]}
|
},
|
||||||
>
|
}),
|
||||||
<CurrencyInput min={0} disabled={disabled} />
|
]}
|
||||||
</Form.Item>
|
>
|
||||||
{!billEdit && (
|
<Input disabled={disabled || disableInvNumber}/>
|
||||||
<Form.Item label={t("bills.fields.allpartslocation")} name="location">
|
</Form.Item>
|
||||||
<Select style={{ width: "10rem" }} disabled={disabled} allowClear>
|
<Form.Item
|
||||||
{bodyshop.md_parts_locations.map((loc, idx) => (
|
label={t("bills.fields.date")}
|
||||||
<Select.Option key={idx} value={loc}>
|
name="date"
|
||||||
{loc}
|
rules={[
|
||||||
</Select.Option>
|
{
|
||||||
))}
|
required: true,
|
||||||
</Select>
|
//message: t("general.validation.required"),
|
||||||
</Form.Item>
|
},
|
||||||
)}
|
({getFieldValue}) => ({
|
||||||
</LayoutFormRow>
|
validator(rule, value) {
|
||||||
<LayoutFormRow>
|
if (
|
||||||
<Form.Item
|
ClosingPeriod.treatment === "on" &&
|
||||||
span={3}
|
bodyshop.accountingconfig.ClosingPeriod
|
||||||
label={t("bills.fields.federal_tax_rate")}
|
) {
|
||||||
name="federal_tax_rate"
|
if (
|
||||||
>
|
dayjs(value)
|
||||||
<CurrencyInput min={0} disabled={disabled} />
|
.startOf("day")
|
||||||
</Form.Item>
|
.isSameOrAfter(
|
||||||
<Form.Item
|
dayjs(
|
||||||
span={3}
|
bodyshop.accountingconfig.ClosingPeriod[0]
|
||||||
label={t("bills.fields.state_tax_rate")}
|
).startOf("day")
|
||||||
name="state_tax_rate"
|
) &&
|
||||||
>
|
dayjs(value)
|
||||||
<CurrencyInput min={0} disabled={disabled} />
|
.startOf("day")
|
||||||
</Form.Item>
|
.isSameOrBefore(
|
||||||
<Form.Item
|
dayjs(
|
||||||
span={3}
|
bodyshop.accountingconfig.ClosingPeriod[1]
|
||||||
label={t("bills.fields.local_tax_rate")}
|
).endOf("day")
|
||||||
name="local_tax_rate"
|
)
|
||||||
>
|
) {
|
||||||
<CurrencyInput min={0} />
|
return Promise.resolve();
|
||||||
</Form.Item>
|
} else {
|
||||||
<Form.Item shouldUpdate span={15}>
|
return Promise.reject(t("bills.validation.closingperiod"));
|
||||||
{() => {
|
}
|
||||||
const values = form.getFieldsValue([
|
} else {
|
||||||
"billlines",
|
return Promise.resolve();
|
||||||
"total",
|
}
|
||||||
"federal_tax_rate",
|
},
|
||||||
"state_tax_rate",
|
}),
|
||||||
"local_tax_rate",
|
]}
|
||||||
]);
|
>
|
||||||
let totals;
|
<FormDatePicker disabled={disabled}/>
|
||||||
if (
|
</Form.Item>
|
||||||
!!values.total &&
|
<Form.Item
|
||||||
!!values.billlines &&
|
label={t("bills.fields.is_credit_memo")}
|
||||||
values.billlines.length > 0
|
name="is_credit_memo"
|
||||||
)
|
valuePropName="checked"
|
||||||
totals = CalculateBillTotal(values);
|
rules={[
|
||||||
if (!!totals)
|
({getFieldValue}) => ({
|
||||||
return (
|
validator(rule, value) {
|
||||||
<div>
|
if (
|
||||||
<Space wrap>
|
value === true &&
|
||||||
<Statistic
|
getFieldValue("jobid") &&
|
||||||
title={t("bills.labels.subtotal")}
|
getFieldValue("vendorid")
|
||||||
value={totals.subtotal.toFormat()}
|
) {
|
||||||
precision={2}
|
//Removed as this would cause an additional reload when validating the form on submit and clear the values.
|
||||||
/>
|
// loadOutstandingReturns({
|
||||||
<Statistic
|
// variables: {
|
||||||
title={t("bills.labels.federal_tax")}
|
// jobId: form.getFieldValue("jobid"),
|
||||||
value={totals.federalTax.toFormat()}
|
// vendorId: form.getFieldValue("vendorid"),
|
||||||
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>
|
|
||||||
|
|
||||||
{Extended_Bill_Posting.treatment === "on" ? (
|
if (
|
||||||
<BillFormLinesExtended
|
!bodyshop.bill_allow_post_to_closed &&
|
||||||
lineData={lineData}
|
job &&
|
||||||
discount={discount}
|
(job.status === bodyshop.md_ro_statuses.default_invoiced ||
|
||||||
form={form}
|
job.status === bodyshop.md_ro_statuses.default_exported ||
|
||||||
responsibilityCenters={responsibilityCenters}
|
job.status === bodyshop.md_ro_statuses.default_void) &&
|
||||||
disabled={disabled}
|
(value === false || !value)
|
||||||
/>
|
) {
|
||||||
) : (
|
return Promise.reject(t("bills.labels.onlycmforinvoiced"));
|
||||||
<BillFormLines
|
}
|
||||||
lineData={lineData}
|
|
||||||
discount={discount}
|
|
||||||
form={form}
|
|
||||||
responsibilityCenters={responsibilityCenters}
|
|
||||||
disabled={disabled}
|
|
||||||
billEdit={billEdit}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Form.Item
|
return Promise.resolve();
|
||||||
name="upload"
|
},
|
||||||
label="Upload"
|
}),
|
||||||
style={{ display: billEdit ? "none" : null }}
|
]}
|
||||||
valuePropName="fileList"
|
>
|
||||||
getValueFromEvent={(e) => {
|
<Switch/>
|
||||||
if (Array.isArray(e)) {
|
</Form.Item>
|
||||||
return e;
|
<Form.Item
|
||||||
}
|
label={t("bills.fields.total")}
|
||||||
return e && e.fileList;
|
name="total"
|
||||||
}}
|
rules={[
|
||||||
>
|
{
|
||||||
<Upload.Dragger
|
required: true,
|
||||||
multiple={true}
|
//message: t("general.validation.required"),
|
||||||
name="logo"
|
},
|
||||||
beforeUpload={() => false}
|
]}
|
||||||
listType="picture"
|
>
|
||||||
>
|
<CurrencyInput min={0} disabled={disabled}/>
|
||||||
<>
|
</Form.Item>
|
||||||
<p className="ant-upload-drag-icon">
|
{!billEdit && (
|
||||||
<UploadOutlined />
|
<Form.Item label={t("bills.fields.allpartslocation")} name="location">
|
||||||
</p>
|
<Select style={{width: "10rem"}} disabled={disabled} allowClear>
|
||||||
<p className="ant-upload-text">
|
{bodyshop.md_parts_locations.map((loc, idx) => (
|
||||||
Click or drag files to this area to upload.
|
<Select.Option key={idx} value={loc}>
|
||||||
</p>
|
{loc}
|
||||||
</>
|
</Select.Option>
|
||||||
</Upload.Dragger>
|
))}
|
||||||
</Form.Item>
|
</Select>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
|
{Extended_Bill_Posting.treatment === "on" ? (
|
||||||
|
<BillFormLinesExtended
|
||||||
|
lineData={lineData}
|
||||||
|
discount={discount}
|
||||||
|
form={form}
|
||||||
|
responsibilityCenters={responsibilityCenters}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<BillFormLines
|
||||||
|
lineData={lineData}
|
||||||
|
discount={discount}
|
||||||
|
form={form}
|
||||||
|
responsibilityCenters={responsibilityCenters}
|
||||||
|
disabled={disabled}
|
||||||
|
billEdit={billEdit}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<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.Dragger
|
||||||
|
multiple={true}
|
||||||
|
name="logo"
|
||||||
|
beforeUpload={() => false}
|
||||||
|
listType="picture"
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<p className="ant-upload-drag-icon">
|
||||||
|
<UploadOutlined/>
|
||||||
|
</p>
|
||||||
|
<p className="ant-upload-text">
|
||||||
|
Click or drag files to this area to upload.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
</Upload.Dragger>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(BillFormComponent);
|
export default connect(mapStateToProps, mapDispatchToProps)(BillFormComponent);
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ export function getRange(dateParam, viewParam) {
|
|||||||
start = dayjs(date).startOf("week");
|
start = dayjs(date).startOf("week");
|
||||||
end = dayjs(date).endOf("week");
|
end = dayjs(date).endOf("week");
|
||||||
}
|
}
|
||||||
//if view is month: from dayjs(date).startOf('month').subtract(7, 'days') to dayjs(date).endOf('month').add(7, 'days'); i do additional 7 days math because you can see adjacent weeks on month view (that is the way how i generate my recurrent events for the Big Calendar, but if you need only start-end of month - just remove that math);
|
//if view is month: from dayjs(date).startOf('month').subtract(7, 'day') to dayjs(date).endOf('month').add(7, 'day'); i do additional 7 days math because you can see adjacent weeks on month view (that is the way how i generate my recurrent events for the Big Calendar, but if you need only start-end of month - just remove that math);
|
||||||
else if (view === "month") {
|
else if (view === "month") {
|
||||||
start = dayjs(date).startOf("month").subtract(7, "days");
|
start = dayjs(date).startOf("month").subtract(7, "day");
|
||||||
end = dayjs(date).endOf("month").add(7, "days");
|
end = dayjs(date).endOf("month").add(7, "day");
|
||||||
}
|
}
|
||||||
// if view is agenda: from dayjs(date).startOf('day') to dayjs(date).endOf('day').add(1, 'month');
|
// if view is agenda: from dayjs(date).startOf('day') to dayjs(date).endOf('day').add(1, 'month');
|
||||||
else if (view === "agenda") {
|
else if (view === "agenda") {
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ export function ScheduleJobModalComponent({
|
|||||||
{() => {
|
{() => {
|
||||||
const values = form.getFieldsValue();
|
const values = form.getFieldsValue();
|
||||||
if (values.start) {
|
if (values.start) {
|
||||||
calculateScheduleLoad(dayjs(values.start).add(3, "days"));
|
calculateScheduleLoad(dayjs(values.start).add(3, "day"));
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="schedule-job-modal">
|
<div className="schedule-job-modal">
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function ScheduleVerifyIntegrity({ currentUser }) {
|
|||||||
data: { arrJobs, compJobs, prodJobs },
|
data: { arrJobs, compJobs, prodJobs },
|
||||||
} = await client.query({
|
} = await client.query({
|
||||||
query: QUERY_SCHEDULE_LOAD_DATA,
|
query: QUERY_SCHEDULE_LOAD_DATA,
|
||||||
variables: { start: dayjs(), end: dayjs().add(180, "days") },
|
variables: { start: dayjs(), end: dayjs().add(180, "day") },
|
||||||
});
|
});
|
||||||
|
|
||||||
//check that the leaving jobs are either in the arriving list, or in production.
|
//check that the leaving jobs are either in the arriving list, or in production.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
const rowGutter = [16, 16];
|
const rowGutter = [16, 16];
|
||||||
const statSpans = { xs: 24, sm: 3 };
|
const statSpans = { xs: 24, sm: 3 };
|
||||||
|
|
||||||
export function ScoreboardTimeticketsTargetsTable({ bodyshop }) {
|
export function ScoreboardTimeTicketsTargetsTable({ bodyshop }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -282,4 +282,4 @@ export function ScoreboardTimeticketsTargetsTable({ bodyshop }) {
|
|||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(ScoreboardTimeticketsTargetsTable);
|
)(ScoreboardTimeTicketsTargetsTable);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default function ScoreboardTimeTickets() {
|
|||||||
const { start, end } = searchParams;
|
const { start, end } = searchParams;
|
||||||
const startDate = start
|
const startDate = start
|
||||||
? dayjs(start)
|
? dayjs(start)
|
||||||
: dayjs().startOf("week").subtract(7, "days");
|
: dayjs().startOf("week").subtract(7, "day");
|
||||||
const endDate = end ? dayjs(end) : dayjs().endOf("week");
|
const endDate = end ? dayjs(end) : dayjs().endOf("week");
|
||||||
|
|
||||||
const fixedPeriods = useMemo(() => {
|
const fixedPeriods = useMemo(() => {
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
|
|
||||||
export function TechClockInComponent({ form, bodyshop, technician }) {
|
export function TechClockInComponent({ form, bodyshop, technician }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const emps = bodyshop.employees.filter((e) => e.id === technician.id)[0];
|
const emps = bodyshop.employees.filter((e) => e.id === technician.id)[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<LayoutFormRow grow noDivider>
|
<LayoutFormRow grow noDivider>
|
||||||
|
|||||||
@@ -46,6 +46,16 @@ export function TechClockInContainer({
|
|||||||
(e) => e.id === (technician && technician.id)
|
(e) => e.id === (technician && technician.id)
|
||||||
)[0];
|
)[0];
|
||||||
|
|
||||||
|
const TechForm = () => {
|
||||||
|
if (technician) {
|
||||||
|
return <Form form={form} layout="vertical" onFinish={handleFinish} >
|
||||||
|
<TechClockInComponent form={form} />
|
||||||
|
</Form>
|
||||||
|
} else {
|
||||||
|
return <div />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleFinish = async (values) => {
|
const handleFinish = async (values) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const theTime = (await axios.post("/utils/time")).data;
|
const theTime = (await axios.post("/utils/time")).data;
|
||||||
@@ -146,9 +156,7 @@ export function TechClockInContainer({
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Form form={form} layout="vertical" onFinish={handleFinish}>
|
<TechForm />
|
||||||
<TechClockInComponent form={form} />
|
|
||||||
</Form>
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +1,101 @@
|
|||||||
import { Card, List, Typography } from "antd";
|
import {Card, List, Typography} from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useQuery } from "@apollo/client";
|
import {useQuery} from "@apollo/client";
|
||||||
import { useTranslation } from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import {connect} from "react-redux";
|
||||||
import { Link } from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import {createStructuredSelector} from "reselect";
|
||||||
import { QUERY_ACTIVE_TIME_TICKETS } from "../../graphql/timetickets.queries";
|
import {QUERY_ACTIVE_TIME_TICKETS} from "../../graphql/timetickets.queries";
|
||||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
import {selectTechnician} from "../../redux/tech/tech.selectors";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import {DateTimeFormatter} from "../../utils/DateFormatter";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import DataLabel from "../data-label/data-label.component";
|
import DataLabel from "../data-label/data-label.component";
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
import TechClockOffButton from "../tech-job-clock-out-button/tech-job-clock-out-button.component";
|
import TechClockOffButton from "../tech-job-clock-out-button/tech-job-clock-out-button.component";
|
||||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
import {OwnerNameDisplayFunction} from "../owner-name-display/owner-name-display.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
technician: selectTechnician,
|
technician: selectTechnician,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
|
|
||||||
export function TechClockedInList({ technician }) {
|
export function TechClockedInList({technician}) {
|
||||||
const { loading, error, data, refetch } = useQuery(
|
const {loading, error, data, refetch} = useQuery(
|
||||||
QUERY_ACTIVE_TIME_TICKETS,
|
QUERY_ACTIVE_TIME_TICKETS,
|
||||||
{
|
{
|
||||||
variables: {
|
variables: {
|
||||||
employeeId: technician.id,
|
employeeId: technician?.id,
|
||||||
},
|
},
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
}
|
skip: !technician,
|
||||||
);
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const {t} = useTranslation();
|
||||||
|
if (loading) return <LoadingSpinner/>;
|
||||||
|
if (error) return <AlertComponent message={error.message} type="error"/>;
|
||||||
|
|
||||||
if (loading) return <LoadingSpinner />;
|
return (
|
||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{data.timetickets.length > 0 ? (
|
|
||||||
<div>
|
<div>
|
||||||
<Typography.Title level={2}>
|
{data?.timetickets?.length > 0 ? (
|
||||||
{t("timetickets.labels.alreadyclockedon")}
|
<div>
|
||||||
</Typography.Title>
|
<Typography.Title level={2}>
|
||||||
<List
|
{t("timetickets.labels.alreadyclockedon")}
|
||||||
grid={{
|
</Typography.Title>
|
||||||
gutter: 32,
|
<List
|
||||||
xs: 1,
|
grid={{
|
||||||
sm: 2,
|
gutter: 32,
|
||||||
md: 3,
|
xs: 1,
|
||||||
lg: 4,
|
sm: 2,
|
||||||
xl: 5,
|
md: 3,
|
||||||
xxl: 6,
|
lg: 4,
|
||||||
}}
|
xl: 5,
|
||||||
dataSource={data.timetickets || []}
|
xxl: 6,
|
||||||
renderItem={(ticket) => (
|
}}
|
||||||
<List.Item>
|
dataSource={data.timetickets || []}
|
||||||
<Card
|
renderItem={(ticket) => (
|
||||||
title={
|
<List.Item>
|
||||||
<Link to={`/tech/joblookup?selected=${ticket.job.id}`}>
|
<Card
|
||||||
{`${
|
title={
|
||||||
ticket.job.ro_number || t("general.labels.na")
|
<Link to={`/tech/joblookup?selected=${ticket.job.id}`}>
|
||||||
} ${OwnerNameDisplayFunction(ticket.job)}`}
|
{`${
|
||||||
</Link>
|
ticket.job.ro_number || t("general.labels.na")
|
||||||
}
|
} ${OwnerNameDisplayFunction(ticket.job)}`}
|
||||||
actions={[
|
</Link>
|
||||||
<TechClockOffButton
|
}
|
||||||
jobId={ticket.jobid}
|
actions={[
|
||||||
timeTicketId={ticket.id}
|
<TechClockOffButton
|
||||||
completedCallback={refetch}
|
jobId={ticket.jobid}
|
||||||
/>,
|
timeTicketId={ticket.id}
|
||||||
]}
|
completedCallback={refetch}
|
||||||
>
|
/>,
|
||||||
<div>
|
]}
|
||||||
{`
|
>
|
||||||
|
<div>
|
||||||
|
{`
|
||||||
${ticket.job.v_model_yr || ""} ${
|
${ticket.job.v_model_yr || ""} ${
|
||||||
ticket.job.v_make_desc || ""
|
ticket.job.v_make_desc || ""
|
||||||
} ${ticket.job.v_model_desc || ""}`}
|
} ${ticket.job.v_model_desc || ""}`}
|
||||||
</div>
|
</div>
|
||||||
<DataLabel label={t("timetickets.fields.clockon")}>
|
<DataLabel label={t("timetickets.fields.clockon")}>
|
||||||
<DateTimeFormatter>{ticket.clockon}</DateTimeFormatter>
|
<DateTimeFormatter>{ticket.clockon}</DateTimeFormatter>
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
<DataLabel label={t("timetickets.fields.cost_center")}>
|
<DataLabel label={t("timetickets.fields.cost_center")}>
|
||||||
{ticket.cost_center === "timetickets.labels.shift"
|
{ticket.cost_center === "timetickets.labels.shift"
|
||||||
? t(ticket.cost_center)
|
? t(ticket.cost_center)
|
||||||
: ticket.cost_center}
|
: ticket.cost_center}
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
</Card>
|
</Card>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
)}
|
)}
|
||||||
></List>
|
></List>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
);
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(TechClockedInList);
|
export default connect(mapStateToProps, mapDispatchToProps)(TechClockedInList);
|
||||||
|
|||||||
@@ -30,8 +30,9 @@ const TechJobStatistics = ({ technician }) => {
|
|||||||
end: endDate.format("YYYY-MM-DD"),
|
end: endDate.format("YYYY-MM-DD"),
|
||||||
fixedStart: dayjs().startOf("month").format("YYYY-MM-DD"),
|
fixedStart: dayjs().startOf("month").format("YYYY-MM-DD"),
|
||||||
fixedEnd: dayjs().endOf("month").format("YYYY-MM-DD"),
|
fixedEnd: dayjs().endOf("month").format("YYYY-MM-DD"),
|
||||||
employeeid: technician.id,
|
employeeid: technician?.id,
|
||||||
},
|
},
|
||||||
|
skip: !technician,
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ export function TechLogin({
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleFinish = (values) => {
|
const handleFinish = (values) => {
|
||||||
techLoginStart(values);
|
// Remap these because EmployeeID form name has previously been used in the project
|
||||||
|
techLoginStart({pin: values.pin, employeeid: values.techEmployeeId});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -49,7 +50,8 @@ export function TechLogin({
|
|||||||
>
|
>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("tech.fields.employeeid")}
|
label={t("tech.fields.employeeid")}
|
||||||
name="employeeid"
|
name="techEmployeeId"
|
||||||
|
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default function TimeTicketsDatesSelector() {
|
|||||||
return (
|
return (
|
||||||
<DatePicker.RangePicker
|
<DatePicker.RangePicker
|
||||||
defaultValue={[
|
defaultValue={[
|
||||||
start ? dayjs(start) : dayjs().startOf("week").subtract(7, "days"),
|
start ? dayjs(start) : dayjs().startOf("week").subtract(7, "day"),
|
||||||
end ? dayjs(end) : dayjs().endOf("week"),
|
end ? dayjs(end) : dayjs().endOf("week"),
|
||||||
]}
|
]}
|
||||||
format="MM/DD/YYYY"
|
format="MM/DD/YYYY"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default function TimeTicketsAttendanceTable() {
|
|||||||
variables: {
|
variables: {
|
||||||
start: start
|
start: start
|
||||||
? start
|
? start
|
||||||
: dayjs().startOf("week").subtract(7, "days").format("YYYY-MM-DD"),
|
: dayjs().startOf("week").subtract(7, "day").format("YYYY-MM-DD"),
|
||||||
end: end ? end : dayjs().endOf("week").format("YYYY-MM-DD"),
|
end: end ? end : dayjs().endOf("week").format("YYYY-MM-DD"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default function TimeTicketsPayrollTable() {
|
|||||||
variables: {
|
variables: {
|
||||||
start: start
|
start: start
|
||||||
? start
|
? start
|
||||||
: dayjs().startOf("week").subtract(7, "days").format("YYYY-MM-DD"),
|
: dayjs().startOf("week").subtract(7, "day").format("YYYY-MM-DD"),
|
||||||
end: end ? end : dayjs().endOf("week").format("YYYY-MM-DD"),
|
end: end ? end : dayjs().endOf("week").format("YYYY-MM-DD"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,20 +18,36 @@ import {
|
|||||||
} from "../../redux/application/application.actions";
|
} from "../../redux/application/application.actions";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping state to props
|
||||||
|
*/
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping dispatch to props
|
||||||
|
*/
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ScoreboardContainer component
|
||||||
|
* @param {Object} props - The props that were defined by the caller of this component.
|
||||||
|
* @param {Function} props.setBreadcrumbs - Function to set breadcrumbs.
|
||||||
|
* @param {Function} props.setSelectedHeader - Function to set selected header.
|
||||||
|
*/
|
||||||
export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
|
export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const searchParams = queryString.parse(useLocation().search);
|
const searchParams = queryString.parse(useLocation().search);
|
||||||
const { tab } = searchParams;
|
const { tab } = searchParams;
|
||||||
const history = useNavigate();
|
const history = useNavigate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* useEffect hook to set document title, selected header and breadcrumbs
|
||||||
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = t("titles.scoreboard");
|
document.title = t("titles.scoreboard");
|
||||||
setSelectedHeader("scoreboard");
|
setSelectedHeader("scoreboard");
|
||||||
@@ -43,6 +59,9 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
|
|||||||
]);
|
]);
|
||||||
}, [t, setBreadcrumbs, setSelectedHeader]);
|
}, [t, setBreadcrumbs, setSelectedHeader]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the component
|
||||||
|
*/
|
||||||
return (
|
return (
|
||||||
<FeatureWrapper featureName="scoreboard">
|
<FeatureWrapper featureName="scoreboard">
|
||||||
<RbacWrapper action="scoreboard:view">
|
<RbacWrapper action="scoreboard:view">
|
||||||
@@ -97,7 +116,11 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
|
|||||||
</FeatureWrapper>
|
</FeatureWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connecting the component to Redux store
|
||||||
|
*/
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(ScoreboardContainer);
|
)(ScoreboardContainer);
|
||||||
@@ -37,7 +37,7 @@ export function TimeTicketsContainer({
|
|||||||
|
|
||||||
const startDate = start
|
const startDate = start
|
||||||
? dayjs(start)
|
? dayjs(start)
|
||||||
: dayjs().startOf("week").subtract(7, "days");
|
: dayjs().startOf("week").subtract(7, "day");
|
||||||
const endDate = end ? dayjs(end) : dayjs().endOf("week");
|
const endDate = end ? dayjs(end) : dayjs().endOf("week");
|
||||||
|
|
||||||
const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE, {
|
const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE, {
|
||||||
|
|||||||
@@ -209,9 +209,9 @@ export function* calculateScheduleLoad({ payload: end }) {
|
|||||||
|
|
||||||
const range = Math.round(dayjs.duration(end.diff(today)).asDays()) + 1;
|
const range = Math.round(dayjs.duration(end.diff(today)).asDays()) + 1;
|
||||||
for (var day = 0; day < range; day++) {
|
for (var day = 0; day < range; day++) {
|
||||||
const current = dayjs(today).add(day, "days").format("yyyy-MM-DD");
|
const current = dayjs(today).add(day, "day").format("yyyy-MM-DD");
|
||||||
const prev = dayjs(today)
|
const prev = dayjs(today)
|
||||||
.add(day - 1, "days")
|
.add(day - 1, "day")
|
||||||
.format("yyyy-MM-DD");
|
.format("yyyy-MM-DD");
|
||||||
if (!!!load[current]) {
|
if (!!!load[current]) {
|
||||||
load[current] = {};
|
load[current] = {};
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import dayjs from "./day";
|
import dayjs from "./day";
|
||||||
const range = {
|
const range = {
|
||||||
Today: [dayjs(), dayjs()],
|
Today: [dayjs(), dayjs()],
|
||||||
"Last 14 days": [dayjs().subtract(14, "days"), dayjs()],
|
"Last 14 days": [dayjs().subtract(14, "day"), dayjs()],
|
||||||
"Last 7 days": [dayjs().subtract(7, "days"), dayjs()],
|
"Last 7 days": [dayjs().subtract(7, "day"), dayjs()],
|
||||||
"Next 7 days": [dayjs(), dayjs().add(7, "days")],
|
"Next 7 days": [dayjs(), dayjs().add(7, "day")],
|
||||||
"Next 14 days": [dayjs(), dayjs().add(14, "days")],
|
"Next 14 days": [dayjs(), dayjs().add(14, "day")],
|
||||||
"Last Month": [
|
"Last Month": [
|
||||||
dayjs().startOf("month").subtract(1, "month"),
|
dayjs().startOf("month").subtract(1, "month"),
|
||||||
dayjs().startOf("month").subtract(1, "month").endOf("month"),
|
dayjs().startOf("month").subtract(1, "month").endOf("month"),
|
||||||
@@ -15,13 +15,13 @@ const range = {
|
|||||||
dayjs().startOf("month").add(1, "month").endOf("month"),
|
dayjs().startOf("month").add(1, "month").endOf("month"),
|
||||||
],
|
],
|
||||||
"Last Quarter": [
|
"Last Quarter": [
|
||||||
dayjs().startOf("quarter").subtract(1, "quarters"),
|
dayjs().startOf("quarter").subtract(1, "quarter"),
|
||||||
dayjs().startOf("quarter").subtract(1, "day"),
|
dayjs().startOf("quarter").subtract(1, "day"),
|
||||||
],
|
],
|
||||||
"This Quarter": [
|
"This Quarter": [
|
||||||
dayjs().startOf("quarter"),
|
dayjs().startOf("quarter"),
|
||||||
dayjs().startOf("quarter").add(1, "quarter").subtract(1, "day"),
|
dayjs().startOf("quarter").add(1, "quarter").subtract(1, "day"),
|
||||||
],
|
],
|
||||||
"Last 90 Days": [dayjs().add(-90, "days"), dayjs()],
|
"Last 90 Days": [dayjs().add(-90, "day"), dayjs()],
|
||||||
};
|
};
|
||||||
export default range;
|
export default range;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import dayjsBusinessDays from "dayjs-business-days2";
|
import dayjsBusinessDays from "dayjs-business-days2";
|
||||||
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
|
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
|
||||||
import updateLocale from 'dayjs/plugin/updateLocale';
|
import updateLocale from 'dayjs/plugin/updateLocale';
|
||||||
@@ -30,6 +31,7 @@ import isYesterday from 'dayjs/plugin/isYesterday';
|
|||||||
import objectSupport from 'dayjs/plugin/objectSupport';
|
import objectSupport from 'dayjs/plugin/objectSupport';
|
||||||
import toArray from 'dayjs/plugin/toArray';
|
import toArray from 'dayjs/plugin/toArray';
|
||||||
import toObject from 'dayjs/plugin/toObject';
|
import toObject from 'dayjs/plugin/toObject';
|
||||||
|
|
||||||
// import badMutable from 'dayjs/plugin/badMutable';
|
// import badMutable from 'dayjs/plugin/badMutable';
|
||||||
// import preParsePostFormat from 'dayjs/plugin/preParsePostFormat';
|
// import preParsePostFormat from 'dayjs/plugin/preParsePostFormat';
|
||||||
|
|
||||||
@@ -42,7 +44,6 @@ dayjs.extend(isYesterday);
|
|||||||
dayjs.extend(isTomorrow);
|
dayjs.extend(isTomorrow);
|
||||||
dayjs.extend(isToday);
|
dayjs.extend(isToday);
|
||||||
dayjs.extend(localeData);
|
dayjs.extend(localeData);
|
||||||
dayjs.extend(relativeTime);
|
|
||||||
dayjs.extend(quarterOfYear);
|
dayjs.extend(quarterOfYear);
|
||||||
dayjs.extend(localizedFormat);
|
dayjs.extend(localizedFormat);
|
||||||
// dayjs.extend(preParsePostFormat); // TODO: This should not be needed
|
// dayjs.extend(preParsePostFormat); // TODO: This should not be needed
|
||||||
@@ -57,6 +58,7 @@ dayjs.extend(calendar);
|
|||||||
dayjs.extend(arraySupport);
|
dayjs.extend(arraySupport);
|
||||||
dayjs.extend(advancedFormat);
|
dayjs.extend(advancedFormat);
|
||||||
dayjs.extend(duration);
|
dayjs.extend(duration);
|
||||||
|
dayjs.extend(relativeTime);
|
||||||
dayjs.extend(pluralGetSet);
|
dayjs.extend(pluralGetSet);
|
||||||
dayjs.extend(customParseFormat);
|
dayjs.extend(customParseFormat);
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
|
|||||||
Reference in New Issue
Block a user