IO-2206 Refine claim tasks modal to add validations and insertions.

This commit is contained in:
Patrick Fic
2023-04-18 13:25:42 -07:00
parent 8465e7539f
commit 3768164f1f
10 changed files with 613 additions and 308 deletions

View File

@@ -256,7 +256,7 @@ export function JobsDetailHeaderActions({
onClick={() => {
setTimeTicketTaskContext({
actions: {},
context: { jobId: job.id },
context: { jobid: job.id },
});
}}
>

View File

@@ -2,6 +2,7 @@ import { DeleteFilled } from "@ant-design/icons";
import { useMutation, useQuery } from "@apollo/client";
import {
Button,
Space,
Card,
Form,
Input,
@@ -383,16 +384,18 @@ export function ShopEmployeeTeamsFormComponent({ bodyshop }) {
>
<CurrencyInput />
</Form.Item>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
<Space align="center">
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</Space>
</LayoutFormRow>
</Form.Item>
))}

View File

@@ -1,3 +1,4 @@
import { useQuery } from "@apollo/client";
import {
Button,
Form,
@@ -9,17 +10,16 @@ import {
Table,
Typography,
} from "antd";
import Dinero from "dinero.js";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { GET_JOB_INFO_DRAW_CALCULATIONS } from "../../graphql/jobs-lines.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import Dinero from "dinero.js";
import { useQuery } from "@apollo/client";
import { GET_JOB_INFO_DRAW_CALCULATIONS } from "../../graphql/jobs-lines.queries";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
@@ -30,20 +30,20 @@ export default connect(
)(TimeTicketListTeamPay);
export function TimeTicketListTeamPay({ bodyshop, context, actions }) {
const { refetch } = actions;
//const { refetch } = actions;
const { jobId } = context;
const [visible, setVisible] = useState(false);
const [form] = Form.useForm();
const { t } = useTranslation();
const { loading, data: lineTicketData } = useQuery(
GET_JOB_INFO_DRAW_CALCULATIONS,
{
variables: { id: jobId },
skip: !jobId,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
}
);
const {
//loading,
data: lineTicketData,
} = useQuery(GET_JOB_INFO_DRAW_CALCULATIONS, {
variables: { id: jobId },
skip: !jobId,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const handleOk = () => {
setVisible(false);
@@ -124,7 +124,7 @@ export function TimeTicketListTeamPay({ bodyshop, context, actions }) {
<Form.Item shouldUpdate noStyle>
{({ getFieldsValue }) => {
const formData = getFieldsValue();
console.log("🚀 ~ file: time-ticket-list-team-pay.component.jsx:127 ~ TimeTicketListTeamPay ~ formData:", formData)
let data = [];
let eligibleHours = 0;
const theTeam = Teams.find((team) => team.name === formData.team);

View File

@@ -1,35 +1,37 @@
import { EditFilled } from "@ant-design/icons";
import { Card, Space, Table } from "antd";
import { Button, Card, Space, Table } from "antd";
import Dinero from "dinero.js";
import moment from "moment";
import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
import {
selectAuthLevel,
selectBodyshop,
} from "../../redux/user/user.selectors";
import { onlyUnique } from "../../utils/arrayHelper";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { onlyUnique } from "../../utils/arrayHelper";
import { alphaSort, dateSort } from "../../utils/sorters";
import RbacWrapper, {
HasRbacAccess,
} from "../rbac-wrapper/rbac-wrapper.component";
import Dinero from "dinero.js";
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
import TimeTicketListTeamPay from "./time-ticket-list-team-pay.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
authLevel: selectAuthLevel,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
setTimeTicketTaskContext: (context) =>
dispatch(setModalContext({ context: context, modal: "timeTicketTask" })),
});
export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketList);
export function TimeTicketList({
bodyshop,
setTimeTicketTaskContext,
authLevel,
disabled,
loading,
@@ -261,10 +263,23 @@ export function TimeTicketList({
title={t("timetickets.labels.timetickets")}
extra={
<Space wrap>
<TimeTicketListTeamPay
actions={{ refetch }}
context={{ jobId: jobId }}
/>
{
// <TimeTicketListTeamPay
// actions={{ refetch }}
// context={{ jobId: jobId }}
// />
}
<Button
onClick={() => {
setTimeTicketTaskContext();
setTimeTicketTaskContext({
actions: {},
context: { jobid: jobId },
});
}}
>
{t("timetickets.actions.claimtasks")}
</Button>
{jobId &&
(techConsole ? null : (
<TimeTicketEnterButton

View File

@@ -184,51 +184,51 @@ export function TimeTicketModalComponent({
name="productivehrs"
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (!bodyshop.tt_enforce_hours_for_tech_console) {
return Promise.resolve();
}
if (
!value ||
getFieldValue("cost_center") === null ||
!lineTicketData
)
return Promise.resolve();
validator(rule, value) {
if (!bodyshop.tt_enforce_hours_for_tech_console) {
return Promise.resolve();
}
if (
!value ||
getFieldValue("cost_center") === null ||
!lineTicketData
)
return Promise.resolve();
//Check the cost center,
const totals = CalculateAllocationsTotals(
bodyshop,
lineTicketData.joblines,
lineTicketData.timetickets,
lineTicketData.jobs_by_pk.lbr_adjustments
);
const fieldTypeToCheck =
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? "mod_lbr_ty"
: "cost_center";
const costCenterDiff =
Math.round(
totals.find(
(total) =>
total[fieldTypeToCheck] ===
getFieldValue("cost_center")
)?.difference * 10
) / 10;
if (value > costCenterDiff)
return Promise.reject(
t(
"timetickets.validation.hoursenteredmorethanavailable"
)
//Check the cost center,
const totals = CalculateAllocationsTotals(
bodyshop,
lineTicketData.joblines,
lineTicketData.timetickets,
lineTicketData.jobs_by_pk.lbr_adjustments
);
else {
return Promise.resolve();
}
},
}),
{
const fieldTypeToCheck =
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? "mod_lbr_ty"
: "cost_center";
const costCenterDiff =
Math.round(
totals.find(
(total) =>
total[fieldTypeToCheck] ===
getFieldValue("cost_center")
)?.difference * 10
) / 10;
if (value > costCenterDiff)
return Promise.reject(
t(
"timetickets.validation.hoursenteredmorethanavailable"
)
);
else {
return Promise.resolve();
}
},
}),
{
required:
form.getFieldValue("cost_center") !==
"timetickets.labels.shift",
@@ -371,7 +371,12 @@ export function TimeTicketModalComponent({
);
}
export function LaborAllocationContainer({ jobid, loading, lineTicketData }) {
export function LaborAllocationContainer({
jobid,
loading,
lineTicketData,
hideTimeTickets = false,
}) {
if (loading) return <LoadingSkeleton />;
if (!lineTicketData) return null;
return (
@@ -382,12 +387,13 @@ export function LaborAllocationContainer({ jobid, loading, lineTicketData }) {
timetickets={lineTicketData.timetickets}
adjustments={lineTicketData.jobs_by_pk.lbr_adjustments}
/>
<TimeTicketList
loading={loading}
timetickets={lineTicketData.timetickets}
techConsole
/>
{!hideTimeTickets && (
<TimeTicketList
loading={loading}
timetickets={lineTicketData.timetickets}
techConsole
/>
)}
</div>
);
}

View File

@@ -1,4 +1,15 @@
import { Button, Form, Input, InputNumber, Radio } from "antd";
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";
@@ -6,9 +17,12 @@ 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 FormDatePickerComponent from "../form-date-picker/form-date-picker.component";
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.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
@@ -26,17 +40,59 @@ export function TimeTicketTaskModalComponent({
bodyshop,
form,
employeeAutoCompleteOptions,
lineTicketCalled,
calculateTimeTickets,
lineTicketLoading,
lineTicketData,
queryJobInfo,
}) {
const { t } = useTranslation();
return (
<div>
<LayoutFormRow grow noDivider>
<Form.Item shouldUpdate>
{() => (
<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="jobid"
label={t("timetickets.fields.ro_number")}
name="hourstype"
rules={[
{
required: true,
@@ -44,180 +100,277 @@ export function TimeTicketTaskModalComponent({
},
]}
>
<JobSearchSelectComponent
convertedOnly={!bodyshop.tt_allow_post_to_invoiced}
notExported={!bodyshop.tt_allow_post_to_invoiced}
/>
<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>
<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
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.Item
name="employeeteamid"
label={t("timetickets.fields.employee_team")}
>
<EmployeeTeamSearchSelectComponent />
</Form.Item>
<Form.Item
name="hourstype"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Radio.Group>
<Radio value="LAB">Body</Radio>
<Radio value="LAR">Refinish</Radio>
<Radio value="LAM">Mechanical</Radio>
<Radio value="LAF">Frame</Radio>
<Radio value="LAG">Glass</Radio>
</Radio.Group>
</Form.Item>
</Form.Item>
<Form.Item
name="percent"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber min={0} max={100} precision={1} addonAfter="%" />
</Form.Item>
</LayoutFormRow>
<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
);
<Form.List name={["timetickets"]}>
{(fields, { add, remove, move }) => {
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 (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key} style={{ padding: 0, margin: 2 }}>
<LayoutFormRow grow>
<Form.Item
label={t("timetickets.fields.employeeid")}
key={`${index}`}
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}`}
name={[field.name, "date"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<FormDatePickerComponent />
</Form.Item>
<Form.Item
label={t("timetickets.fields.productivehrs")}
key={`${index}`}
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}`}
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}`}
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}`}
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}`}
name={[field.name, "memo"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
</LayoutFormRow>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "100%" }}
>
{t("employee_teams.actions.newmember")}
</Button>
</Form.Item>
</div>
<LaborAllocationContainer
jobid={jobid || null}
loading={lineTicketLoading}
lineTicketData={lineTicketData}
hideTimeTickets
/>
);
}}
</Form.List>
</Form.Item>
</div>
);
}

View File

@@ -1,16 +1,20 @@
import React from "react";
import React, { useEffect } from "react";
import { useLazyQuery, useMutation, useQuery } from "@apollo/client";
import { Form, Modal, notification } from "antd";
import Dinero from "dinero.js";
import _ from "lodash";
import moment from "moment";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectTimeTicketTasks } from "../../redux/modals/modals.selectors";
import { Modal, Form } from "antd";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { QUERY_ACTIVE_EMPLOYEES } from "../../graphql/employees.queries";
import { useLazyQuery, useQuery } from "@apollo/client";
import TimeTicketTaskModalComponent from "./time-ticket-task-modal.component";
import { GET_JOB_INFO_DRAW_CALCULATIONS } from "../../graphql/jobs-lines.queries";
import { INSERT_NEW_TIME_TICKET } from "../../graphql/timetickets.queries";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectTimeTicketTasks } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import Dinero from "dinero.js";
import TimeTicketTaskModalComponent from "./time-ticket-task-modal.component";
const mapStateToProps = createStructuredSelector({
timeTicketTasksModal: selectTimeTicketTasks,
@@ -36,30 +40,63 @@ export function TimeTickeTaskModalContainer({
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
//Query the Job Information and Prefill the Form.
const [queryJobInfo, { loading, data: lineTicketData }] = useLazyQuery(
GET_JOB_INFO_DRAW_CALCULATIONS,
{
const { t } = useTranslation();
const [insertTimeTickets] = useMutation(INSERT_NEW_TIME_TICKET);
const [queryJobInfo, { called, loading, data: lineTicketData }] =
useLazyQuery(GET_JOB_INFO_DRAW_CALCULATIONS, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
async function handleFinish(values) {
console.log(
"🚀 ~ file: time-ticket-task-modal.container.jsx:52 ~ handleFinish ~ values:",
values
);
try {
const result = await insertTimeTickets({
variables: {
timeTicketInput: values.timetickets.map((ticket) =>
_.omit(ticket, "pay")
),
},
});
if (result.errors) {
notification.open({
type: "error",
message: t("timetickets.errors.creating", {
message: JSON.stringify(result.errors),
}),
});
} else {
notification.open({
type: "success",
message: t("timetickets.successes.created"),
});
toggleModalVisible();
}
} catch (error) {
} finally {
}
);
}
async function handleFinish(values) {}
useEffect(() => {
if (context.jobid) {
console.log("UE Fired.");
queryJobInfo({ variables: { id: context.jobid } });
}
}, [context.jobid, queryJobInfo]);
const handleFieldsChange = async (changed, allFields) => {
const calculateTimeTickets = (presetMemo) => {
const formData = form.getFieldsValue();
if (changed[0].name[0] === "jobid") {
await queryJobInfo({ variables: { id: changed[0].value } });
}
if (
!formData.jobid ||
!formData.employeeteamid ||
!formData.hourstype ||
!formData.percent
formData.hourstype.length === 0 ||
!formData.percent ||
!lineTicketData
) {
console.log("Not everything populated.");
return;
}
let data = [];
@@ -68,56 +105,75 @@ export function TimeTickeTaskModalContainer({
const theTeam = JSON.parse(formData.employeeteamid);
if (theTeam) {
eligibleHours =
lineTicketData.joblines.reduce(
(acc, val) =>
acc + (formData.hourstype === val.mod_lbr_ty ? val.mod_lb_hrs : 0),
0
) * (formData.percent / 100 || 0);
data = [];
data = theTeam.employee_team_members.map((e) => {
return {
employeeid: e.id,
date: 0,
percentage: e.percentage,
rate: e.labor_rates[formData.hourstype],
cost_center:
bodyshop.md_responsibility_centers.defaults.costs[
formData.hourstype
],
productivehrs:
Math.round(eligibleHours * 100 * (e.percentage / 100)) / 100,
pay: Dinero({
amount: Math.round((e.labor_rates[formData.hourstype] || 0) * 100),
})
.multiply(
Math.round(eligibleHours * 100 * (e.percentage / 100)) / 100
)
.toFormat("$0.00"),
};
formData.hourstype.forEach((hourstype) => {
eligibleHours =
lineTicketData.joblines.reduce(
(acc, val) =>
acc + (hourstype === val.mod_lbr_ty ? val.mod_lb_hrs : 0),
0
) * (formData.percent / 100 || 0);
theTeam.employee_team_members.forEach((e) => {
const newTicket = {
employeeid: e.employeeid,
bodyshopid: bodyshop.id,
date: moment().format("YYYY-MM-DD"),
jobid: formData.jobid,
rate: e.labor_rates[hourstype],
actualhrs: 0,
memo: presetMemo,
flat_rate: true,
cost_center:
bodyshop.md_responsibility_centers.defaults.costs[hourstype],
productivehrs:
Math.round(eligibleHours * 100 * (e.percentage / 100)) / 100,
pay: Dinero({
amount: Math.round((e.labor_rates[hourstype] || 0) * 100),
})
.multiply(
Math.round(eligibleHours * 100 * (e.percentage / 100)) / 100
)
.toFormat("$0.00"),
};
data.push(newTicket);
});
});
form.setFieldsValue({ timetickets: data });
form.setFieldsValue({
timetickets: data.filter((d) => d.productivehrs > 0),
});
form.validateFields();
}
};
return (
<Modal
destroyOnClose
open={visible}
onCancel={() => toggleModalVisible()}
width="80%"
onOk={() => form.submit()}
>
<Form
autoComplete={"off"}
form={form}
layout="vertical"
onHandleFinish={handleFinish}
onFieldsChange={handleFieldsChange}
onFinish={handleFinish}
// onFieldsChange={handleFieldsChange}
initialValues={context}
>
<TimeTicketTaskModalComponent
form={form}
employeeAutoCompleteOptions={
EmployeeAutoCompleteData && EmployeeAutoCompleteData.employees
}
lineTicketData={lineTicketData}
lineTicketLoading={loading}
lineTicketCalled={called}
calculateTimeTickets={calculateTimeTickets}
queryJobInfo={queryJobInfo}
/>
</Form>
</Modal>

View File

@@ -0,0 +1,50 @@
import { Button, Dropdown } from "antd";
import React from "react";
export default function TimeTicketsTasksPresets({
form,
calculateTimeTickets,
}) {
const handleClick = (props) => {
const preset = samplePresets.find((p) => {
return p.name === props.key;
});
if (preset) {
form.setFieldsValue({
percent: preset.percent,
hourstype: preset.hourstype,
});
calculateTimeTickets(preset.memo);
}
};
return (
<Dropdown
trigger="click"
menu={{
items: samplePresets.map((p) => ({ label: p.name, key: p.name })),
onClick: handleClick,
}}
>
<Button>Presets</Button>
</Dropdown>
);
}
const samplePresets = [
{
name: "Teardown",
hourstype: ["LAB", "LAM"],
percent: 10,
memo: "Teardown Preset Task",
},
{
name: "Disassembly",
hourstype: ["LAB", "LAD"],
percent: 20,
memo: "Disassy Preset Claim",
},
{ name: "Body", hourstype: ["LAB", "LAD"], percent: 20 },
{ name: "Prep", hourstype: ["LAR"], percent: 20 },
];

View File

@@ -7,6 +7,7 @@ export const QUERY_TEAMS = gql`
name
employee_team_members {
id
employeeid
labor_rates
percentage
}

View File

@@ -95,6 +95,27 @@ export const GET_JOB_INFO_DRAW_CALCULATIONS = gql`
rate_laf
rate_lam
}
timetickets(where: { jobid: { _eq: $id } }) {
actualhrs
ciecacode
cost_center
date
id
jobid
employeeid
memo
flat_rate
clockon
clockoff
rate
employee {
id
first_name
last_name
employee_number
}
productivehrs
}
joblines(where: { jobid: { _eq: $id }, removed: { _eq: false } }) {
id
line_desc