377 lines
13 KiB
JavaScript
377 lines
13 KiB
JavaScript
import {
|
|
Alert,
|
|
Button,
|
|
Checkbox,
|
|
Col,
|
|
Form,
|
|
Input,
|
|
InputNumber,
|
|
Row,
|
|
Space,
|
|
Table,
|
|
} 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 EmployeeSearchSelectComponent from "../employee-search-select/employee-search-select.component";
|
|
import EmployeeTeamSearchSelectComponent from "../employee-team-search-select/employee-team-search-select.component";
|
|
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
|
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
|
import { LaborAllocationContainer } from "../time-ticket-modal/time-ticket-modal.component";
|
|
import TimeTicketsTasksPresets from "../time-ticket-tasks-presets/time-ticket-tasks-presets.component";
|
|
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
|
import _ from "lodash";
|
|
|
|
const mapStateToProps = createStructuredSelector({
|
|
//currentUser: selectCurrentUser
|
|
bodyshop: selectBodyshop,
|
|
});
|
|
const mapDispatchToProps = (dispatch) => ({
|
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
});
|
|
export default connect(
|
|
mapStateToProps,
|
|
mapDispatchToProps
|
|
)(TimeTicketTaskModalComponent);
|
|
|
|
export function TimeTicketTaskModalComponent({
|
|
bodyshop,
|
|
form,
|
|
employeeAutoCompleteOptions,
|
|
lineTicketCalled,
|
|
calculateTimeTickets,
|
|
lineTicketLoading,
|
|
lineTicketData,
|
|
queryJobInfo,
|
|
}) {
|
|
const { t } = useTranslation();
|
|
|
|
return (
|
|
<div>
|
|
<TimeTicketsTasksPresets
|
|
form={form}
|
|
calculateTimeTickets={calculateTimeTickets}
|
|
/>
|
|
<Row gutter={[16, 16]}>
|
|
<Col lg={12} md={24}>
|
|
<Form.Item
|
|
name="jobid"
|
|
label={t("timetickets.fields.ro_number")}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
//message: t("general.validation.required"),
|
|
},
|
|
]}
|
|
>
|
|
<JobSearchSelectComponent
|
|
convertedOnly={!bodyshop.tt_allow_post_to_invoiced}
|
|
notExported={!bodyshop.tt_allow_post_to_invoiced}
|
|
/>
|
|
</Form.Item>
|
|
|
|
<Form.Item name="employeeid" label={t("timetickets.fields.employee")}>
|
|
<EmployeeSearchSelectComponent
|
|
options={employeeAutoCompleteOptions}
|
|
allowClear
|
|
onSelect={(value) => {
|
|
const emps =
|
|
employeeAutoCompleteOptions &&
|
|
employeeAutoCompleteOptions.filter((e) => e.id === value)[0];
|
|
form.setFieldsValue({ flat_rate: emps && emps.flat_rate });
|
|
}}
|
|
/>
|
|
</Form.Item>
|
|
<Form.Item
|
|
name="employeeteamid"
|
|
label={t("timetickets.fields.employee_team")}
|
|
>
|
|
<EmployeeTeamSearchSelectComponent />
|
|
</Form.Item>
|
|
<Space wrap>
|
|
<Form.Item
|
|
name="hourstype"
|
|
rules={[
|
|
{
|
|
required: true,
|
|
//message: t("general.validation.required"),
|
|
},
|
|
]}
|
|
>
|
|
<Checkbox.Group>
|
|
<Space wrap>
|
|
<Checkbox value="LAB" style={{ display: "flex" }}>
|
|
Body
|
|
</Checkbox>
|
|
<Checkbox value="LAR" style={{ display: "flex" }}>
|
|
Refinish
|
|
</Checkbox>
|
|
<Checkbox value="LAM" style={{ display: "flex" }}>
|
|
Mechanical
|
|
</Checkbox>
|
|
<Checkbox value="LAF" style={{ display: "flex" }}>
|
|
Frame
|
|
</Checkbox>
|
|
<Checkbox value="LAG" style={{ display: "flex" }}>
|
|
Glass
|
|
</Checkbox>
|
|
</Space>
|
|
</Checkbox.Group>
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
name="percent"
|
|
rules={[
|
|
{
|
|
required: true,
|
|
//message: t("general.validation.required"),
|
|
},
|
|
]}
|
|
>
|
|
<InputNumber min={0} max={100} precision={1} addonAfter="%" />
|
|
</Form.Item>
|
|
</Space>
|
|
<Button onClick={calculateTimeTickets}>Calculate</Button>
|
|
</Col>
|
|
<Col lg={12} md={24}>
|
|
<Form.Item shouldUpdate>
|
|
{() => {
|
|
const data = form.getFieldValue("timetickets");
|
|
return (
|
|
<Table
|
|
dataSource={data}
|
|
rowKey={"employeeid"}
|
|
columns={[
|
|
{
|
|
title: t("timetickets.fields.employee"),
|
|
dataIndex: "employee",
|
|
key: "employee",
|
|
render: (text, record) => {
|
|
const emp = bodyshop.employees.find(
|
|
(e) => e.id === record.employeeid
|
|
);
|
|
return `${emp?.first_name} ${emp?.last_name}`;
|
|
},
|
|
},
|
|
{
|
|
title: t("timetickets.fields.cost_center"),
|
|
dataIndex: "cost_center",
|
|
key: "cost_center",
|
|
|
|
render: (text, record) =>
|
|
record.cost_center === "timetickets.labels.shift"
|
|
? t(record.cost_center)
|
|
: record.cost_center,
|
|
},
|
|
{
|
|
title: t("timetickets.fields.productivehrs"),
|
|
dataIndex: "productivehrs",
|
|
key: "productivehrs",
|
|
},
|
|
{
|
|
title: "Percentage",
|
|
dataIndex: "percentage",
|
|
key: "percentage",
|
|
},
|
|
{
|
|
title: "Rate",
|
|
dataIndex: "rate",
|
|
key: "rate",
|
|
},
|
|
{
|
|
title: "Pay",
|
|
dataIndex: "pay",
|
|
key: "pay",
|
|
},
|
|
]}
|
|
/>
|
|
);
|
|
}}
|
|
</Form.Item>
|
|
|
|
<Form.List
|
|
name={["timetickets"]}
|
|
rules={[
|
|
{
|
|
validator: (rule, value) => {
|
|
//Check the cost center,
|
|
const totals = CalculateAllocationsTotals(
|
|
bodyshop,
|
|
lineTicketData.joblines,
|
|
lineTicketData.timetickets,
|
|
lineTicketData.jobs_by_pk.lbr_adjustments
|
|
);
|
|
|
|
const grouped = _.groupBy(value, "cost_center");
|
|
let error = false;
|
|
Object.keys(grouped).forEach((key) => {
|
|
const totalProdTicketHours = grouped[key].reduce(
|
|
(acc, val) => acc + val.productivehrs,
|
|
0
|
|
);
|
|
|
|
const fieldTypeToCheck = "cost_center";
|
|
// bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
|
// ? "mod_lbr_ty"
|
|
// : "cost_center";
|
|
|
|
const costCenterDiff =
|
|
Math.round(
|
|
totals.find((total) => total[fieldTypeToCheck] === key)
|
|
?.difference * 10
|
|
) / 10;
|
|
|
|
if (totalProdTicketHours > costCenterDiff) error = true;
|
|
else {
|
|
// return Promise.resolve();
|
|
}
|
|
});
|
|
|
|
if (!error) return Promise.resolve();
|
|
return Promise.reject(
|
|
"Too many hours are being claimed as a part of this task"
|
|
);
|
|
},
|
|
},
|
|
]}
|
|
>
|
|
{(fields, { add, remove, move }, { errors }) => {
|
|
return (
|
|
<div>
|
|
{errors.map((e, idx) => (
|
|
<Alert key={idx} message={e} />
|
|
))}
|
|
<div style={{ display: "none" }}>
|
|
{fields.map((field, index) => (
|
|
<Form.Item
|
|
key={field.key}
|
|
style={{ padding: 0, margin: 2 }}
|
|
>
|
|
<Space wrap>
|
|
<Form.Item
|
|
label={t("timetickets.fields.employeeid")}
|
|
key={`${index}employeeid`}
|
|
name={[field.name, "employeeid"]}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
//message: t("general.validation.required"),
|
|
},
|
|
]}
|
|
>
|
|
<EmployeeSearchSelectComponent
|
|
options={bodyshop.employees}
|
|
/>
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t("timetickets.fields.date")}
|
|
key={`${index}date`}
|
|
name={[field.name, "date"]}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
//message: t("general.validation.required"),
|
|
},
|
|
]}
|
|
>
|
|
<FormDateTimePickerComponent />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t("timetickets.fields.productivehrs")}
|
|
key={`${index}productivehrs`}
|
|
name={[field.name, "productivehrs"]}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
//message: t("general.validation.required"),
|
|
},
|
|
]}
|
|
>
|
|
<InputNumber min={0} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t("timetickets.fields.actualhrs")}
|
|
key={`${index}actualhrs`}
|
|
name={[field.name, "actualhrs"]}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
//message: t("general.validation.required"),
|
|
},
|
|
]}
|
|
>
|
|
<InputNumber min={0} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t("timetickets.fields.rate")}
|
|
key={`${index}rate`}
|
|
name={[field.name, "rate"]}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
//message: t("general.validation.required"),
|
|
},
|
|
]}
|
|
>
|
|
<InputNumber />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t("timetickets.fields.cost_center")}
|
|
key={`${index}cost_center`}
|
|
name={[field.name, "cost_center"]}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
//message: t("general.validation.required"),
|
|
},
|
|
]}
|
|
>
|
|
<Input />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t("timetickets.fields.memo")}
|
|
key={`${index}memo`}
|
|
name={[field.name, "memo"]}
|
|
>
|
|
<Input />
|
|
</Form.Item>
|
|
</Space>
|
|
</Form.Item>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}}
|
|
</Form.List>
|
|
</Col>
|
|
</Row>
|
|
<Form.Item dependencies={["jobid"]}>
|
|
{() => {
|
|
const jobid = form.getFieldValue("jobid");
|
|
|
|
if (
|
|
(!lineTicketCalled && jobid) ||
|
|
(jobid &&
|
|
lineTicketData?.jobs_by_pk?.id !== jobid &&
|
|
!lineTicketLoading)
|
|
) {
|
|
queryJobInfo({ variables: { id: jobid } }).then(() =>
|
|
calculateTimeTickets()
|
|
);
|
|
}
|
|
return (
|
|
<LaborAllocationContainer
|
|
jobid={jobid || null}
|
|
loading={lineTicketLoading}
|
|
lineTicketData={lineTicketData}
|
|
hideTimeTickets
|
|
/>
|
|
);
|
|
}}
|
|
</Form.Item>
|
|
</div>
|
|
);
|
|
}
|