Refactor task claiming and implement basic claim functionality.
This commit is contained in:
@@ -4318,6 +4318,48 @@
|
|||||||
<folder_node>
|
<folder_node>
|
||||||
<name>dms</name>
|
<name>dms</name>
|
||||||
<children>
|
<children>
|
||||||
|
<concept_node>
|
||||||
|
<name>apcontrol</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>appostingaccount</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>cashierid</name>
|
<name>cashierid</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -5734,6 +5776,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>nextstatus</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>percent</name>
|
<name>percent</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -46488,6 +46551,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>claimtaskpreview</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>clockhours</name>
|
<name>clockhours</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -46677,6 +46761,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>payrollclaimedtasks</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>pmbreak</name>
|
<name>pmbreak</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -46782,6 +46887,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>task</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>timetickets</name>
|
<name>timetickets</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
|
|||||||
@@ -1,9 +1,26 @@
|
|||||||
import Dinero from "dinero.js";
|
import Dinero from "dinero.js";
|
||||||
import React, { forwardRef } from "react";
|
import React, { forwardRef } from "react";
|
||||||
|
|
||||||
const ReadOnlyFormItem = ({ value, type = "text", onChange }, ref) => {
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
|
});
|
||||||
|
|
||||||
|
const ReadOnlyFormItem = (
|
||||||
|
{ bodyshop, value, type = "text", onChange },
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
if (!value) return null;
|
if (!value) return null;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case "employee":
|
||||||
|
const emp = bodyshop.employees.find((e) => e.id === value);
|
||||||
|
return `${emp?.first_name} ${emp?.last_name}`;
|
||||||
|
|
||||||
case "text":
|
case "text":
|
||||||
return <div>{value}</div>;
|
return <div>{value}</div>;
|
||||||
case "currency":
|
case "currency":
|
||||||
@@ -14,4 +31,8 @@ const ReadOnlyFormItem = ({ value, type = "text", onChange }, ref) => {
|
|||||||
return <div>{value}</div>;
|
return <div>{value}</div>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export default forwardRef(ReadOnlyFormItem);
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(forwardRef(ReadOnlyFormItem));
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { EditFilled } from "@ant-design/icons";
|
|
||||||
import { Button, Card, Col, Row, Space, Table, Typography } from "antd";
|
import { Button, Card, Col, Row, Space, Table, Typography } from "antd";
|
||||||
|
import axios from "axios";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -8,12 +8,7 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
|
||||||
import LaborAllocationsAdjustmentEdit from "../labor-allocations-adjustment-edit/labor-allocations-adjustment-edit.component";
|
|
||||||
import "./labor-allocations-table.styles.scss";
|
import "./labor-allocations-table.styles.scss";
|
||||||
import { CalculateAllocationsTotals } from "./labor-allocations-table.utility";
|
|
||||||
import axios from "axios";
|
|
||||||
import { onlyUnique } from "../../utils/arrayHelper";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -211,28 +206,33 @@ export function PayrollLaborAllocationsTable({
|
|||||||
return (
|
return (
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<Button
|
<Card
|
||||||
onClick={async () => {
|
title={t("jobs.labels.laborallocations")}
|
||||||
const { data } = await axios.post("/payroll/payall", {
|
extra={
|
||||||
jobid: jobId,
|
<Space>
|
||||||
});
|
<Button
|
||||||
refetch();
|
onClick={async () => {
|
||||||
}}
|
await axios.post("/payroll/payall", {
|
||||||
|
jobid: jobId,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
(TODO: Add Label) Pay All
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
const { data } = await axios.post("/payroll/calculatelabor", {
|
||||||
|
jobid: jobId,
|
||||||
|
});
|
||||||
|
setTotals(data);
|
||||||
|
refetch();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
(TODO: Add Label) Calculate Labor
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Pay All Test
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={async () => {
|
|
||||||
const { data } = await axios.post("/payroll/calculatelabor", {
|
|
||||||
jobid: jobId,
|
|
||||||
});
|
|
||||||
setTotals(data);
|
|
||||||
refetch();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Calculate Labor
|
|
||||||
</Button>
|
|
||||||
<Card title={t("jobs.labels.laborallocations")}>
|
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey={(record) => `${record.cost_center} ${record.mod_lbr_ty}`}
|
rowKey={(record) => `${record.cost_center} ${record.mod_lbr_ty}`}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
Row,
|
Row,
|
||||||
|
Select,
|
||||||
Space,
|
Space,
|
||||||
Switch,
|
Switch,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
@@ -15,7 +16,21 @@ import { useTranslation } from "react-i18next";
|
|||||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
|
||||||
export default function ShopInfoTaskPresets({ form }) {
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ShopInfoTaskPresets);
|
||||||
|
|
||||||
|
export function ShopInfoTaskPresets({ bodyshop, form }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -59,6 +74,7 @@ export default function ShopInfoTaskPresets({ form }) {
|
|||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
span={12}
|
||||||
label={t("bodyshop.fields.md_tasks_presets.hourstype")}
|
label={t("bodyshop.fields.md_tasks_presets.hourstype")}
|
||||||
key={`${index}hourstype`}
|
key={`${index}hourstype`}
|
||||||
name={[field.name, "hourstype"]}
|
name={[field.name, "hourstype"]}
|
||||||
@@ -71,7 +87,15 @@ export default function ShopInfoTaskPresets({ form }) {
|
|||||||
>
|
>
|
||||||
<Checkbox.Group>
|
<Checkbox.Group>
|
||||||
<Row>
|
<Row>
|
||||||
<Col span={8}>
|
<Col span={4}>
|
||||||
|
<Checkbox
|
||||||
|
value="LAA"
|
||||||
|
style={{ lineHeight: "32px" }}
|
||||||
|
>
|
||||||
|
{t("joblines.fields.lbr_types.LAA")}
|
||||||
|
</Checkbox>
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
value="LAB"
|
value="LAB"
|
||||||
style={{ lineHeight: "32px" }}
|
style={{ lineHeight: "32px" }}
|
||||||
@@ -79,23 +103,23 @@ export default function ShopInfoTaskPresets({ form }) {
|
|||||||
{t("joblines.fields.lbr_types.LAB")}
|
{t("joblines.fields.lbr_types.LAB")}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={8}>
|
<Col span={4}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
value="LAR"
|
value="LAD"
|
||||||
style={{ lineHeight: "32px" }}
|
style={{ lineHeight: "32px" }}
|
||||||
>
|
>
|
||||||
{t("joblines.fields.lbr_types.LAR")}
|
{t("joblines.fields.lbr_types.LAD")}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={8}>
|
<Col span={4}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
value="LAM"
|
value="LAE"
|
||||||
style={{ lineHeight: "32px" }}
|
style={{ lineHeight: "32px" }}
|
||||||
>
|
>
|
||||||
{t("joblines.fields.lbr_types.LAM")}
|
{t("joblines.fields.lbr_types.LAE")}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={8}>
|
<Col span={4}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
value="LAF"
|
value="LAF"
|
||||||
style={{ lineHeight: "32px" }}
|
style={{ lineHeight: "32px" }}
|
||||||
@@ -103,7 +127,7 @@ export default function ShopInfoTaskPresets({ form }) {
|
|||||||
{t("joblines.fields.lbr_types.LAF")}
|
{t("joblines.fields.lbr_types.LAF")}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={8}>
|
<Col span={4}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
value="LAG"
|
value="LAG"
|
||||||
style={{ lineHeight: "32px" }}
|
style={{ lineHeight: "32px" }}
|
||||||
@@ -111,12 +135,90 @@ export default function ShopInfoTaskPresets({ form }) {
|
|||||||
{t("joblines.fields.lbr_types.LAG")}
|
{t("joblines.fields.lbr_types.LAG")}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</Col>
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
<Checkbox
|
||||||
|
value="LAM"
|
||||||
|
style={{ lineHeight: "32px" }}
|
||||||
|
>
|
||||||
|
{t("joblines.fields.lbr_types.LAM")}
|
||||||
|
</Checkbox>
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
<Checkbox
|
||||||
|
value="LAM"
|
||||||
|
style={{ lineHeight: "32px" }}
|
||||||
|
>
|
||||||
|
{t("joblines.fields.lbr_types.LAM")}
|
||||||
|
</Checkbox>
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
<Checkbox
|
||||||
|
value="LAR"
|
||||||
|
style={{ lineHeight: "32px" }}
|
||||||
|
>
|
||||||
|
{t("joblines.fields.lbr_types.LAR")}
|
||||||
|
</Checkbox>
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
<Checkbox
|
||||||
|
value="LAS"
|
||||||
|
style={{ lineHeight: "32px" }}
|
||||||
|
>
|
||||||
|
{t("joblines.fields.lbr_types.LAS")}
|
||||||
|
</Checkbox>
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
<Checkbox
|
||||||
|
value="LAU"
|
||||||
|
style={{ lineHeight: "32px" }}
|
||||||
|
>
|
||||||
|
{t("joblines.fields.lbr_types.LAU")}
|
||||||
|
</Checkbox>
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
<Checkbox
|
||||||
|
value="LA1"
|
||||||
|
style={{ lineHeight: "32px" }}
|
||||||
|
>
|
||||||
|
{t("joblines.fields.lbr_types.LA1")}
|
||||||
|
</Checkbox>
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
<Checkbox
|
||||||
|
value="LA2"
|
||||||
|
style={{ lineHeight: "32px" }}
|
||||||
|
>
|
||||||
|
{t("joblines.fields.lbr_types.LA2")}
|
||||||
|
</Checkbox>
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
<Checkbox
|
||||||
|
value="LA3"
|
||||||
|
style={{ lineHeight: "32px" }}
|
||||||
|
>
|
||||||
|
{t("joblines.fields.lbr_types.LA3")}
|
||||||
|
</Checkbox>
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
<Checkbox
|
||||||
|
value="LA4"
|
||||||
|
style={{ lineHeight: "32px" }}
|
||||||
|
>
|
||||||
|
{t("joblines.fields.lbr_types.LA4")}
|
||||||
|
</Checkbox>
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Checkbox.Group>
|
</Checkbox.Group>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.md_tasks_presets.percent")}
|
label={t("bodyshop.fields.md_tasks_presets.percent")}
|
||||||
key={`${index}percent`}
|
key={`${index}percent`}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
name={[field.name, "percent"]}
|
name={[field.name, "percent"]}
|
||||||
>
|
>
|
||||||
<InputNumber min={0} max={100} />
|
<InputNumber min={0} max={100} />
|
||||||
@@ -128,6 +230,17 @@ export default function ShopInfoTaskPresets({ form }) {
|
|||||||
>
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("bodyshop.fields.md_tasks_presets.nextstatus")}
|
||||||
|
key={`${index}nextstatus`}
|
||||||
|
name={[field.name, "nextstatus"]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
options={bodyshop.md_ro_statuses.production_statuses.map(
|
||||||
|
(o) => ({ value: o, label: o })
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<DeleteFilled
|
<DeleteFilled
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -1,28 +1,25 @@
|
|||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Button,
|
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Col,
|
Col,
|
||||||
Form,
|
Form,
|
||||||
Input,
|
Radio,
|
||||||
InputNumber,
|
|
||||||
Row,
|
Row,
|
||||||
|
Select,
|
||||||
|
Skeleton,
|
||||||
Space,
|
Space,
|
||||||
Table,
|
Table,
|
||||||
|
Typography,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import _ from "lodash";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
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 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 JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
||||||
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
|
||||||
import { LaborAllocationContainer } from "../time-ticket-modal/time-ticket-modal.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import TimeTicketsTasksPresets from "../time-ticket-tasks-presets/time-ticket-tasks-presets.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
@@ -36,23 +33,11 @@ export default connect(
|
|||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(TimeTicketTaskModalComponent);
|
)(TimeTicketTaskModalComponent);
|
||||||
|
|
||||||
export function TimeTicketTaskModalComponent({
|
export function TimeTicketTaskModalComponent({ bodyshop, form, loading }) {
|
||||||
bodyshop,
|
|
||||||
form,
|
|
||||||
lineTicketCalled,
|
|
||||||
calculateTimeTickets,
|
|
||||||
lineTicketLoading,
|
|
||||||
lineTicketData,
|
|
||||||
queryJobInfo,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<TimeTicketsTasksPresets
|
|
||||||
form={form}
|
|
||||||
calculateTimeTickets={calculateTimeTickets}
|
|
||||||
/>
|
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
<Col xl={12} lg={24}>
|
<Col xl={12} lg={24}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -70,303 +55,119 @@ export function TimeTicketTaskModalComponent({
|
|||||||
notExported={!bodyshop.tt_allow_post_to_invoiced}
|
notExported={!bodyshop.tt_allow_post_to_invoiced}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Space wrap>
|
||||||
<Form.Item
|
<Form.Item name="task" label={t("timetickets.labels.task")}>
|
||||||
name="employeeteamid"
|
<Radio.Group
|
||||||
label={t("timetickets.fields.employee_team")}
|
optionType="button"
|
||||||
>
|
options={bodyshop.md_tasks_presets.presets.map((preset) => ({
|
||||||
<EmployeeTeamSearchSelectComponent />
|
value: preset.name,
|
||||||
</Form.Item>
|
label: preset.name,
|
||||||
|
}))}
|
||||||
<Form.Item
|
/>
|
||||||
name="hourstype"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Checkbox.Group>
|
|
||||||
<Space wrap>
|
|
||||||
<Checkbox value="LAB" style={{ display: "flex" }}>
|
|
||||||
{t("jobs.fields.lab")}
|
|
||||||
</Checkbox>
|
|
||||||
<Checkbox value="LAR" style={{ display: "flex" }}>
|
|
||||||
{t("jobs.fields.lar")}
|
|
||||||
</Checkbox>
|
|
||||||
<Checkbox value="LAM" style={{ display: "flex" }}>
|
|
||||||
{t("jobs.fields.lam")}
|
|
||||||
</Checkbox>
|
|
||||||
<Checkbox value="LAF" style={{ display: "flex" }}>
|
|
||||||
{t("jobs.fields.laf")}
|
|
||||||
</Checkbox>
|
|
||||||
<Checkbox value="LAG" style={{ display: "flex" }}>
|
|
||||||
{t("jobs.fields.lag")}
|
|
||||||
</Checkbox>
|
|
||||||
</Space>
|
|
||||||
</Checkbox.Group>
|
|
||||||
</Form.Item>
|
|
||||||
<Space wrap align="start">
|
|
||||||
<Form.Item
|
|
||||||
name="percent"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<InputNumber min={0} max={100} precision={1} addonAfter="%" />
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item dependencies={["task"]}>
|
||||||
|
{() => {
|
||||||
|
const { task } = form.getFieldsValue();
|
||||||
|
const theTaskPreset = bodyshop.md_tasks_presets.presets.find(
|
||||||
|
(tp) => tp.name === task
|
||||||
|
);
|
||||||
|
|
||||||
<Button onClick={calculateTimeTickets}>
|
if (!task) return null;
|
||||||
{t("tt_approvals.labels.calculate")}
|
return (
|
||||||
</Button>
|
<table className="bill-inventory-table">
|
||||||
|
<tr>
|
||||||
|
<td>{t("bodyshop.fields.md_tasks_presets.percent")}</td>
|
||||||
|
<td>{`${theTaskPreset.percent || 0}%`}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{t("bodyshop.fields.md_tasks_presets.hourstype")}</td>
|
||||||
|
<td>{theTaskPreset.hourstype.join(", ")}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{t("bodyshop.fields.md_tasks_presets.nextstatus")}
|
||||||
|
</td>
|
||||||
|
<td>{theTaskPreset.nextstatus}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Form.Item>
|
||||||
</Space>
|
</Space>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xl={12} lg={24}>
|
<Col xl={12} lg={24}>
|
||||||
<Form.Item shouldUpdate>
|
{loading ? (
|
||||||
{() => {
|
<Skeleton />
|
||||||
const data = form.getFieldValue("timetickets");
|
) : (
|
||||||
return (
|
<Form.List name="timetickets">
|
||||||
<Table
|
{(fields, { add, remove, move }) => {
|
||||||
dataSource={data}
|
return (
|
||||||
rowKey={"employeeid"}
|
<>
|
||||||
columns={[
|
<Typography.Title level={4}>
|
||||||
{
|
{t("timetickets.labels.claimtaskpreview")}
|
||||||
title: t("timetickets.fields.employee"),
|
</Typography.Title>
|
||||||
dataIndex: "employee",
|
<table className="bill-cm-returns-table">
|
||||||
key: "employee",
|
<thead>
|
||||||
render: (text, record) => {
|
<tr>
|
||||||
const emp = bodyshop.employees.find(
|
<th>{t("timetickets.fields.employee")}</th>
|
||||||
(e) => e.id === record.employeeid
|
<th>{t("timetickets.fields.cost_center")}</th>
|
||||||
);
|
<th>{t("timetickets.fields.ciecacode")}</th>
|
||||||
return `${emp?.first_name} ${emp?.last_name}`;
|
<th>{t("timetickets.fields.productivehrs")}</th>
|
||||||
},
|
</tr>
|
||||||
},
|
</thead>
|
||||||
{
|
<tbody>
|
||||||
title: t("timetickets.fields.cost_center"),
|
{fields.map((field, index) => (
|
||||||
dataIndex: "cost_center",
|
<tr key={field.key}>
|
||||||
key: "cost_center",
|
<td>
|
||||||
|
<Form.Item
|
||||||
render: (text, record) =>
|
key={`${index}employeeid`}
|
||||||
record.cost_center === "timetickets.labels.shift"
|
name={[field.name, "employeeid"]}
|
||||||
? t(record.cost_center)
|
>
|
||||||
: record.cost_center,
|
<ReadOnlyFormItemComponent type="employee" />
|
||||||
},
|
</Form.Item>
|
||||||
{
|
</td>
|
||||||
title: t("timetickets.fields.productivehrs"),
|
<td>
|
||||||
dataIndex: "productivehrs",
|
<Form.Item
|
||||||
key: "productivehrs",
|
key={`${index}cost_center`}
|
||||||
},
|
name={[field.name, "cost_center"]}
|
||||||
{
|
>
|
||||||
title: "Percentage",
|
<ReadOnlyFormItemComponent />
|
||||||
dataIndex: "percentage",
|
</Form.Item>
|
||||||
key: "percentage",
|
</td>
|
||||||
},
|
<td>
|
||||||
{
|
<Form.Item
|
||||||
title: "Rate",
|
key={`${index}ciecacode`}
|
||||||
dataIndex: "rate",
|
name={[field.name, "ciecacode"]}
|
||||||
key: "rate",
|
>
|
||||||
},
|
<ReadOnlyFormItemComponent />
|
||||||
// {
|
</Form.Item>
|
||||||
// title: "Pay",
|
</td>
|
||||||
// dataIndex: "pay",
|
<td>
|
||||||
// key: "pay",
|
<Form.Item
|
||||||
// },
|
key={`${index}productivehrs`}
|
||||||
]}
|
name={[field.name, "productivehrs"]}
|
||||||
/>
|
>
|
||||||
);
|
<ReadOnlyFormItemComponent />
|
||||||
}}
|
</Form.Item>
|
||||||
</Form.Item>
|
</td>
|
||||||
|
</tr>
|
||||||
<Form.List
|
))}
|
||||||
name={["timetickets"]}
|
</tbody>
|
||||||
rules={[
|
</table>
|
||||||
{
|
<Alert
|
||||||
validator: (rule, value) => {
|
type="success"
|
||||||
//Check the cost center,
|
message={t("timetickets.labels.payrollclaimedtasks")}
|
||||||
const totals = CalculateAllocationsTotals(
|
/>
|
||||||
bodyshop,
|
</>
|
||||||
lineTicketData.joblines,
|
);
|
||||||
lineTicketData.timetickets,
|
}}
|
||||||
lineTicketData.jobs_by_pk.lbr_adjustments
|
</Form.List>
|
||||||
);
|
)}
|
||||||
|
|
||||||
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>
|
</Col>
|
||||||
</Row>
|
</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>
|
|
||||||
{bodyshop?.md_tasks_presets?.use_approvals && (
|
{bodyshop?.md_tasks_presets?.use_approvals && (
|
||||||
<Col span={24}>
|
<Col xl={12} lg={24}>
|
||||||
<Alert
|
<Alert
|
||||||
message={t("tt_approvals.labels.approval_queue_in_use")}
|
message={t("tt_approvals.labels.approval_queue_in_use")}
|
||||||
type="warning"
|
type="warning"
|
||||||
|
|||||||
@@ -1,21 +1,14 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import { useLazyQuery, useMutation, useQuery } from "@apollo/client";
|
|
||||||
import { Form, Modal, notification } from "antd";
|
import { Form, Modal, notification } from "antd";
|
||||||
import Dinero from "dinero.js";
|
import axios from "axios";
|
||||||
import _ from "lodash";
|
|
||||||
import moment from "moment";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { QUERY_ACTIVE_EMPLOYEES } from "../../graphql/employees.queries";
|
|
||||||
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 { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||||
import { selectTimeTicketTasks } from "../../redux/modals/modals.selectors";
|
import { selectTimeTicketTasks } from "../../redux/modals/modals.selectors";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import TimeTicketTaskModalComponent from "./time-ticket-task-modal.component";
|
import TimeTicketTaskModalComponent from "./time-ticket-task-modal.component";
|
||||||
import { INSERT_NEW_TT_APPROVALS } from "../../graphql/tt-approvals.queries";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
timeTicketTasksModal: selectTimeTicketTasks,
|
timeTicketTasksModal: selectTimeTicketTasks,
|
||||||
@@ -36,147 +29,48 @@ export function TimeTickeTaskModalContainer({
|
|||||||
}) {
|
}) {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const { context, visible } = timeTicketTasksModal;
|
const { context, visible } = timeTicketTasksModal;
|
||||||
const { data: EmployeeAutoCompleteData } = useQuery(QUERY_ACTIVE_EMPLOYEES, {
|
|
||||||
skip: !visible,
|
|
||||||
fetchPolicy: "network-only",
|
|
||||||
nextFetchPolicy: "network-only",
|
|
||||||
});
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [insertTimeTickets] = useMutation(INSERT_NEW_TIME_TICKET);
|
const [loading, setLoading] = useState(false);
|
||||||
const [insertTimeTicketApproval] = useMutation(INSERT_NEW_TT_APPROVALS);
|
|
||||||
const [queryJobInfo, { called, loading, data: lineTicketData }] =
|
|
||||||
useLazyQuery(GET_JOB_INFO_DRAW_CALCULATIONS, {
|
|
||||||
fetchPolicy: "network-only",
|
|
||||||
nextFetchPolicy: "network-only",
|
|
||||||
});
|
|
||||||
|
|
||||||
async function handleFinish(values) {
|
async function handleFinish(values) {
|
||||||
try {
|
calculateTickets({ values, handleFinish: true });
|
||||||
if (bodyshop.md_tasks_presets.use_approvals) {
|
}
|
||||||
const result = await insertTimeTicketApproval({
|
|
||||||
variables: {
|
async function handleValueChange(changedValues, allValues) {
|
||||||
timeTicketInput: values.timetickets.map((ticket) => ({
|
if (allValues.jobid && allValues.task) {
|
||||||
..._.omit(ticket, "pay"),
|
calculateTickets({ values: allValues, handleFinish: false });
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
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"),
|
|
||||||
});
|
|
||||||
form.resetFields();
|
|
||||||
toggleModalVisible();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const result = await insertTimeTickets({
|
|
||||||
variables: {
|
|
||||||
timeTicketInput: values.timetickets.map((ticket) =>
|
|
||||||
_.omit(ticket, "pay")
|
|
||||||
),
|
|
||||||
},
|
|
||||||
refetchQueries: ["GET_LINE_TICKET_BY_PK"]
|
|
||||||
});
|
|
||||||
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) {
|
|
||||||
console.log("🚀 ~ file: time-ticket-task-modal.container.jsx:104 ~ handleFinish ~ error:", error)
|
|
||||||
notification.open({
|
|
||||||
type: "error",
|
|
||||||
message: t("timetickets.errors.creating", {
|
|
||||||
message: JSON.stringify(error),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
const calculateTickets = async ({ values, handleFinish }) => {
|
||||||
if (visible && context.jobid) {
|
setLoading(true);
|
||||||
queryJobInfo({ variables: { id: context.jobid } });
|
try {
|
||||||
}
|
const { data, ...response } = await axios.post("/payroll/claimtask", {
|
||||||
}, [context.jobid, queryJobInfo, visible]);
|
jobid: values.jobid,
|
||||||
|
task: values.task,
|
||||||
const calculateTimeTickets = (presetMemo) => {
|
calculateOnly: !handleFinish,
|
||||||
const formData = form.getFieldsValue();
|
});
|
||||||
if (
|
if (response.status === 200 && handleFinish) {
|
||||||
!formData.jobid ||
|
//Close the modal
|
||||||
!formData.employeeteamid ||
|
toggleModalVisible();
|
||||||
!formData.hourstype ||
|
} else if (handleFinish === false) {
|
||||||
formData.hourstype.length === 0 ||
|
form.setFieldsValue({ timetickets: data });
|
||||||
!formData.percent ||
|
} else {
|
||||||
!lineTicketData
|
notification.open({
|
||||||
) {
|
type: "error",
|
||||||
return;
|
message: t("timetickets.errors.creating", {
|
||||||
}
|
message: JSON.stringify(data),
|
||||||
|
}),
|
||||||
let data = [];
|
|
||||||
let eligibleHours = 0;
|
|
||||||
|
|
||||||
const theTeam = JSON.parse(formData.employeeteamid);
|
|
||||||
|
|
||||||
if (theTeam) {
|
|
||||||
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: typeof presetMemo === "string" ? presetMemo : "",
|
|
||||||
flat_rate: true,
|
|
||||||
ciecacode: hourstype,
|
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
notification.open({
|
||||||
|
type: "error",
|
||||||
|
message: t("timetickets.errors.creating", { message: error.message }),
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
form.setFieldsValue({
|
setLoading(false);
|
||||||
timetickets: data.filter((d) => d.productivehrs > 0),
|
|
||||||
});
|
|
||||||
form.validateFields();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -197,18 +91,9 @@ export function TimeTickeTaskModalContainer({
|
|||||||
layout="vertical"
|
layout="vertical"
|
||||||
onFinish={handleFinish}
|
onFinish={handleFinish}
|
||||||
initialValues={context}
|
initialValues={context}
|
||||||
|
onValuesChange={handleValueChange}
|
||||||
>
|
>
|
||||||
<TimeTicketTaskModalComponent
|
<TimeTicketTaskModalComponent form={form} loading={loading} />
|
||||||
form={form}
|
|
||||||
employeeAutoCompleteOptions={
|
|
||||||
EmployeeAutoCompleteData && EmployeeAutoCompleteData.employees
|
|
||||||
}
|
|
||||||
lineTicketData={lineTicketData}
|
|
||||||
lineTicketLoading={loading}
|
|
||||||
lineTicketCalled={called}
|
|
||||||
calculateTimeTickets={calculateTimeTickets}
|
|
||||||
queryJobInfo={queryJobInfo}
|
|
||||||
/>
|
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { Button, Dropdown } from "antd";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(TimeTicketTaskCollector);
|
|
||||||
|
|
||||||
export function TimeTicketTaskCollector({ form, bodyshop }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const items = [];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dropdown menu={{ items }}>
|
|
||||||
<Button>{t("timetickets.actions.tasks")}</Button>
|
|
||||||
</Dropdown>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
import { Button, Dropdown } from "antd";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
//currentUser: selectCurrentUser
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(TimeTicketsTasksPresets);
|
|
||||||
|
|
||||||
export function TimeTicketsTasksPresets({
|
|
||||||
bodyshop,
|
|
||||||
form,
|
|
||||||
calculateTimeTickets,
|
|
||||||
}) {
|
|
||||||
const handleClick = (props) => {
|
|
||||||
const preset = bodyshop.md_tasks_presets?.presets?.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: bodyshop.md_tasks_presets?.presets
|
|
||||||
? bodyshop.md_tasks_presets?.presets?.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 },
|
|
||||||
// ];
|
|
||||||
@@ -269,6 +269,8 @@
|
|||||||
"templates": "Delivery Templates"
|
"templates": "Delivery Templates"
|
||||||
},
|
},
|
||||||
"dms": {
|
"dms": {
|
||||||
|
"apcontrol": "",
|
||||||
|
"appostingaccount": "",
|
||||||
"cashierid": "Cashier ID",
|
"cashierid": "Cashier ID",
|
||||||
"default_journal": "Default Journal",
|
"default_journal": "Default Journal",
|
||||||
"disablebillwip": "Disable bill WIP for A/P Posting",
|
"disablebillwip": "Disable bill WIP for A/P Posting",
|
||||||
@@ -347,6 +349,7 @@
|
|||||||
"hourstype": "Hour Types",
|
"hourstype": "Hour Types",
|
||||||
"memo": "Time Ticket Memo",
|
"memo": "Time Ticket Memo",
|
||||||
"name": "Preset Name",
|
"name": "Preset Name",
|
||||||
|
"nextstatus": "Next Status",
|
||||||
"percent": "Percent",
|
"percent": "Percent",
|
||||||
"use_approvals": "Use Time Ticket Approval Queue"
|
"use_approvals": "Use Time Ticket Approval Queue"
|
||||||
},
|
},
|
||||||
@@ -2759,6 +2762,7 @@
|
|||||||
"alreadyclockedon": "You are already clocked in to the following job(s):",
|
"alreadyclockedon": "You are already clocked in to the following job(s):",
|
||||||
"ambreak": "AM Break",
|
"ambreak": "AM Break",
|
||||||
"amshift": "AM Shift",
|
"amshift": "AM Shift",
|
||||||
|
"claimtaskpreview": "Claimed Tasks Preview",
|
||||||
"clockhours": "Shift Clock Hours Summary",
|
"clockhours": "Shift Clock Hours Summary",
|
||||||
"clockintojob": "Clock In to Job",
|
"clockintojob": "Clock In to Job",
|
||||||
"deleteconfirm": "Are you sure you want to delete this time ticket? This cannot be undone.",
|
"deleteconfirm": "Are you sure you want to delete this time ticket? This cannot be undone.",
|
||||||
@@ -2768,11 +2772,13 @@
|
|||||||
"jobhours": "Job Related Time Tickets Summary",
|
"jobhours": "Job Related Time Tickets Summary",
|
||||||
"lunch": "Lunch",
|
"lunch": "Lunch",
|
||||||
"new": "New Time Ticket",
|
"new": "New Time Ticket",
|
||||||
|
"payrollclaimedtasks": "These time tickets will be automatically entered to the system as a part of claiming this task. These numbers are calculated using the jobs assigned lines. If lines are unassigned, they will be excluded from created tickets.",
|
||||||
"pmbreak": "PM Break",
|
"pmbreak": "PM Break",
|
||||||
"pmshift": "PM Shift",
|
"pmshift": "PM Shift",
|
||||||
"shift": "Shift",
|
"shift": "Shift",
|
||||||
"shiftalreadyclockedon": "Active Shift Time Tickets",
|
"shiftalreadyclockedon": "Active Shift Time Tickets",
|
||||||
"straight_time": "Straight Time",
|
"straight_time": "Straight Time",
|
||||||
|
"task": "Task",
|
||||||
"timetickets": "Time Tickets",
|
"timetickets": "Time Tickets",
|
||||||
"zeroactualnegativeprod": "Actual hours must be 0 if entering negative productive hours."
|
"zeroactualnegativeprod": "Actual hours must be 0 if entering negative productive hours."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -269,6 +269,8 @@
|
|||||||
"templates": ""
|
"templates": ""
|
||||||
},
|
},
|
||||||
"dms": {
|
"dms": {
|
||||||
|
"apcontrol": "",
|
||||||
|
"appostingaccount": "",
|
||||||
"cashierid": "",
|
"cashierid": "",
|
||||||
"default_journal": "",
|
"default_journal": "",
|
||||||
"disablebillwip": "",
|
"disablebillwip": "",
|
||||||
@@ -347,6 +349,7 @@
|
|||||||
"hourstype": "",
|
"hourstype": "",
|
||||||
"memo": "",
|
"memo": "",
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"nextstatus": "",
|
||||||
"percent": "",
|
"percent": "",
|
||||||
"use_approvals": ""
|
"use_approvals": ""
|
||||||
},
|
},
|
||||||
@@ -2759,6 +2762,7 @@
|
|||||||
"alreadyclockedon": "",
|
"alreadyclockedon": "",
|
||||||
"ambreak": "",
|
"ambreak": "",
|
||||||
"amshift": "",
|
"amshift": "",
|
||||||
|
"claimtaskpreview": "",
|
||||||
"clockhours": "",
|
"clockhours": "",
|
||||||
"clockintojob": "",
|
"clockintojob": "",
|
||||||
"deleteconfirm": "",
|
"deleteconfirm": "",
|
||||||
@@ -2768,11 +2772,13 @@
|
|||||||
"jobhours": "",
|
"jobhours": "",
|
||||||
"lunch": "",
|
"lunch": "",
|
||||||
"new": "",
|
"new": "",
|
||||||
|
"payrollclaimedtasks": "",
|
||||||
"pmbreak": "",
|
"pmbreak": "",
|
||||||
"pmshift": "",
|
"pmshift": "",
|
||||||
"shift": "",
|
"shift": "",
|
||||||
"shiftalreadyclockedon": "",
|
"shiftalreadyclockedon": "",
|
||||||
"straight_time": "",
|
"straight_time": "",
|
||||||
|
"task": "",
|
||||||
"timetickets": "",
|
"timetickets": "",
|
||||||
"zeroactualnegativeprod": ""
|
"zeroactualnegativeprod": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -269,6 +269,8 @@
|
|||||||
"templates": ""
|
"templates": ""
|
||||||
},
|
},
|
||||||
"dms": {
|
"dms": {
|
||||||
|
"apcontrol": "",
|
||||||
|
"appostingaccount": "",
|
||||||
"cashierid": "",
|
"cashierid": "",
|
||||||
"default_journal": "",
|
"default_journal": "",
|
||||||
"disablebillwip": "",
|
"disablebillwip": "",
|
||||||
@@ -347,6 +349,7 @@
|
|||||||
"hourstype": "",
|
"hourstype": "",
|
||||||
"memo": "",
|
"memo": "",
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"nextstatus": "",
|
||||||
"percent": "",
|
"percent": "",
|
||||||
"use_approvals": ""
|
"use_approvals": ""
|
||||||
},
|
},
|
||||||
@@ -2759,6 +2762,7 @@
|
|||||||
"alreadyclockedon": "",
|
"alreadyclockedon": "",
|
||||||
"ambreak": "",
|
"ambreak": "",
|
||||||
"amshift": "",
|
"amshift": "",
|
||||||
|
"claimtaskpreview": "",
|
||||||
"clockhours": "",
|
"clockhours": "",
|
||||||
"clockintojob": "",
|
"clockintojob": "",
|
||||||
"deleteconfirm": "",
|
"deleteconfirm": "",
|
||||||
@@ -2768,11 +2772,13 @@
|
|||||||
"jobhours": "",
|
"jobhours": "",
|
||||||
"lunch": "",
|
"lunch": "",
|
||||||
"new": "",
|
"new": "",
|
||||||
|
"payrollclaimedtasks": "",
|
||||||
"pmbreak": "",
|
"pmbreak": "",
|
||||||
"pmshift": "",
|
"pmshift": "",
|
||||||
"shift": "",
|
"shift": "",
|
||||||
"shiftalreadyclockedon": "",
|
"shiftalreadyclockedon": "",
|
||||||
"straight_time": "",
|
"straight_time": "",
|
||||||
|
"task": "",
|
||||||
"timetickets": "",
|
"timetickets": "",
|
||||||
"zeroactualnegativeprod": ""
|
"zeroactualnegativeprod": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -268,6 +268,7 @@ app.post(
|
|||||||
payroll.calculatelabor
|
payroll.calculatelabor
|
||||||
);
|
);
|
||||||
app.post("/payroll/payall", fb.validateFirebaseIdToken, payroll.payall);
|
app.post("/payroll/payall", fb.validateFirebaseIdToken, payroll.payall);
|
||||||
|
app.post("/payroll/claimtask", fb.validateFirebaseIdToken, payroll.claimtask);
|
||||||
|
|
||||||
var ioevent = require("./server/ioevent/ioevent");
|
var ioevent = require("./server/ioevent/ioevent");
|
||||||
app.post("/ioevent", ioevent.default);
|
app.post("/ioevent", ioevent.default);
|
||||||
|
|||||||
@@ -1828,6 +1828,7 @@ exports.QUERY_JOB_PAYROLL_DATA = `query QUERY_JOB_PAYROLL_DATA($id: uuid!) {
|
|||||||
bodyshop{
|
bodyshop{
|
||||||
id
|
id
|
||||||
md_responsibility_centers
|
md_responsibility_centers
|
||||||
|
md_tasks_presets
|
||||||
employee_teams{
|
employee_teams{
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
@@ -1909,6 +1910,8 @@ exports.QUERY_JOB_PAYROLL_DATA = `query QUERY_JOB_PAYROLL_DATA($id: uuid!) {
|
|||||||
misc_amt
|
misc_amt
|
||||||
misc_tax
|
misc_tax
|
||||||
assigned_team
|
assigned_team
|
||||||
|
convertedtolbr
|
||||||
|
convertedtolbr_data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
|
|||||||
88
server/payroll/claim-task.js
Normal file
88
server/payroll/claim-task.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
const Dinero = require("dinero.js");
|
||||||
|
const queries = require("../graphql-client/queries");
|
||||||
|
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||||
|
const logger = require("../utils/logger");
|
||||||
|
const {
|
||||||
|
CalculateExpectedHoursForJob,
|
||||||
|
CalculateTicketsHoursForJob,
|
||||||
|
} = require("./pay-all");
|
||||||
|
|
||||||
|
// Dinero.defaultCurrency = "USD";
|
||||||
|
// Dinero.globalLocale = "en-CA";
|
||||||
|
Dinero.globalRoundingMode = "HALF_EVEN";
|
||||||
|
|
||||||
|
exports.claimtask = async function (req, res) {
|
||||||
|
const BearerToken = req.headers.authorization;
|
||||||
|
const { jobid, task, calculateOnly } = req.body;
|
||||||
|
logger.log("job-payroll-pay-all", "DEBUG", req.user.email, jobid, null);
|
||||||
|
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
||||||
|
headers: {
|
||||||
|
Authorization: BearerToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { jobs_by_pk: job } = await client
|
||||||
|
.setHeaders({ Authorization: BearerToken })
|
||||||
|
.request(queries.QUERY_JOB_PAYROLL_DATA, {
|
||||||
|
id: jobid,
|
||||||
|
});
|
||||||
|
|
||||||
|
const theTaskPreset = job.bodyshop.md_tasks_presets.presets.find(
|
||||||
|
(tp) => tp.name === task
|
||||||
|
);
|
||||||
|
|
||||||
|
//Get all of the assignments that are filtered.
|
||||||
|
const { employeeHash } = CalculateExpectedHoursForJob(
|
||||||
|
job,
|
||||||
|
theTaskPreset.hourstype
|
||||||
|
);
|
||||||
|
const ticketsToInsert = [];
|
||||||
|
//Then add them in based on a percentage to each employee.
|
||||||
|
|
||||||
|
Object.keys(employeeHash).forEach((employeeIdKey) => {
|
||||||
|
//At the employee level.
|
||||||
|
Object.keys(employeeHash[employeeIdKey]).forEach((laborTypeKey) => {
|
||||||
|
//At the labor level
|
||||||
|
Object.keys(employeeHash[employeeIdKey][laborTypeKey]).forEach(
|
||||||
|
(rateKey) => {
|
||||||
|
//At the rate level.
|
||||||
|
const expectedHours =
|
||||||
|
employeeHash[employeeIdKey][laborTypeKey][rateKey] *
|
||||||
|
(theTaskPreset.percent / 100);
|
||||||
|
|
||||||
|
ticketsToInsert.push({
|
||||||
|
jobid: job.id,
|
||||||
|
bodyshopid: job.bodyshop.id,
|
||||||
|
employeeid: employeeIdKey,
|
||||||
|
productivehrs: expectedHours,
|
||||||
|
rate: rateKey,
|
||||||
|
ciecacode: laborTypeKey,
|
||||||
|
flat_rate: true,
|
||||||
|
cost_center:
|
||||||
|
job.bodyshop.md_responsibility_centers.defaults.costs[
|
||||||
|
laborTypeKey
|
||||||
|
],
|
||||||
|
memo: `*Claimed Task* ${theTaskPreset.memo}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (!calculateOnly) {
|
||||||
|
//Insert the time ticekts if we're not just calculating them.
|
||||||
|
const insertResult = await client.request(queries.INSERT_TIME_TICKETS, {
|
||||||
|
timetickets: ticketsToInsert.filter(
|
||||||
|
(ticket) => ticket.productivehrs !== 0
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
res.json(ticketsToInsert);
|
||||||
|
} catch (error) {
|
||||||
|
logger.log("job-payroll-claim-task-error", "ERROR", req.user.email, jobid, {
|
||||||
|
jobid: jobid,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
res.status(503).send();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -85,9 +85,6 @@ exports.payall = async function (req, res) {
|
|||||||
} else if (diff.op === "update") {
|
} else if (diff.op === "update") {
|
||||||
} else {
|
} else {
|
||||||
//Has to be a delete
|
//Has to be a delete
|
||||||
|
|
||||||
//////
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
typeof diff.oldVal === "object" &&
|
typeof diff.oldVal === "object" &&
|
||||||
Object.keys(diff.oldVal).length > 1
|
Object.keys(diff.oldVal).length > 1
|
||||||
@@ -211,58 +208,75 @@ function diffParser(diff) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
function CalculateExpectedHoursForJob(job) {
|
function CalculateExpectedHoursForJob(job, filterToLbrTypes) {
|
||||||
const assignmentHash = { unassigned: 0 };
|
const assignmentHash = { unassigned: 0 };
|
||||||
const employeeHash = {}; // employeeid => Cieca labor type => rate => hours. Contains how many hours each person should be paid.
|
const employeeHash = {}; // employeeid => Cieca labor type => rate => hours. Contains how many hours each person should be paid.
|
||||||
job.joblines.forEach((jobline) => {
|
job.joblines
|
||||||
if (jobline.mod_lb_hrs != 0) {
|
.filter((jobline) => {
|
||||||
//Check if the line is assigned. If not, keep track of it as an unassigned line by type.
|
if (!filterToLbrTypes) return true;
|
||||||
if (jobline.assigned_team === null) {
|
else {
|
||||||
assignmentHash.unassigned =
|
return (
|
||||||
assignmentHash.unassigned + jobline.mod_lb_hrs;
|
filterToLbrTypes.includes(jobline.mod_lbr_ty) ||
|
||||||
} else {
|
(jobline.convertedtolbr &&
|
||||||
//Line is assigned.
|
filterToLbrTypes.includes(jobline.convertedtolbr_data.mod_lbr_ty))
|
||||||
if (!assignmentHash[jobline.assigned_team]) {
|
|
||||||
assignmentHash[jobline.assigned_team] = 0;
|
|
||||||
}
|
|
||||||
assignmentHash[jobline.assigned_team] =
|
|
||||||
assignmentHash[jobline.assigned_team] + jobline.mod_lb_hrs;
|
|
||||||
|
|
||||||
//Create the assignment breakdown.
|
|
||||||
const theTeam = job.bodyshop.employee_teams.find(
|
|
||||||
(team) => team.id === jobline.assigned_team
|
|
||||||
);
|
);
|
||||||
|
|
||||||
theTeam.employee_team_members.forEach((tm) => {
|
|
||||||
//Figure out how many hours they are owed at this line, and at what rate.
|
|
||||||
|
|
||||||
if (!employeeHash[tm.employee.id]) {
|
|
||||||
employeeHash[tm.employee.id] = {};
|
|
||||||
}
|
|
||||||
if (!employeeHash[tm.employee.id][jobline.mod_lbr_ty]) {
|
|
||||||
employeeHash[tm.employee.id][jobline.mod_lbr_ty] = {};
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!employeeHash[tm.employee.id][jobline.mod_lbr_ty][
|
|
||||||
tm.labor_rates[jobline.mod_lbr_ty]
|
|
||||||
]
|
|
||||||
) {
|
|
||||||
employeeHash[tm.employee.id][jobline.mod_lbr_ty][
|
|
||||||
tm.labor_rates[jobline.mod_lbr_ty]
|
|
||||||
] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hoursOwed = (tm.percentage * jobline.mod_lb_hrs) / 100;
|
|
||||||
employeeHash[tm.employee.id][jobline.mod_lbr_ty][
|
|
||||||
tm.labor_rates[jobline.mod_lbr_ty]
|
|
||||||
] =
|
|
||||||
employeeHash[tm.employee.id][jobline.mod_lbr_ty][
|
|
||||||
tm.labor_rates[jobline.mod_lbr_ty]
|
|
||||||
] + hoursOwed;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
});
|
.forEach((jobline) => {
|
||||||
|
if (jobline.convertedtolbr) {
|
||||||
|
// Line has been converte to labor. Temporarily re-assign the hours.
|
||||||
|
jobline.mod_lbr_ty =
|
||||||
|
jobline.mod_lbr_ty || jobline.convertedtolbr_data.mod_lbr_ty;
|
||||||
|
jobline.mod_lb_hrs += jobline.convertedtolbr_data.mod_lb_hrs;
|
||||||
|
}
|
||||||
|
if (jobline.mod_lb_hrs != 0) {
|
||||||
|
//Check if the line is assigned. If not, keep track of it as an unassigned line by type.
|
||||||
|
if (jobline.assigned_team === null) {
|
||||||
|
assignmentHash.unassigned =
|
||||||
|
assignmentHash.unassigned + jobline.mod_lb_hrs;
|
||||||
|
} else {
|
||||||
|
//Line is assigned.
|
||||||
|
if (!assignmentHash[jobline.assigned_team]) {
|
||||||
|
assignmentHash[jobline.assigned_team] = 0;
|
||||||
|
}
|
||||||
|
assignmentHash[jobline.assigned_team] =
|
||||||
|
assignmentHash[jobline.assigned_team] + jobline.mod_lb_hrs;
|
||||||
|
|
||||||
|
//Create the assignment breakdown.
|
||||||
|
const theTeam = job.bodyshop.employee_teams.find(
|
||||||
|
(team) => team.id === jobline.assigned_team
|
||||||
|
);
|
||||||
|
|
||||||
|
theTeam.employee_team_members.forEach((tm) => {
|
||||||
|
//Figure out how many hours they are owed at this line, and at what rate.
|
||||||
|
|
||||||
|
if (!employeeHash[tm.employee.id]) {
|
||||||
|
employeeHash[tm.employee.id] = {};
|
||||||
|
}
|
||||||
|
if (!employeeHash[tm.employee.id][jobline.mod_lbr_ty]) {
|
||||||
|
employeeHash[tm.employee.id][jobline.mod_lbr_ty] = {};
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!employeeHash[tm.employee.id][jobline.mod_lbr_ty][
|
||||||
|
tm.labor_rates[jobline.mod_lbr_ty]
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
employeeHash[tm.employee.id][jobline.mod_lbr_ty][
|
||||||
|
tm.labor_rates[jobline.mod_lbr_ty]
|
||||||
|
] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hoursOwed = (tm.percentage * jobline.mod_lb_hrs) / 100;
|
||||||
|
employeeHash[tm.employee.id][jobline.mod_lbr_ty][
|
||||||
|
tm.labor_rates[jobline.mod_lbr_ty]
|
||||||
|
] =
|
||||||
|
employeeHash[tm.employee.id][jobline.mod_lbr_ty][
|
||||||
|
tm.labor_rates[jobline.mod_lbr_ty]
|
||||||
|
] + hoursOwed;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return { assignmentHash, employeeHash };
|
return { assignmentHash, employeeHash };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
exports.calculatelabor = require("./calculate-totals").calculatelabor;
|
exports.calculatelabor = require("./calculate-totals").calculatelabor;
|
||||||
exports.payall = require("./pay-all").payall;
|
exports.payall = require("./pay-all").payall;
|
||||||
|
exports.claimtask = require("./claim-task").claimtask;
|
||||||
|
|||||||
Reference in New Issue
Block a user