Refactored job closing to be line based instead of totals based. BOD-383
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
// );
|
||||
// }
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
`;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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": "",
|
||||
|
||||
@@ -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": "",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."joblines" DROP COLUMN "profitcenter_labor";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."joblines" ADD COLUMN "profitcenter_labor" text NULL;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."joblines" DROP COLUMN "profitcenter_part";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."joblines" ADD COLUMN "profitcenter_part" text NULL;
|
||||
type: run_sql
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user