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
|
BabelEdit project file
|
||||||
@@ -11026,6 +11026,48 @@
|
|||||||
</concept_node>
|
</concept_node>
|
||||||
</children>
|
</children>
|
||||||
</folder_node>
|
</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>
|
<concept_node>
|
||||||
<name>status</name>
|
<name>status</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -11791,6 +11833,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</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>
|
<concept_node>
|
||||||
<name>creating</name>
|
<name>creating</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -15617,6 +15680,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</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>
|
<concept_node>
|
||||||
<name>cost</name>
|
<name>cost</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -16787,6 +16871,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</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>
|
<concept_node>
|
||||||
<name>converted</name>
|
<name>converted</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<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 { 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 { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import Dinero from "dinero.js";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobsCloseAutoAllocate({
|
export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
|
||||||
bodyshop,
|
|
||||||
labmatAllocations,
|
|
||||||
setLabmatAllocations,
|
|
||||||
partsAllocations,
|
|
||||||
setPartsAllocations,
|
|
||||||
|
|
||||||
disabled,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const handleAllocate = () => {
|
const handleAllocate = () => {
|
||||||
logImEXEvent("jobs_close_allocate_auto");
|
logImEXEvent("jobs_close_allocate_auto");
|
||||||
|
|
||||||
const { defaults } = bodyshop.md_responsibility_centers;
|
const { defaults } = bodyshop.md_responsibility_centers;
|
||||||
|
|
||||||
Object.keys(labmatAllocations).forEach((i) => {
|
form.setFieldsValue({
|
||||||
const defaultProfitCenter = defaults.profits[i.toUpperCase()];
|
joblines: joblines.map((jl) => {
|
||||||
|
const ret = jl;
|
||||||
if (
|
if (jl.part_type) {
|
||||||
!!defaultProfitCenter &&
|
ret.profitcenter_part = defaults.profits[jl.part_type.toUpperCase()];
|
||||||
Dinero(labmatAllocations[i].total).getAmount() > 0
|
} else {
|
||||||
) {
|
ret.profitcenter_part = null;
|
||||||
setLabmatAllocations((st) => {
|
}
|
||||||
return {
|
if (jl.mod_lbr_ty) {
|
||||||
...st,
|
ret.profitcenter_labor =
|
||||||
[i]: {
|
defaults.profits[jl.mod_lbr_ty.toUpperCase()];
|
||||||
...labmatAllocations[i],
|
} else {
|
||||||
allocations: [
|
ret.profitcenter_labor = null;
|
||||||
{
|
}
|
||||||
center: defaultProfitCenter,
|
return ret;
|
||||||
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,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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 { useTranslation } from "react-i18next";
|
||||||
import Dinero from "dinero.js";
|
import Dinero from "dinero.js";
|
||||||
|
|
||||||
export default function JobsCloseTotals({
|
export default function JobsCloseTotals({ jobTotals }) {
|
||||||
jobTotals,
|
|
||||||
labMatTotal,
|
|
||||||
partsTotal,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
------Should be removed-----
|
||||||
<Descriptions
|
<Descriptions
|
||||||
bordered
|
bordered
|
||||||
size="small"
|
size="small"
|
||||||
@@ -58,22 +55,6 @@ export default function JobsCloseTotals({
|
|||||||
title={t("jobs.labels.net_repairs")}
|
title={t("jobs.labels.net_repairs")}
|
||||||
value={Dinero(jobTotals.totals.net_repairs).toFormat()}
|
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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,14 +141,18 @@ export function JobsDetailHeaderActions({
|
|||||||
>
|
>
|
||||||
{t("jobs.actions.postInvoices")}
|
{t("jobs.actions.postInvoices")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="closejob">
|
<Menu.Item disabled={!!job.date_invoiced} key="closejob">
|
||||||
<Link
|
{job.date_invoiced ? (
|
||||||
to={{
|
t("menus.jobsactions.closejob")
|
||||||
pathname: `/manage/jobs/${job.id}/close`,
|
) : (
|
||||||
}}
|
<Link
|
||||||
>
|
to={{
|
||||||
{t("menus.jobsactions.closejob")}
|
pathname: `/manage/jobs/${job.id}/close`,
|
||||||
</Link>
|
}}
|
||||||
|
>
|
||||||
|
{t("menus.jobsactions.closejob")}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<JobsDetaiLheaderCsi job={job} />
|
<JobsDetaiLheaderCsi job={job} />
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export default function LayoutFormRow({ header, children, grow = false }) {
|
|||||||
const rowGutter = { gutter: [16, 16] };
|
const rowGutter = { gutter: [16, 16] };
|
||||||
|
|
||||||
const colSpan = (spanOverride) => {
|
const colSpan = (spanOverride) => {
|
||||||
|
if (spanOverride) return { span: spanOverride };
|
||||||
return {
|
return {
|
||||||
xs: {
|
xs: {
|
||||||
span: 24,
|
span: 24,
|
||||||
@@ -52,40 +53,3 @@ export default function LayoutFormRow({ header, children, grow = false }) {
|
|||||||
</div>
|
</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: {
|
// act_price: {
|
||||||
// _gt: "0";
|
// _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!) {
|
query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
|
||||||
jobs_by_pk(id: $id) {
|
jobs_by_pk(id: $id) {
|
||||||
ro_number
|
ro_number
|
||||||
clm_total
|
|
||||||
inproduction
|
|
||||||
plate_no
|
|
||||||
v_vin
|
|
||||||
v_model_yr
|
|
||||||
v_model_desc
|
|
||||||
v_make_desc
|
|
||||||
v_color
|
|
||||||
invoice_allocation
|
invoice_allocation
|
||||||
ins_co_id
|
ins_co_id
|
||||||
policy_no
|
|
||||||
clm_no
|
|
||||||
ins_co_nm
|
|
||||||
regie_number
|
|
||||||
id
|
id
|
||||||
ded_amt
|
ded_amt
|
||||||
ded_status
|
ded_status
|
||||||
@@ -1096,16 +1084,6 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
|
|||||||
tax_levies_rt
|
tax_levies_rt
|
||||||
parts_tax_rates
|
parts_tax_rates
|
||||||
job_totals
|
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_la1
|
||||||
rate_la2
|
rate_la2
|
||||||
rate_la3
|
rate_la3
|
||||||
@@ -1130,10 +1108,10 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
|
|||||||
rate_mash
|
rate_mash
|
||||||
rate_matd
|
rate_matd
|
||||||
status
|
status
|
||||||
owner_owing
|
|
||||||
date_exported
|
date_exported
|
||||||
joblines {
|
joblines {
|
||||||
id
|
id
|
||||||
|
removed
|
||||||
tax_part
|
tax_part
|
||||||
line_desc
|
line_desc
|
||||||
prt_dsmk_p
|
prt_dsmk_p
|
||||||
@@ -1149,6 +1127,8 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
|
|||||||
lbr_op
|
lbr_op
|
||||||
lbr_amt
|
lbr_amt
|
||||||
op_code_desc
|
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 { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import JobsCloseLaborMaterialAllocation from "../../components/jobs-close-labmat-allocation/jobs-close-labmat-allocation.component";
|
import FormsFieldChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.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 JobsScoreboardAdd from "../../components/job-scoreboard-add-button/job-scoreboard-add-button.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({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobsCloseComponent({ job, bodyshop, jobTotals }) {
|
export function JobsCloseComponent({ job, bodyshop }) {
|
||||||
const [invoiced, setInvoiced] = useState(!!job.invoice_allocation);
|
const { t } = useTranslation();
|
||||||
const [labmatAllocations, setLabmatAllocations] = useState(
|
const [form] = Form.useForm();
|
||||||
!!job.invoice_allocation && !!job.invoice_allocation.labMatAllocations
|
const client = useApolloClient();
|
||||||
? Object.keys(job.invoice_allocation.labMatAllocations).reduce(
|
const history = useHistory();
|
||||||
(acc, val) => {
|
const [closeJob] = useMutation(UPDATE_JOB);
|
||||||
if (val.includes("subtotal")) {
|
// useEffect(() => {
|
||||||
acc[val] = Dinero(job.invoice_allocation.labMatAllocations[val]);
|
// //if (job && form) form.setFields({ joblines: job.joblines });
|
||||||
} else {
|
// }, [job, form]);
|
||||||
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) };
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
const handleFinish = async (values) => {
|
||||||
},
|
console.log(values);
|
||||||
{}
|
|
||||||
)
|
|
||||||
: 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 [partsAllocations, setPartsAllocations] = useState(
|
const result = await client.mutate({
|
||||||
!!job.invoice_allocation && !!job.invoice_allocation.partsAllocations
|
mutation: generateJobLinesUpdatesForInvoicing(values.joblines),
|
||||||
? Object.keys(job.invoice_allocation.partsAllocations).reduce(
|
});
|
||||||
(acc, val) => {
|
console.log("result.data", result.data);
|
||||||
acc[val] = {
|
form.resetFields();
|
||||||
...job.invoice_allocation.partsAllocations[val],
|
form.resetFields();
|
||||||
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: [] };
|
|
||||||
|
|
||||||
return acc;
|
const handleClose = async () => {
|
||||||
}, {}),
|
const result = await closeJob({
|
||||||
pas: {
|
variables: {
|
||||||
...jobTotals.parts.sublets,
|
jobId: job.id,
|
||||||
allocations: [],
|
job: {
|
||||||
},
|
status: bodyshop.md_ro_statuses.default_invoiced || "",
|
||||||
}
|
date_invoiced: new Date(),
|
||||||
);
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const labmatAllocatedTotalsArray = Object.keys(labmatAllocations)
|
if (!!!result.errors) {
|
||||||
.filter((i) => !i.includes("subtotal"))
|
notification["success"]({ message: t("job.successes.closed") });
|
||||||
.map((i) => labmatAllocations[i].allocations)
|
history.push(`/manage/jobs/${job.id}`);
|
||||||
.flat();
|
} else {
|
||||||
|
notification["error"]({
|
||||||
const labmatAllocatedTotal = Dinero({
|
message: t("job.errors.closing", {
|
||||||
amount: labmatAllocatedTotalsArray.reduce((acc, val) => {
|
error: JSON.stringify(result.errors),
|
||||||
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),
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<JobsCloseSaveButton
|
<Form
|
||||||
jobId={job.id}
|
layout="vertical"
|
||||||
invoiced={invoiced}
|
form={form}
|
||||||
setInvoicedState={setInvoiced}
|
onFinish={handleFinish}
|
||||||
partsAllocations={partsAllocations}
|
initialValues={{ joblines: job.joblines }}
|
||||||
labMatAllocations={labmatAllocations}
|
>
|
||||||
disabled={!!job.date_exported}
|
<Space>
|
||||||
suspenseAmount={Dinero(jobTotals.totals.subtotal)
|
<JobsCloseAutoAllocate
|
||||||
.subtract(labmatAllocatedTotal)
|
joblines={job.joblines}
|
||||||
.subtract(partsAllocatedTotal)
|
form={form}
|
||||||
.getAmount()}
|
disabled={!!job.date_exported}
|
||||||
/>
|
/>
|
||||||
<JobsScoreboardAdd job={job} disabled={!invoiced} />
|
|
||||||
<JobsCloseExportButton jobId={job.id} disabled={!invoiced} />
|
<Button onClick={() => form.submit()}>
|
||||||
<JobsCloseTotals
|
{t("general.actions.save")}
|
||||||
jobTotals={jobTotals}
|
</Button>
|
||||||
labMatTotal={labmatAllocatedTotal}
|
|
||||||
partsTotal={partsAllocatedTotal}
|
<Popconfirm
|
||||||
/>
|
onConfirm={handleClose}
|
||||||
<JobsCloseAutoAllocate
|
okText={t("general.labels.yes")}
|
||||||
labmatAllocations={labmatAllocations}
|
cancelText={t("general.labels.no")}
|
||||||
setLabmatAllocations={setLabmatAllocations}
|
title={t("jobs.labels.closeconfirm")}
|
||||||
partsAllocations={partsAllocations}
|
>
|
||||||
setPartsAllocations={setPartsAllocations}
|
<Button type="danger">{t("general.actions.close")}</Button>
|
||||||
disabled={!!job.date_exported}
|
</Popconfirm>
|
||||||
/>
|
|
||||||
<JobsCloseLaborMaterialAllocation
|
<JobsScoreboardAdd job={job} disabled={false} />
|
||||||
labmatAllocations={labmatAllocations}
|
<JobsCloseExportButton jobId={job.id} disabled={false} />
|
||||||
setLabmatAllocations={setLabmatAllocations}
|
</Space>
|
||||||
labMatTotalAllocation={labmatAllocatedTotal}
|
<FormsFieldChanged form={form} />
|
||||||
invoiced={!!job.date_exported}
|
<JobsCloseTotals jobTotals={job.job_totals} form={form} />
|
||||||
/>
|
<JobsCloseLines joblines={job.joblines} />
|
||||||
<JobsClosePartsAllocation
|
</Form>
|
||||||
partsAllocations={partsAllocations}
|
|
||||||
setPartsAllocations={setPartsAllocations}
|
|
||||||
partsAllocatedTotal={partsAllocatedTotal}
|
|
||||||
invoiced={!!job.date_exported}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,13 +61,11 @@ export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader }) {
|
|||||||
extra={<JobCalculateTotals job={data.jobs_by_pk} />}
|
extra={<JobCalculateTotals job={data.jobs_by_pk} />}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RbacWrapper action="jobs:close">
|
<RbacWrapper action="jobs:close">
|
||||||
<div>
|
<div>
|
||||||
<JobsCloseComponent
|
<JobsCloseComponent job={data ? data.jobs_by_pk : {}} />
|
||||||
job={data ? data.jobs_by_pk : {}}
|
|
||||||
jobTotals={data.jobs_by_pk.job_totals}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</RbacWrapper>
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -725,6 +725,8 @@
|
|||||||
"PAS": "Sublet",
|
"PAS": "Sublet",
|
||||||
"PASL": "Sublet"
|
"PASL": "Sublet"
|
||||||
},
|
},
|
||||||
|
"profitcenter_labor": "Profit Center: Labor",
|
||||||
|
"profitcenter_part": "Profit Center: Part",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
"unq_seq": "Seq #"
|
"unq_seq": "Seq #"
|
||||||
@@ -772,6 +774,7 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"addingtoproduction": "Error adding to production. {{error}}",
|
"addingtoproduction": "Error adding to production. {{error}}",
|
||||||
|
"closing": "Error closing job. {{error}}",
|
||||||
"creating": "Error encountered while creating job. {{error}}",
|
"creating": "Error encountered while creating job. {{error}}",
|
||||||
"deleted": "Error deleting job.",
|
"deleted": "Error deleting job.",
|
||||||
"exporting": "Error exporting job. {{error}}",
|
"exporting": "Error exporting job. {{error}}",
|
||||||
@@ -963,6 +966,7 @@
|
|||||||
"vehicle": "Vehicle"
|
"vehicle": "Vehicle"
|
||||||
},
|
},
|
||||||
"checklists": "Checklists",
|
"checklists": "Checklists",
|
||||||
|
"closeconfirm": "Are you sure you want to close this job? This cannot be easily undone.",
|
||||||
"cost": "Cost",
|
"cost": "Cost",
|
||||||
"cost_labor": "Cost - Labor",
|
"cost_labor": "Cost - Labor",
|
||||||
"cost_parts": "Cost - Parts",
|
"cost_parts": "Cost - Parts",
|
||||||
@@ -1024,6 +1028,7 @@
|
|||||||
"successes": {
|
"successes": {
|
||||||
"addedtoproduction": "Job added to production board.",
|
"addedtoproduction": "Job added to production board.",
|
||||||
"all_deleted": "{{count}} jobs deleted successfully.",
|
"all_deleted": "{{count}} jobs deleted successfully.",
|
||||||
|
"closed": "Job closed successfully.",
|
||||||
"converted": "Job converted successfully.",
|
"converted": "Job converted successfully.",
|
||||||
"created": "Job created successfully. Click to view.",
|
"created": "Job created successfully. Click to view.",
|
||||||
"created_subtitle": "Estimate Number {{est_number}} has been created.",
|
"created_subtitle": "Estimate Number {{est_number}} has been created.",
|
||||||
|
|||||||
@@ -725,6 +725,8 @@
|
|||||||
"PAS": "",
|
"PAS": "",
|
||||||
"PASL": ""
|
"PASL": ""
|
||||||
},
|
},
|
||||||
|
"profitcenter_labor": "",
|
||||||
|
"profitcenter_part": "",
|
||||||
"status": "Estado",
|
"status": "Estado",
|
||||||
"total": "",
|
"total": "",
|
||||||
"unq_seq": "Seq #"
|
"unq_seq": "Seq #"
|
||||||
@@ -772,6 +774,7 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"addingtoproduction": "",
|
"addingtoproduction": "",
|
||||||
|
"closing": "",
|
||||||
"creating": "",
|
"creating": "",
|
||||||
"deleted": "Error al eliminar el trabajo.",
|
"deleted": "Error al eliminar el trabajo.",
|
||||||
"exporting": "",
|
"exporting": "",
|
||||||
@@ -963,6 +966,7 @@
|
|||||||
"vehicle": "Vehículo"
|
"vehicle": "Vehículo"
|
||||||
},
|
},
|
||||||
"checklists": "",
|
"checklists": "",
|
||||||
|
"closeconfirm": "",
|
||||||
"cost": "",
|
"cost": "",
|
||||||
"cost_labor": "",
|
"cost_labor": "",
|
||||||
"cost_parts": "",
|
"cost_parts": "",
|
||||||
@@ -1024,6 +1028,7 @@
|
|||||||
"successes": {
|
"successes": {
|
||||||
"addedtoproduction": "",
|
"addedtoproduction": "",
|
||||||
"all_deleted": "{{count}} trabajos eliminados con éxito.",
|
"all_deleted": "{{count}} trabajos eliminados con éxito.",
|
||||||
|
"closed": "",
|
||||||
"converted": "Trabajo convertido con éxito.",
|
"converted": "Trabajo convertido con éxito.",
|
||||||
"created": "Trabajo creado con éxito. Click para ver.",
|
"created": "Trabajo creado con éxito. Click para ver.",
|
||||||
"created_subtitle": "",
|
"created_subtitle": "",
|
||||||
|
|||||||
@@ -725,6 +725,8 @@
|
|||||||
"PAS": "",
|
"PAS": "",
|
||||||
"PASL": ""
|
"PASL": ""
|
||||||
},
|
},
|
||||||
|
"profitcenter_labor": "",
|
||||||
|
"profitcenter_part": "",
|
||||||
"status": "Statut",
|
"status": "Statut",
|
||||||
"total": "",
|
"total": "",
|
||||||
"unq_seq": "Seq #"
|
"unq_seq": "Seq #"
|
||||||
@@ -772,6 +774,7 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"addingtoproduction": "",
|
"addingtoproduction": "",
|
||||||
|
"closing": "",
|
||||||
"creating": "",
|
"creating": "",
|
||||||
"deleted": "Erreur lors de la suppression du travail.",
|
"deleted": "Erreur lors de la suppression du travail.",
|
||||||
"exporting": "",
|
"exporting": "",
|
||||||
@@ -963,6 +966,7 @@
|
|||||||
"vehicle": "Véhicule"
|
"vehicle": "Véhicule"
|
||||||
},
|
},
|
||||||
"checklists": "",
|
"checklists": "",
|
||||||
|
"closeconfirm": "",
|
||||||
"cost": "",
|
"cost": "",
|
||||||
"cost_labor": "",
|
"cost_labor": "",
|
||||||
"cost_parts": "",
|
"cost_parts": "",
|
||||||
@@ -1024,6 +1028,7 @@
|
|||||||
"successes": {
|
"successes": {
|
||||||
"addedtoproduction": "",
|
"addedtoproduction": "",
|
||||||
"all_deleted": "{{count}} travaux supprimés avec succès.",
|
"all_deleted": "{{count}} travaux supprimés avec succès.",
|
||||||
|
"closed": "",
|
||||||
"converted": "Travail converti avec succès.",
|
"converted": "Travail converti avec succès.",
|
||||||
"created": "Le travail a été créé avec succès. Clique pour voir.",
|
"created": "Le travail a été créé avec succès. Clique pour voir.",
|
||||||
"created_subtitle": "",
|
"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
|
- part_type
|
||||||
- price_inc
|
- price_inc
|
||||||
- price_j
|
- price_j
|
||||||
|
- profitcenter_labor
|
||||||
|
- profitcenter_part
|
||||||
- prt_dsmk_m
|
- prt_dsmk_m
|
||||||
- prt_dsmk_p
|
- prt_dsmk_p
|
||||||
- removed
|
- removed
|
||||||
@@ -1819,6 +1821,8 @@ tables:
|
|||||||
- part_type
|
- part_type
|
||||||
- price_inc
|
- price_inc
|
||||||
- price_j
|
- price_j
|
||||||
|
- profitcenter_labor
|
||||||
|
- profitcenter_part
|
||||||
- prt_dsmk_m
|
- prt_dsmk_m
|
||||||
- prt_dsmk_p
|
- prt_dsmk_p
|
||||||
- removed
|
- removed
|
||||||
@@ -1886,6 +1890,8 @@ tables:
|
|||||||
- part_type
|
- part_type
|
||||||
- price_inc
|
- price_inc
|
||||||
- price_j
|
- price_j
|
||||||
|
- profitcenter_labor
|
||||||
|
- profitcenter_part
|
||||||
- prt_dsmk_m
|
- prt_dsmk_m
|
||||||
- prt_dsmk_p
|
- prt_dsmk_p
|
||||||
- removed
|
- removed
|
||||||
|
|||||||
@@ -26,19 +26,20 @@ function CalculateAdditional(job) {
|
|||||||
return job.joblines
|
return job.joblines
|
||||||
.filter(
|
.filter(
|
||||||
(jl) =>
|
(jl) =>
|
||||||
jl.lbr_op === "OP2" ||
|
!jl.removed &&
|
||||||
jl.lbr_op === "OP3" ||
|
(jl.lbr_op === "OP2" ||
|
||||||
jl.lbr_op === "OP4" ||
|
jl.lbr_op === "OP3" ||
|
||||||
jl.lbr_op === "OP5" ||
|
jl.lbr_op === "OP4" ||
|
||||||
jl.lbr_op === "OP6" ||
|
jl.lbr_op === "OP5" ||
|
||||||
jl.lbr_op === "OP7" ||
|
jl.lbr_op === "OP6" ||
|
||||||
jl.lbr_op === "OP8" ||
|
jl.lbr_op === "OP7" ||
|
||||||
jl.lbr_op === "OP9" ||
|
jl.lbr_op === "OP8" ||
|
||||||
jl.lbr_op === "OP10" ||
|
jl.lbr_op === "OP9" ||
|
||||||
jl.lbr_op === "OP13" ||
|
jl.lbr_op === "OP10" ||
|
||||||
jl.lbr_op === "OP13" ||
|
jl.lbr_op === "OP13" ||
|
||||||
jl.lbr_op === "OP14" ||
|
jl.lbr_op === "OP13" ||
|
||||||
jl.lbr_op === "OP15"
|
jl.lbr_op === "OP14" ||
|
||||||
|
jl.lbr_op === "OP15")
|
||||||
)
|
)
|
||||||
.reduce((acc, val) => {
|
.reduce((acc, val) => {
|
||||||
return acc.add(
|
return acc.add(
|
||||||
@@ -58,25 +59,27 @@ function CalculateTaxesTotals(job, otherTotals) {
|
|||||||
.add(Dinero({ amount: (job.storage_payable || 0) * 100 }));
|
.add(Dinero({ amount: (job.storage_payable || 0) * 100 }));
|
||||||
//TODO Levies should be included??
|
//TODO Levies should be included??
|
||||||
|
|
||||||
const statePartsTax = job.joblines.reduce((acc, val) => {
|
const statePartsTax = job.joblines
|
||||||
if (!!!val.tax_part) return acc;
|
.filter((jl) => !jl.removed)
|
||||||
// if (!!job.parts_tax_rates[val.part_type]) {
|
.reduce((acc, val) => {
|
||||||
// console.log("val.line_desc", val.line_desc);
|
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(
|
return acc.add(
|
||||||
Dinero({ amount: Math.round(val.act_price * 100) })
|
Dinero({ amount: Math.round(val.act_price * 100) })
|
||||||
.multiply(val.part_qty)
|
.multiply(val.part_qty)
|
||||||
.percentage(
|
.percentage(
|
||||||
(!val.part_type && val.lbr_op === "OP13"
|
(!val.part_type && val.lbr_op === "OP13"
|
||||||
? job.parts_tax_rates["PAN"].prt_tax_rt
|
? job.parts_tax_rates["PAN"].prt_tax_rt
|
||||||
: job.parts_tax_rates[val.part_type] &&
|
: job.parts_tax_rates[val.part_type] &&
|
||||||
job.parts_tax_rates[val.part_type].prt_tax_rt) * 100
|
job.parts_tax_rates[val.part_type].prt_tax_rt) * 100
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
// } else {
|
// } else {
|
||||||
// return acc;
|
// return acc;
|
||||||
// }
|
// }
|
||||||
}, Dinero({ amount: 0 }));
|
}, Dinero({ amount: 0 }));
|
||||||
|
|
||||||
let ret = {
|
let ret = {
|
||||||
subtotal: subtotal,
|
subtotal: subtotal,
|
||||||
@@ -119,7 +122,7 @@ function CalculateTaxesTotals(job, otherTotals) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function CalculateRatesTotals(ratesList, shoprates) {
|
function CalculateRatesTotals(ratesList, shoprates) {
|
||||||
const jobLines = ratesList.joblines;
|
const jobLines = ratesList.joblines.filter((jl) => !jl.removed);
|
||||||
|
|
||||||
let ret = {
|
let ret = {
|
||||||
la1: {
|
la1: {
|
||||||
@@ -242,72 +245,74 @@ function CalculateRatesTotals(ratesList, shoprates) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function CalculatePartsTotals(jobLines) {
|
function CalculatePartsTotals(jobLines) {
|
||||||
const ret = jobLines.reduce(
|
const ret = jobLines
|
||||||
(acc, value) => {
|
.filter((jl) => !jl.removed)
|
||||||
switch (value.part_type) {
|
.reduce(
|
||||||
case "PAS":
|
(acc, value) => {
|
||||||
case "PASL":
|
switch (value.part_type) {
|
||||||
return {
|
case "PAS":
|
||||||
...acc,
|
case "PASL":
|
||||||
sublets: {
|
return {
|
||||||
...acc.sublets,
|
...acc,
|
||||||
subtotal: acc.sublets.subtotal.add(
|
sublets: {
|
||||||
Dinero({ amount: Math.round(value.act_price * 100) })
|
...acc.sublets,
|
||||||
),
|
subtotal: acc.sublets.subtotal.add(
|
||||||
//TODO Add Adjustments in
|
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),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
subtotal: acc.parts.subtotal.add(
|
};
|
||||||
Dinero({ amount: Math.round(value.act_price * 100) }).multiply(
|
|
||||||
value.part_qty
|
default:
|
||||||
)
|
if (!value.part_type) return acc;
|
||||||
),
|
return {
|
||||||
//TODO Add Adjustments in
|
...acc,
|
||||||
},
|
parts: {
|
||||||
};
|
...acc.parts,
|
||||||
// default:
|
list: {
|
||||||
// return acc;
|
...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 {
|
return {
|
||||||
parts: {
|
parts: {
|
||||||
|
|||||||
Reference in New Issue
Block a user