Refactored job closing to be line based instead of totals based. BOD-383

This commit is contained in:
Patrick Fic
2020-09-14 13:54:11 -07:00
parent e3f108c567
commit eff49e3d25
34 changed files with 1030 additions and 822 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.7.1">
<babeledit_project be_version="2.7.1" version="1.2">
<!--
BabelEdit project file
@@ -11026,6 +11026,48 @@
</concept_node>
</children>
</folder_node>
<concept_node>
<name>profitcenter_labor</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>profitcenter_part</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>status</name>
<definition_loaded>false</definition_loaded>
@@ -11791,6 +11833,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>closing</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>creating</name>
<definition_loaded>false</definition_loaded>
@@ -15617,6 +15680,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>closeconfirm</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>cost</name>
<definition_loaded>false</definition_loaded>
@@ -16787,6 +16871,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>closed</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>converted</name>
<definition_loaded>false</definition_loaded>

View File

@@ -0,0 +1,11 @@
import React, { forwardRef } from "react";
import { useTranslation } from "react-i18next";
const LaborTypeFormItem = ({ value, onChange }, ref) => {
const { t } = useTranslation();
if (!value) return null;
return <div>{t(`joblines.fields.lbr_types.${value}`)}</div>;
};
export default forwardRef(LaborTypeFormItem);

View File

@@ -0,0 +1,11 @@
import React, { forwardRef } from "react";
import { useTranslation } from "react-i18next";
const PartTypeFormItem = ({ value, onChange }, ref) => {
const { t } = useTranslation();
if (!value) return null;
return <div>{t(`joblines.fields.part_types.${value}`)}</div>;
};
export default forwardRef(PartTypeFormItem);

View File

@@ -0,0 +1,17 @@
import Dinero from "dinero.js";
import React, { forwardRef } from "react";
const ReadOnlyFormItem = ({ value, type = "text", onChange }, ref) => {
if (!value) return null;
switch (type) {
case "text":
return <div>{value}</div>;
case "currency":
return (
<div>{Dinero({ amount: Math.round(value * 100) }).toFormat()}</div>
);
default:
return <div>{value}</div>;
}
};
export default forwardRef(ReadOnlyFormItem);

View File

@@ -1,23 +0,0 @@
import { Button } from "antd";
import React, { forwardRef } from "react";
import { useTranslation } from "react-i18next";
import AlertComponent from "../alert/alert.component";
function ResetForm({ resetFields }) {
const { t } = useTranslation();
return (
<AlertComponent
message={
<div>
{t("general.messages.unsavedchanges")}
<Button style={{ marginLeft: "20px" }} onClick={() => resetFields()}>
{t("general.actions.reset")}
</Button>
</div>
}
closable
/>
);
}
export default forwardRef(ResetForm);

View File

@@ -1,119 +0,0 @@
import { PlusCircleFilled, CloseCircleFilled } from "@ant-design/icons";
import { Button, InputNumber, Select } from "antd";
import Dinero from "dinero.js";
import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const { Option } = Select;
export function JobsCloseLabmatAllocationButton({
remainingAmount,
allocationKey,
allocation,
setAllocations,
bodyshop,
invoiced,
}) {
const [visible, setVisible] = useState(false);
const [state, setState] = useState({ center: "", amount: 0 });
const { t } = useTranslation();
const handleAllocate = () => {
logImEXEvent("jobs_close_allocate_single");
const existingIndex = allocation.allocations.findIndex(
(e) => e.center === state.center
);
const newAllocations = allocation.allocations.slice(0);
if (existingIndex > -1) {
newAllocations[existingIndex] = {
center: state.center,
amount: newAllocations[existingIndex].amount.add(
Dinero({ amount: (state.amount || 0) * 100 })
),
};
} else {
newAllocations.push({
center: state.center,
amount: Dinero({ amount: (state.amount || 0) * 100 }),
});
}
setAllocations((labMatState) => {
return {
...labMatState,
[allocationKey]: {
...allocation,
allocations: newAllocations,
},
};
});
setState({ center: "", amount: 0 });
};
const showAllocation = Dinero(allocation.total).getAmount() > 0;
useEffect(() => {
if (remainingAmount === 0) setVisible(false);
}, [remainingAmount, setVisible]);
if (!showAllocation) return null;
return (
<div style={{ display: "flex", alignItems: "center" }}>
<div style={{ display: visible ? "" : "none" }}>
<Select
style={{ width: "200px" }}
value={state.center}
onSelect={(val) => setState({ ...state, center: val })}
>
{bodyshop.md_responsibility_centers.profits.map((r, idx) => (
<Option key={idx} value={r.name}>
{r.name}
</Option>
))}
</Select>
<InputNumber
precision={2}
min={0}
value={state.amount}
onChange={(val) => setState({ ...state, amount: val })}
max={remainingAmount / 100}
/>
<Button
onClick={handleAllocate}
disabled={
state.amount === 0 ||
state.center === "" ||
remainingAmount === 0 ||
invoiced
}
>
{t("jobs.actions.allocate")}
</Button>
</div>
<div>
{visible ? (
<CloseCircleFilled
onClick={() => setVisible(false)}
disabled={invoiced}
/>
) : (
<PlusCircleFilled
onClick={() => setVisible(true)}
disabled={invoiced}
/>
)}
</div>
</div>
);
}
export default connect(mapStateToProps, null)(JobsCloseLabmatAllocationButton);

View File

@@ -1,35 +0,0 @@
import React from "react";
import { Tag } from "antd";
import Dinero from "dinero.js";
export default function JobsCloseLabMatAllocationTags({
allocationKey,
allocation,
setAllocations,
invoiced,
}) {
return (
<div>
{allocation.allocations.map((a, idx) => (
<Tag
closable={!invoiced} //Value is whether it is invoiced.
visible
color="green"
onClose={() => {
setAllocations((state) => {
return {
...state,
[allocationKey]: {
...allocation,
allocations: allocation.allocations.filter(
(val, index) => index !== idx
),
},
};
});
}}
key={idx}
>{`${a.center} - ${Dinero(a.amount).toFormat()}`}</Tag>
))}
</div>
);
}

View File

@@ -1,76 +1,38 @@
import React from "react";
import { Button } from "antd";
import { selectBodyshop } from "../../redux/user/user.selectors";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
import Dinero from "dinero.js";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
export function JobsCloseAutoAllocate({
bodyshop,
labmatAllocations,
setLabmatAllocations,
partsAllocations,
setPartsAllocations,
disabled,
}) {
export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
const { t } = useTranslation();
const handleAllocate = () => {
logImEXEvent("jobs_close_allocate_auto");
const { defaults } = bodyshop.md_responsibility_centers;
Object.keys(labmatAllocations).forEach((i) => {
const defaultProfitCenter = defaults.profits[i.toUpperCase()];
if (
!!defaultProfitCenter &&
Dinero(labmatAllocations[i].total).getAmount() > 0
) {
setLabmatAllocations((st) => {
return {
...st,
[i]: {
...labmatAllocations[i],
allocations: [
{
center: defaultProfitCenter,
amount: labmatAllocations[i].total,
},
],
},
};
});
}
});
Object.keys(partsAllocations).forEach((i) => {
const defaultProfitCenter = defaults.profits[i.toUpperCase()];
if (
!!defaultProfitCenter &&
Dinero(partsAllocations[i].total).getAmount() > 0
) {
setPartsAllocations((st) => {
return {
...st,
[i]: {
...partsAllocations[i],
allocations: [
{
center: defaultProfitCenter,
amount: partsAllocations[i].total,
},
],
},
};
});
}
form.setFieldsValue({
joblines: joblines.map((jl) => {
const ret = jl;
if (jl.part_type) {
ret.profitcenter_part = defaults.profits[jl.part_type.toUpperCase()];
} else {
ret.profitcenter_part = null;
}
if (jl.mod_lbr_ty) {
ret.profitcenter_labor =
defaults.profits[jl.mod_lbr_ty.toUpperCase()];
} else {
ret.profitcenter_labor = null;
}
return ret;
}),
});
};

View File

@@ -1,94 +0,0 @@
import Dinero from "dinero.js";
import React from "react";
import { useTranslation } from "react-i18next";
import AllocationButton from "../jobs-close-allocation-button/jobs-close-allocation-button.component";
import AllocationTags from "../jobs-close-allocation-tags/jobs-close-allocation-tags.component";
export default function JobCloseLabMatAllocation({
labmatAllocations,
setLabmatAllocations,
labMatTotalAllocation,
invoiced,
}) {
const { t } = useTranslation();
return (
<div style={{ display: "flex" }}>
<table>
<thead>
<tr>
<th>{t("jobs.labels.laborallocations")}</th>
<th>{t("jobs.labels.totals")}</th>
<th>{t("jobs.labels.available")}</th>
<th>{t("jobs.actions.allocate")}</th>
<th>{t("jobs.labels.allocations")}</th>
</tr>
</thead>
<tbody>
{Object.keys(labmatAllocations).map((alloc, idx) => {
if (!alloc.includes("subtotal"))
return (
<tr key={idx}>
<td>{t(`jobs.fields.${alloc}`)}</td>
<td>
{labmatAllocations[alloc].total &&
Dinero(labmatAllocations[alloc].total).toFormat()}
</td>
<td>
{Dinero(labmatAllocations[alloc].total)
.subtract(
Dinero({
amount: labmatAllocations[alloc].allocations.reduce(
(acc, val) => {
return acc + Dinero(val.amount).getAmount();
},
0
),
})
)
.toFormat()}
</td>
<td>
<AllocationButton
allocationKey={alloc}
invoiced={invoiced}
remainingAmount={Dinero(labmatAllocations[alloc].total)
.subtract(
Dinero({
amount: labmatAllocations[alloc].allocations.reduce(
(acc, val) => {
return acc + Dinero(val.amount).getAmount();
},
0
),
})
)
.getAmount()}
allocation={labmatAllocations[alloc]}
setAllocations={setLabmatAllocations}
/>
</td>
<td>
<AllocationTags
allocationKey={alloc}
invoiced={invoiced}
allocation={labmatAllocations[alloc]}
setAllocations={setLabmatAllocations}
/>
</td>
</tr>
);
else return null;
})}
<tr>
<td></td>
<td>{Dinero(labmatAllocations.subtotal).toFormat()}</td>
<td></td>
<td></td>
<td>{Dinero(labMatTotalAllocation).toFormat()}</td>
</tr>
</tbody>
</table>
</div>
);
}

View File

@@ -0,0 +1,120 @@
import { Form, Select } 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 LaborTypeFormItem from "../form-items-formatted/labor-type-form-item.component";
import PartTypeFormItem from "../form-items-formatted/part-type-form-item.component";
import ReadOnlyFormItem from "../form-items-formatted/read-only-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function JobsCloseLines({ bodyshop, joblines }) {
const { t } = useTranslation();
return (
<div>
<Form.List name={["joblines"]}>
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<LayoutFormRow>
<Form.Item
label={t("joblines.fields.line_desc")}
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
>
<ReadOnlyFormItem />
</Form.Item>
<Form.Item
span={2}
label={t("joblines.fields.part_type")}
key={`${index}part_type`}
name={[field.name, "part_type"]}
>
<PartTypeFormItem />
</Form.Item>
<Form.Item
span={2}
label={t("joblines.fields.act_price")}
key={`${index}act_price`}
name={[field.name, "act_price"]}
>
<ReadOnlyFormItem type="currency" />
</Form.Item>
<Form.Item
span={2}
label={t("joblines.fields.mod_lbr_ty")}
key={`${index}mod_lbr_ty`}
name={[field.name, "mod_lbr_ty"]}
>
<LaborTypeFormItem />
</Form.Item>
<Form.Item
span={2}
label={t("joblines.fields.mod_lb_hrs")}
key={`${index}mod_lb_hrs`}
name={[field.name, "mod_lb_hrs"]}
>
<ReadOnlyFormItem />
</Form.Item>
<Form.Item
label={t("joblines.fields.profitcenter_part")}
key={`${index}profitcenter_part`}
name={[field.name, "profitcenter_part"]}
rules={[
{
required:
!!joblines[index].part_type &&
!!joblines[index].act_price,
message: t("general.validation.required"),
},
]}
>
<Select allowClear>
{bodyshop.md_responsibility_centers.profits.map((p) => (
<Select.Option key={p.name} value={p.name}>
{p.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("joblines.fields.profitcenter_labor")}
key={`${index}profitcenter_labor`}
name={[field.name, "profitcenter_labor"]}
rules={[
{
required: !!joblines[index].mod_lbr_ty,
message: t("general.validation.required"),
},
]}
>
<Select allowClear>
{bodyshop.md_responsibility_centers.profits.map((p) => (
<Select.Option key={p.name} value={p.name}>
{p.name}
</Select.Option>
))}
</Select>
</Form.Item>
</LayoutFormRow>
</Form.Item>
))}
</div>
);
}}
</Form.List>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseLines);

View File

@@ -1,101 +0,0 @@
import React from "react";
import { useTranslation } from "react-i18next";
import Dinero from "dinero.js";
import AllocationButton from "../jobs-close-allocation-button/jobs-close-allocation-button.component";
import AllocationTags from "../jobs-close-allocation-tags/jobs-close-allocation-tags.component";
export default function JobsClosePartsAllocation({
partsAllocations,
setPartsAllocations,
partsAllocatedTotal,
invoiced,
}) {
const { t } = useTranslation();
return (
<div>
{
<table>
<thead>
<tr>
<th>{t("jobs.labels.laborallocations")}</th>
<th>{t("jobs.labels.totals")}</th>
<th>{t("jobs.labels.available")}</th>
<th>{t("jobs.actions.allocate")}</th>
<th>{t("jobs.labels.allocations")}</th>
</tr>
</thead>
<tbody>
{Object.keys(partsAllocations).map((alloc, idx) => {
return (
<tr key={idx}>
<td>{t(`jobs.fields.${alloc.toLowerCase()}`)}</td>
<td>
{partsAllocations[alloc].total &&
Dinero(partsAllocations[alloc].total).toFormat()}
</td>
<td>
{Dinero(partsAllocations[alloc].total)
.subtract(
Dinero({
amount: partsAllocations[alloc].allocations.reduce(
(acc, val) => {
return acc + Dinero(val.amount).getAmount();
},
0
),
})
)
.toFormat()}
</td>
<td>
<AllocationButton
allocationKey={alloc}
invoiced={invoiced}
remainingAmount={Dinero(partsAllocations[alloc].total)
.subtract(
Dinero({
amount: partsAllocations[alloc].allocations.reduce(
(acc, val) => {
return acc + Dinero(val.amount).getAmount();
},
0
),
})
)
.getAmount()}
allocation={partsAllocations[alloc]}
setAllocations={setPartsAllocations}
/>
</td>
<td>
<AllocationTags
invoiced={invoiced}
allocationKey={alloc}
allocation={partsAllocations[alloc]}
setAllocations={setPartsAllocations}
/>
</td>
</tr>
);
})}
<tr>
<td></td>
<td>
{Dinero({
amount: Object.keys(partsAllocations).reduce((acc, val) => {
return (acc =
acc + Dinero(partsAllocations[val].total).getAmount());
}, 0),
}).toFormat()}
</td>
<td></td>
<td></td>
<td>{partsAllocatedTotal.toFormat()}</td>
</tr>
</tbody>
</table>
}
</div>
);
}

View File

@@ -1,72 +0,0 @@
import { useMutation } from "@apollo/react-hooks";
import { Button, notification } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
export function JobsCloseSaveButton({
bodyshop,
suspenseAmount,
jobId,
labMatAllocations,
partsAllocations,
setInvoicedState,
disabled,
}) {
const [loading, setLoading] = useState(false);
const { t } = useTranslation();
const [updateJob] = useMutation(UPDATE_JOB);
const handleSave = async () => {
logImEXEvent("jobs_close_save");
setLoading(true);
const result = await updateJob({
variables: {
jobId: jobId,
job: {
date_invoiced: new Date(),
status: bodyshop.md_ro_statuses.default_invoiced || "Invoiced*",
invoice_allocation: {
labMatAllocations,
partsAllocations,
},
},
},
});
if (!!!result.errors) {
notification["success"]({ message: t("jobs.successes.invoiced") });
setInvoicedState(true);
} else {
notification["error"]({
message: t("jobs.errors.invoicing", {
error: JSON.stringify(result.errors),
}),
});
}
setLoading(false);
};
return (
<Button
onClick={handleSave}
type="primary"
disabled={suspenseAmount > 0 || disabled}
loading={loading}
>
{t("general.actions.save")}
</Button>
);
}
export default connect(mapStateToProps, null)(JobsCloseSaveButton);

View File

@@ -3,14 +3,11 @@ import React from "react";
import { useTranslation } from "react-i18next";
import Dinero from "dinero.js";
export default function JobsCloseTotals({
jobTotals,
labMatTotal,
partsTotal,
}) {
export default function JobsCloseTotals({ jobTotals }) {
const { t } = useTranslation();
return (
<div>
------Should be removed-----
<Descriptions
bordered
size="small"
@@ -58,22 +55,6 @@ export default function JobsCloseTotals({
title={t("jobs.labels.net_repairs")}
value={Dinero(jobTotals.totals.net_repairs).toFormat()}
/>
<Statistic
title={t("jobs.labels.suspense")}
valueStyle={{
color:
Dinero(jobTotals.totals.subtotal)
.subtract(labMatTotal)
.subtract(partsTotal)
.getAmount() === 0
? "green"
: "red",
}}
value={Dinero(jobTotals.totals.subtotal)
.subtract(labMatTotal)
.subtract(partsTotal)
.toFormat()}
/>
</div>
);
}

View File

@@ -141,14 +141,18 @@ export function JobsDetailHeaderActions({
>
{t("jobs.actions.postInvoices")}
</Menu.Item>
<Menu.Item key="closejob">
<Link
to={{
pathname: `/manage/jobs/${job.id}/close`,
}}
>
{t("menus.jobsactions.closejob")}
</Link>
<Menu.Item disabled={!!job.date_invoiced} key="closejob">
{job.date_invoiced ? (
t("menus.jobsactions.closejob")
) : (
<Link
to={{
pathname: `/manage/jobs/${job.id}/close`,
}}
>
{t("menus.jobsactions.closejob")}
</Link>
)}
</Menu.Item>
<JobsDetaiLheaderCsi job={job} />
<Menu.Item

View File

@@ -17,6 +17,7 @@ export default function LayoutFormRow({ header, children, grow = false }) {
const rowGutter = { gutter: [16, 16] };
const colSpan = (spanOverride) => {
if (spanOverride) return { span: spanOverride };
return {
xs: {
span: 24,
@@ -52,40 +53,3 @@ export default function LayoutFormRow({ header, children, grow = false }) {
</div>
);
}
// export default function LayoutFormRow({ header, children }) {
// if (!!!children.length) {
// //We have only one element. It's going to get the whole thing.
// return children;
// }
// const rowGutter = { gutter: [16, 16] };
// const colSpan = (maxspan) => {
// return {
// xs: {
// span: 24,
// },
// md: {
// span: !!maxspan ? Math.min(12, maxspan) : 12,
// },
// lg: {
// span: !!maxspan
// ? Math.min(Math.max(24 / children.length, 6), maxspan)
// : Math.max(24 / children.length, 6),
// },
// };
// };
// return (
// <div className='imex-form-row'>
// {header ? <Typography.Title level={4}>{header}</Typography.Title> : null}
// <Row {...rowGutter}>
// {children.map((c, idx) => (
// <Col key={idx} {...colSpan(c.props && c.props.maxspan)}>
// {c}
// </Col>
// ))}
// </Row>
// </div>
// );
// }

View File

@@ -149,3 +149,30 @@ export const GET_JOB_LINES_TO_ENTER_INVOICE = gql`
// act_price: {
// _gt: "0";
// }
export const generateJobLinesUpdatesForInvoicing = (joblines) => {
console.log("generateJobLinesUpdatesForInvoicing -> joblines", joblines);
const updates = joblines.reduce((acc, jl, idx) => {
return (
acc +
`a${idx}:update_joblines(where: {id: {_eq: "${
jl.id
}"}}, _set: {profitcenter_labor: "${
jl.profitcenter_labor || ""
}", profitcenter_part: "${jl.profitcenter_part || ""}"}) {
returning {
line_desc
profitcenter_part
profitcenter_labor
id
}
}`
);
}, "");
return gql`
mutation UPDATE_JOBLINES_FOR_INVOICING{
${updates}
}
`;
};

View File

@@ -1062,20 +1062,8 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
jobs_by_pk(id: $id) {
ro_number
clm_total
inproduction
plate_no
v_vin
v_model_yr
v_model_desc
v_make_desc
v_color
invoice_allocation
ins_co_id
policy_no
clm_no
ins_co_nm
regie_number
id
ded_amt
ded_status
@@ -1096,16 +1084,6 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
tax_levies_rt
parts_tax_rates
job_totals
ownr_fn
ownr_ln
ownr_ea
ownr_addr1
ownr_addr2
ownr_city
ownr_st
ownr_zip
ownr_ctry
ownr_ph1
rate_la1
rate_la2
rate_la3
@@ -1130,10 +1108,10 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
rate_mash
rate_matd
status
owner_owing
date_exported
joblines {
id
removed
tax_part
line_desc
prt_dsmk_p
@@ -1149,6 +1127,8 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
lbr_op
lbr_amt
op_code_desc
profitcenter_labor
profitcenter_part
}
}
}

View File

@@ -1,146 +1,102 @@
import React, { useState } from "react";
import { Button, Form, Space, notification, Popconfirm } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import JobsCloseLaborMaterialAllocation from "../../components/jobs-close-labmat-allocation/jobs-close-labmat-allocation.component";
import { selectBodyshop } from "../../redux/user/user.selectors";
import JobsClosePartsAllocation from "../../components/jobs-close-parts-allocation/jobs-close-parts-allocation.component";
import Dinero from "dinero.js";
import JobsCloseTotals from "../../components/jobs-close-totals/jobs-close-totals.component";
import JobsCloseAutoAllocate from "../../components/jobs-close-auto-allocate/jobs-close-auto-allocate.component";
import JobsCloseSaveButton from "../../components/jobs-close-save-button/jobs-close-save-button.component";
import JobsCloseExportButton from "../../components/jobs-close-export-button/jobs-close-export-button.component";
import FormsFieldChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
import JobsScoreboardAdd from "../../components/job-scoreboard-add-button/job-scoreboard-add-button.component";
import JobsCloseAutoAllocate from "../../components/jobs-close-auto-allocate/jobs-close-auto-allocate.component";
import JobsCloseExportButton from "../../components/jobs-close-export-button/jobs-close-export-button.component";
import JobsCloseLines from "../../components/jobs-close-lines/jobs-close-lines.component";
import JobsCloseTotals from "../../components/jobs-close-totals/jobs-close-totals.component";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useApolloClient, useMutation } from "react-apollo";
import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.queries";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { useHistory } from "react-router-dom";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
export function JobsCloseComponent({ job, bodyshop, jobTotals }) {
const [invoiced, setInvoiced] = useState(!!job.invoice_allocation);
const [labmatAllocations, setLabmatAllocations] = useState(
!!job.invoice_allocation && !!job.invoice_allocation.labMatAllocations
? Object.keys(job.invoice_allocation.labMatAllocations).reduce(
(acc, val) => {
if (val.includes("subtotal")) {
acc[val] = Dinero(job.invoice_allocation.labMatAllocations[val]);
} else {
acc[val] = {
...job.invoice_allocation.labMatAllocations[val],
total: Dinero(
job.invoice_allocation.labMatAllocations[val].total
),
allocations: job.invoice_allocation.labMatAllocations[
val
].allocations.map((item) => {
return { ...item, amount: Dinero(item.amount) };
}),
};
}
export function JobsCloseComponent({ job, bodyshop }) {
const { t } = useTranslation();
const [form] = Form.useForm();
const client = useApolloClient();
const history = useHistory();
const [closeJob] = useMutation(UPDATE_JOB);
// useEffect(() => {
// //if (job && form) form.setFields({ joblines: job.joblines });
// }, [job, form]);
return acc;
},
{}
)
: Object.keys(jobTotals.rates).reduce((acc, val) => {
acc[val] = jobTotals.rates[val];
if (val.includes("subtotal")) return acc;
//Not a subtotal - therefore can be allocated.
acc[val].allocations = [];
return acc;
}, {})
);
const handleFinish = async (values) => {
console.log(values);
const [partsAllocations, setPartsAllocations] = useState(
!!job.invoice_allocation && !!job.invoice_allocation.partsAllocations
? Object.keys(job.invoice_allocation.partsAllocations).reduce(
(acc, val) => {
acc[val] = {
...job.invoice_allocation.partsAllocations[val],
total: Dinero(job.invoice_allocation.partsAllocations[val].total),
allocations: job.invoice_allocation.partsAllocations[
val
].allocations.map((item) => {
return { ...item, amount: Dinero(item.amount) };
}),
};
return acc;
},
{}
)
: {
...Object.keys(jobTotals.parts.parts.list).reduce((acc, val) => {
acc[val] = { ...jobTotals.parts.parts.list[val], allocations: [] };
const result = await client.mutate({
mutation: generateJobLinesUpdatesForInvoicing(values.joblines),
});
console.log("result.data", result.data);
form.resetFields();
form.resetFields();
};
return acc;
}, {}),
pas: {
...jobTotals.parts.sublets,
allocations: [],
},
}
);
const handleClose = async () => {
const result = await closeJob({
variables: {
jobId: job.id,
job: {
status: bodyshop.md_ro_statuses.default_invoiced || "",
date_invoiced: new Date(),
},
},
});
const labmatAllocatedTotalsArray = Object.keys(labmatAllocations)
.filter((i) => !i.includes("subtotal"))
.map((i) => labmatAllocations[i].allocations)
.flat();
const labmatAllocatedTotal = Dinero({
amount: labmatAllocatedTotalsArray.reduce((acc, val) => {
return (acc = acc + Dinero(val.amount).getAmount());
}, 0),
});
const partsAllocatedTotalsArray = Object.keys(partsAllocations)
.map((i) => partsAllocations[i].allocations)
.flat();
const partsAllocatedTotal = Dinero({
amount: partsAllocatedTotalsArray.reduce((acc, val) => {
return (acc = acc + Dinero(val.amount).getAmount());
}, 0),
});
if (!!!result.errors) {
notification["success"]({ message: t("job.successes.closed") });
history.push(`/manage/jobs/${job.id}`);
} else {
notification["error"]({
message: t("job.errors.closing", {
error: JSON.stringify(result.errors),
}),
});
}
};
return (
<div>
<JobsCloseSaveButton
jobId={job.id}
invoiced={invoiced}
setInvoicedState={setInvoiced}
partsAllocations={partsAllocations}
labMatAllocations={labmatAllocations}
disabled={!!job.date_exported}
suspenseAmount={Dinero(jobTotals.totals.subtotal)
.subtract(labmatAllocatedTotal)
.subtract(partsAllocatedTotal)
.getAmount()}
/>
<JobsScoreboardAdd job={job} disabled={!invoiced} />
<JobsCloseExportButton jobId={job.id} disabled={!invoiced} />
<JobsCloseTotals
jobTotals={jobTotals}
labMatTotal={labmatAllocatedTotal}
partsTotal={partsAllocatedTotal}
/>
<JobsCloseAutoAllocate
labmatAllocations={labmatAllocations}
setLabmatAllocations={setLabmatAllocations}
partsAllocations={partsAllocations}
setPartsAllocations={setPartsAllocations}
disabled={!!job.date_exported}
/>
<JobsCloseLaborMaterialAllocation
labmatAllocations={labmatAllocations}
setLabmatAllocations={setLabmatAllocations}
labMatTotalAllocation={labmatAllocatedTotal}
invoiced={!!job.date_exported}
/>
<JobsClosePartsAllocation
partsAllocations={partsAllocations}
setPartsAllocations={setPartsAllocations}
partsAllocatedTotal={partsAllocatedTotal}
invoiced={!!job.date_exported}
/>
<Form
layout="vertical"
form={form}
onFinish={handleFinish}
initialValues={{ joblines: job.joblines }}
>
<Space>
<JobsCloseAutoAllocate
joblines={job.joblines}
form={form}
disabled={!!job.date_exported}
/>
<Button onClick={() => form.submit()}>
{t("general.actions.save")}
</Button>
<Popconfirm
onConfirm={handleClose}
okText={t("general.labels.yes")}
cancelText={t("general.labels.no")}
title={t("jobs.labels.closeconfirm")}
>
<Button type="danger">{t("general.actions.close")}</Button>
</Popconfirm>
<JobsScoreboardAdd job={job} disabled={false} />
<JobsCloseExportButton jobId={job.id} disabled={false} />
</Space>
<FormsFieldChanged form={form} />
<JobsCloseTotals jobTotals={job.job_totals} form={form} />
<JobsCloseLines joblines={job.joblines} />
</Form>
</div>
);
}

View File

@@ -61,13 +61,11 @@ export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader }) {
extra={<JobCalculateTotals job={data.jobs_by_pk} />}
/>
);
return (
<RbacWrapper action="jobs:close">
<div>
<JobsCloseComponent
job={data ? data.jobs_by_pk : {}}
jobTotals={data.jobs_by_pk.job_totals}
/>
<JobsCloseComponent job={data ? data.jobs_by_pk : {}} />
</div>
</RbacWrapper>
);

View File

@@ -725,6 +725,8 @@
"PAS": "Sublet",
"PASL": "Sublet"
},
"profitcenter_labor": "Profit Center: Labor",
"profitcenter_part": "Profit Center: Part",
"status": "Status",
"total": "Total",
"unq_seq": "Seq #"
@@ -772,6 +774,7 @@
},
"errors": {
"addingtoproduction": "Error adding to production. {{error}}",
"closing": "Error closing job. {{error}}",
"creating": "Error encountered while creating job. {{error}}",
"deleted": "Error deleting job.",
"exporting": "Error exporting job. {{error}}",
@@ -963,6 +966,7 @@
"vehicle": "Vehicle"
},
"checklists": "Checklists",
"closeconfirm": "Are you sure you want to close this job? This cannot be easily undone.",
"cost": "Cost",
"cost_labor": "Cost - Labor",
"cost_parts": "Cost - Parts",
@@ -1024,6 +1028,7 @@
"successes": {
"addedtoproduction": "Job added to production board.",
"all_deleted": "{{count}} jobs deleted successfully.",
"closed": "Job closed successfully.",
"converted": "Job converted successfully.",
"created": "Job created successfully. Click to view.",
"created_subtitle": "Estimate Number {{est_number}} has been created.",

View File

@@ -725,6 +725,8 @@
"PAS": "",
"PASL": ""
},
"profitcenter_labor": "",
"profitcenter_part": "",
"status": "Estado",
"total": "",
"unq_seq": "Seq #"
@@ -772,6 +774,7 @@
},
"errors": {
"addingtoproduction": "",
"closing": "",
"creating": "",
"deleted": "Error al eliminar el trabajo.",
"exporting": "",
@@ -963,6 +966,7 @@
"vehicle": "Vehículo"
},
"checklists": "",
"closeconfirm": "",
"cost": "",
"cost_labor": "",
"cost_parts": "",
@@ -1024,6 +1028,7 @@
"successes": {
"addedtoproduction": "",
"all_deleted": "{{count}} trabajos eliminados con éxito.",
"closed": "",
"converted": "Trabajo convertido con éxito.",
"created": "Trabajo creado con éxito. Click para ver.",
"created_subtitle": "",

View File

@@ -725,6 +725,8 @@
"PAS": "",
"PASL": ""
},
"profitcenter_labor": "",
"profitcenter_part": "",
"status": "Statut",
"total": "",
"unq_seq": "Seq #"
@@ -772,6 +774,7 @@
},
"errors": {
"addingtoproduction": "",
"closing": "",
"creating": "",
"deleted": "Erreur lors de la suppression du travail.",
"exporting": "",
@@ -963,6 +966,7 @@
"vehicle": "Véhicule"
},
"checklists": "",
"closeconfirm": "",
"cost": "",
"cost_labor": "",
"cost_parts": "",
@@ -1024,6 +1028,7 @@
"successes": {
"addedtoproduction": "",
"all_deleted": "{{count}} travaux supprimés avec succès.",
"closed": "",
"converted": "Travail converti avec succès.",
"created": "Le travail a été créé avec succès. Clique pour voir.",
"created_subtitle": "",

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."joblines" DROP COLUMN "profitcenter_labor";
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."joblines" ADD COLUMN "profitcenter_labor" text NULL;
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."joblines" DROP COLUMN "profitcenter_part";
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."joblines" ADD COLUMN "profitcenter_part" text NULL;
type: run_sql

View File

@@ -0,0 +1,77 @@
- args:
role: user
table:
name: joblines
schema: public
type: drop_insert_permission
- args:
permission:
check:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- act_price
- alt_co_id
- alt_overrd
- alt_part_i
- alt_partm
- alt_partno
- bett_amt
- bett_pctg
- bett_tax
- bett_type
- cert_part
- created_at
- db_hrs
- db_price
- db_ref
- est_seq
- glass_flag
- id
- jobid
- lbr_amt
- lbr_hrs_j
- lbr_inc
- lbr_op
- lbr_op_j
- lbr_tax
- lbr_typ_j
- line_desc
- line_ind
- line_no
- line_ref
- location
- misc_amt
- misc_sublt
- misc_tax
- mod_lb_hrs
- mod_lbr_ty
- notes
- oem_partno
- op_code_desc
- paint_stg
- paint_tone
- part_qty
- part_type
- price_inc
- price_j
- prt_dsmk_m
- prt_dsmk_p
- removed
- status
- tax_part
- unq_seq
- updated_at
set: {}
role: user
table:
name: joblines
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,79 @@
- args:
role: user
table:
name: joblines
schema: public
type: drop_insert_permission
- args:
permission:
check:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- act_price
- alt_co_id
- alt_overrd
- alt_part_i
- alt_partm
- alt_partno
- bett_amt
- bett_pctg
- bett_tax
- bett_type
- cert_part
- created_at
- db_hrs
- db_price
- db_ref
- est_seq
- glass_flag
- id
- jobid
- lbr_amt
- lbr_hrs_j
- lbr_inc
- lbr_op
- lbr_op_j
- lbr_tax
- lbr_typ_j
- line_desc
- line_ind
- line_no
- line_ref
- location
- misc_amt
- misc_sublt
- misc_tax
- mod_lb_hrs
- mod_lbr_ty
- notes
- oem_partno
- op_code_desc
- paint_stg
- paint_tone
- part_qty
- part_type
- price_inc
- price_j
- profitcenter_labor
- profitcenter_part
- prt_dsmk_m
- prt_dsmk_p
- removed
- status
- tax_part
- unq_seq
- updated_at
set: {}
role: user
table:
name: joblines
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,78 @@
- args:
role: user
table:
name: joblines
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: true
columns:
- act_price
- alt_co_id
- alt_overrd
- alt_part_i
- alt_partm
- alt_partno
- bett_amt
- bett_pctg
- bett_tax
- bett_type
- cert_part
- created_at
- db_hrs
- db_price
- db_ref
- est_seq
- glass_flag
- id
- jobid
- lbr_amt
- lbr_hrs_j
- lbr_inc
- lbr_op
- lbr_op_j
- lbr_tax
- lbr_typ_j
- line_desc
- line_ind
- line_no
- line_ref
- location
- misc_amt
- misc_sublt
- misc_tax
- mod_lb_hrs
- mod_lbr_ty
- notes
- oem_partno
- op_code_desc
- paint_stg
- paint_tone
- part_qty
- part_type
- price_inc
- price_j
- prt_dsmk_m
- prt_dsmk_p
- removed
- status
- tax_part
- unq_seq
- updated_at
computed_fields: []
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: joblines
schema: public
type: create_select_permission

View File

@@ -0,0 +1,80 @@
- args:
role: user
table:
name: joblines
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: true
columns:
- act_price
- alt_co_id
- alt_overrd
- alt_part_i
- alt_partm
- alt_partno
- bett_amt
- bett_pctg
- bett_tax
- bett_type
- cert_part
- created_at
- db_hrs
- db_price
- db_ref
- est_seq
- glass_flag
- id
- jobid
- lbr_amt
- lbr_hrs_j
- lbr_inc
- lbr_op
- lbr_op_j
- lbr_tax
- lbr_typ_j
- line_desc
- line_ind
- line_no
- line_ref
- location
- misc_amt
- misc_sublt
- misc_tax
- mod_lb_hrs
- mod_lbr_ty
- notes
- oem_partno
- op_code_desc
- paint_stg
- paint_tone
- part_qty
- part_type
- price_inc
- price_j
- profitcenter_labor
- profitcenter_part
- prt_dsmk_m
- prt_dsmk_p
- removed
- status
- tax_part
- unq_seq
- updated_at
computed_fields: []
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: joblines
schema: public
type: create_select_permission

View File

@@ -0,0 +1,77 @@
- args:
role: user
table:
name: joblines
schema: public
type: drop_update_permission
- args:
permission:
columns:
- act_price
- alt_co_id
- alt_overrd
- alt_part_i
- alt_partm
- alt_partno
- bett_amt
- bett_pctg
- bett_tax
- bett_type
- cert_part
- created_at
- db_hrs
- db_price
- db_ref
- est_seq
- glass_flag
- id
- jobid
- lbr_amt
- lbr_hrs_j
- lbr_inc
- lbr_op
- lbr_op_j
- lbr_tax
- lbr_typ_j
- line_desc
- line_ind
- line_no
- line_ref
- location
- misc_amt
- misc_sublt
- misc_tax
- mod_lb_hrs
- mod_lbr_ty
- notes
- oem_partno
- op_code_desc
- paint_stg
- paint_tone
- part_qty
- part_type
- price_inc
- price_j
- prt_dsmk_m
- prt_dsmk_p
- removed
- status
- tax_part
- unq_seq
- updated_at
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: joblines
schema: public
type: create_update_permission

View File

@@ -0,0 +1,79 @@
- args:
role: user
table:
name: joblines
schema: public
type: drop_update_permission
- args:
permission:
columns:
- act_price
- alt_co_id
- alt_overrd
- alt_part_i
- alt_partm
- alt_partno
- bett_amt
- bett_pctg
- bett_tax
- bett_type
- cert_part
- created_at
- db_hrs
- db_price
- db_ref
- est_seq
- glass_flag
- id
- jobid
- lbr_amt
- lbr_hrs_j
- lbr_inc
- lbr_op
- lbr_op_j
- lbr_tax
- lbr_typ_j
- line_desc
- line_ind
- line_no
- line_ref
- location
- misc_amt
- misc_sublt
- misc_tax
- mod_lb_hrs
- mod_lbr_ty
- notes
- oem_partno
- op_code_desc
- paint_stg
- paint_tone
- part_qty
- part_type
- price_inc
- price_j
- profitcenter_labor
- profitcenter_part
- prt_dsmk_m
- prt_dsmk_p
- removed
- status
- tax_part
- unq_seq
- updated_at
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: joblines
schema: public
type: create_update_permission

View File

@@ -1763,6 +1763,8 @@ tables:
- part_type
- price_inc
- price_j
- profitcenter_labor
- profitcenter_part
- prt_dsmk_m
- prt_dsmk_p
- removed
@@ -1819,6 +1821,8 @@ tables:
- part_type
- price_inc
- price_j
- profitcenter_labor
- profitcenter_part
- prt_dsmk_m
- prt_dsmk_p
- removed
@@ -1886,6 +1890,8 @@ tables:
- part_type
- price_inc
- price_j
- profitcenter_labor
- profitcenter_part
- prt_dsmk_m
- prt_dsmk_p
- removed

View File

@@ -26,19 +26,20 @@ function CalculateAdditional(job) {
return job.joblines
.filter(
(jl) =>
jl.lbr_op === "OP2" ||
jl.lbr_op === "OP3" ||
jl.lbr_op === "OP4" ||
jl.lbr_op === "OP5" ||
jl.lbr_op === "OP6" ||
jl.lbr_op === "OP7" ||
jl.lbr_op === "OP8" ||
jl.lbr_op === "OP9" ||
jl.lbr_op === "OP10" ||
jl.lbr_op === "OP13" ||
jl.lbr_op === "OP13" ||
jl.lbr_op === "OP14" ||
jl.lbr_op === "OP15"
!jl.removed &&
(jl.lbr_op === "OP2" ||
jl.lbr_op === "OP3" ||
jl.lbr_op === "OP4" ||
jl.lbr_op === "OP5" ||
jl.lbr_op === "OP6" ||
jl.lbr_op === "OP7" ||
jl.lbr_op === "OP8" ||
jl.lbr_op === "OP9" ||
jl.lbr_op === "OP10" ||
jl.lbr_op === "OP13" ||
jl.lbr_op === "OP13" ||
jl.lbr_op === "OP14" ||
jl.lbr_op === "OP15")
)
.reduce((acc, val) => {
return acc.add(
@@ -58,25 +59,27 @@ function CalculateTaxesTotals(job, otherTotals) {
.add(Dinero({ amount: (job.storage_payable || 0) * 100 }));
//TODO Levies should be included??
const statePartsTax = job.joblines.reduce((acc, val) => {
if (!!!val.tax_part) return acc;
// if (!!job.parts_tax_rates[val.part_type]) {
// console.log("val.line_desc", val.line_desc);
const statePartsTax = job.joblines
.filter((jl) => !jl.removed)
.reduce((acc, val) => {
if (!!!val.tax_part) return acc;
// if (!!job.parts_tax_rates[val.part_type]) {
// console.log("val.line_desc", val.line_desc);
return acc.add(
Dinero({ amount: Math.round(val.act_price * 100) })
.multiply(val.part_qty)
.percentage(
(!val.part_type && val.lbr_op === "OP13"
? job.parts_tax_rates["PAN"].prt_tax_rt
: job.parts_tax_rates[val.part_type] &&
job.parts_tax_rates[val.part_type].prt_tax_rt) * 100
)
);
// } else {
// return acc;
// }
}, Dinero({ amount: 0 }));
return acc.add(
Dinero({ amount: Math.round(val.act_price * 100) })
.multiply(val.part_qty)
.percentage(
(!val.part_type && val.lbr_op === "OP13"
? job.parts_tax_rates["PAN"].prt_tax_rt
: job.parts_tax_rates[val.part_type] &&
job.parts_tax_rates[val.part_type].prt_tax_rt) * 100
)
);
// } else {
// return acc;
// }
}, Dinero({ amount: 0 }));
let ret = {
subtotal: subtotal,
@@ -119,7 +122,7 @@ function CalculateTaxesTotals(job, otherTotals) {
}
function CalculateRatesTotals(ratesList, shoprates) {
const jobLines = ratesList.joblines;
const jobLines = ratesList.joblines.filter((jl) => !jl.removed);
let ret = {
la1: {
@@ -242,72 +245,74 @@ function CalculateRatesTotals(ratesList, shoprates) {
}
function CalculatePartsTotals(jobLines) {
const ret = jobLines.reduce(
(acc, value) => {
switch (value.part_type) {
case "PAS":
case "PASL":
return {
...acc,
sublets: {
...acc.sublets,
subtotal: acc.sublets.subtotal.add(
Dinero({ amount: Math.round(value.act_price * 100) })
),
//TODO Add Adjustments in
},
};
default:
if (!value.part_type) return acc;
return {
...acc,
parts: {
...acc.parts,
list: {
...acc.parts.list,
[value.part_type]:
acc.parts.list[value.part_type] &&
acc.parts.list[value.part_type].total
? {
total: acc.parts.list[value.part_type].total.add(
Dinero({
amount: Math.round((value.act_price || 0) * 100),
}).multiply(value.part_qty || 1)
),
}
: {
total: Dinero({
amount: Math.round((value.act_price || 0) * 100),
}).multiply(value.part_qty || 1),
},
const ret = jobLines
.filter((jl) => !jl.removed)
.reduce(
(acc, value) => {
switch (value.part_type) {
case "PAS":
case "PASL":
return {
...acc,
sublets: {
...acc.sublets,
subtotal: acc.sublets.subtotal.add(
Dinero({ amount: Math.round(value.act_price * 100) })
),
//TODO Add Adjustments in
},
subtotal: acc.parts.subtotal.add(
Dinero({ amount: Math.round(value.act_price * 100) }).multiply(
value.part_qty
)
),
//TODO Add Adjustments in
},
};
// default:
// return acc;
};
default:
if (!value.part_type) return acc;
return {
...acc,
parts: {
...acc.parts,
list: {
...acc.parts.list,
[value.part_type]:
acc.parts.list[value.part_type] &&
acc.parts.list[value.part_type].total
? {
total: acc.parts.list[value.part_type].total.add(
Dinero({
amount: Math.round((value.act_price || 0) * 100),
}).multiply(value.part_qty || 1)
),
}
: {
total: Dinero({
amount: Math.round((value.act_price || 0) * 100),
}).multiply(value.part_qty || 1),
},
},
subtotal: acc.parts.subtotal.add(
Dinero({
amount: Math.round(value.act_price * 100),
}).multiply(value.part_qty)
),
//TODO Add Adjustments in
},
};
// default:
// return acc;
}
},
{
parts: {
list: {},
subtotal: Dinero({ amount: 0 }),
adjustments: Dinero({ amount: 0 }),
total: Dinero({ amount: 0 }),
},
sublets: {
subtotal: Dinero({ amount: 0 }),
adjustments: Dinero({ amount: 0 }),
total: Dinero({ amount: 0 }),
},
}
},
{
parts: {
list: {},
subtotal: Dinero({ amount: 0 }),
adjustments: Dinero({ amount: 0 }),
total: Dinero({ amount: 0 }),
},
sublets: {
subtotal: Dinero({ amount: 0 }),
adjustments: Dinero({ amount: 0 }),
total: Dinero({ amount: 0 }),
},
}
);
);
return {
parts: {