Fixed job costing bugs. BOD-247

This commit is contained in:
Patrick Fic
2020-08-11 13:26:53 -07:00
parent 1715c08296
commit 1e3bf19cd3
12 changed files with 134 additions and 27 deletions

View File

@@ -13753,6 +13753,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>cost_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>cost_parts</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>
<folder_node> <folder_node>
<name>create</name> <name>create</name>
<children> <children>

View File

@@ -127,6 +127,10 @@ function InvoiceEnterModalContainer({
if (enterAgain) form.submit(); if (enterAgain) form.submit();
}, [enterAgain, form]); }, [enterAgain, form]);
useEffect(() => {
if (invoiceEnterModal.visible) form.resetFields();
}, [invoiceEnterModal.visible, form]);
return ( return (
<Modal <Modal
title={t("invoices.labels.new")} title={t("invoices.labels.new")}
@@ -165,6 +169,7 @@ function InvoiceEnterModalContainer({
setEnterAgain(false); setEnterAgain(false);
}} }}
initialValues={{ initialValues={{
...invoiceEnterModal.context.invoice,
jobid: jobid:
(invoiceEnterModal.context.job && (invoiceEnterModal.context.job &&
invoiceEnterModal.context.job.id) || invoiceEnterModal.context.job.id) ||
@@ -172,7 +177,6 @@ function InvoiceEnterModalContainer({
federal_tax_rate: bodyshop.invoice_tax_rates.federal_tax_rate || 0, federal_tax_rate: bodyshop.invoice_tax_rates.federal_tax_rate || 0,
state_tax_rate: bodyshop.invoice_tax_rates.state_tax_rate || 0, state_tax_rate: bodyshop.invoice_tax_rates.state_tax_rate || 0,
local_tax_rate: bodyshop.invoice_tax_rates.local_tax_rate || 0, local_tax_rate: bodyshop.invoice_tax_rates.local_tax_rate || 0,
...invoiceEnterModal.context.invoice,
}} }}
> >
<InvoiceFormContainer form={form} /> <InvoiceFormContainer form={form} />

View File

@@ -19,12 +19,12 @@ export function JobCostingModalComponent({ bodyshop, job }) {
const jobLineTotalsByProfitCenter = job.joblines.reduce( const jobLineTotalsByProfitCenter = job.joblines.reduce(
(acc, val) => { (acc, val) => {
const laborProfitCenter = defaultProfits[val.mod_lbr_ty]; const laborProfitCenter = defaultProfits[val.mod_lbr_ty] || "?";
if (!!!laborProfitCenter) // if (!!!laborProfitCenter)
console.log( // console.log(
"Unknown cost/profit center mapping for labor.", // "Unknown cost/profit center mapping for labor.",
val.mod_lbr_ty // val.mod_lbr_ty
); // );
const rateName = `rate_${(val.mod_lbr_ty || "").toLowerCase()}`; const rateName = `rate_${(val.mod_lbr_ty || "").toLowerCase()}`;
const laborAmount = Dinero({ const laborAmount = Dinero({
@@ -36,7 +36,7 @@ export function JobCostingModalComponent({ bodyshop, job }) {
laborAmount laborAmount
); );
const partsProfitCenter = defaultProfits[val.part_type]; const partsProfitCenter = defaultProfits[val.part_type] || "?";
if (!!!partsProfitCenter) if (!!!partsProfitCenter)
console.log( console.log(
"Unknown cost/profit center mapping for parts.", "Unknown cost/profit center mapping for parts.",
@@ -79,10 +79,31 @@ export function JobCostingModalComponent({ bodyshop, job }) {
{} {}
); );
const ticketTotalsByProfitCenter = job.timetickets.reduce(
(ticket_acc, ticket_val) => {
//At the invoice level.
if (!!!ticket_acc[ticket_val.cost_center])
ticket_acc[ticket_val.cost_center] = Dinero();
ticket_acc[ticket_val.cost_center] = ticket_acc[
ticket_val.cost_center
].add(
Dinero({
amount: Math.round((ticket_val.rate || 0) * 100),
}).multiply(ticket_val.actualhrs || 0)
);
return ticket_acc;
},
{}
);
const summaryData = { const summaryData = {
totalLaborSales: Dinero({ amount: 0 }), totalLaborSales: Dinero({ amount: 0 }),
totalPartsSales: Dinero({ amount: 0 }), totalPartsSales: Dinero({ amount: 0 }),
totalSales: Dinero({ amount: 0 }), totalSales: Dinero({ amount: 0 }),
totalLaborCost: Dinero({ amount: 0 }),
totalPartsCost: Dinero({ amount: 0 }),
totalCost: Dinero({ amount: 0 }), totalCost: Dinero({ amount: 0 }),
gpdollars: Dinero({ amount: 0 }), gpdollars: Dinero({ amount: 0 }),
gppercent: null, gppercent: null,
@@ -95,7 +116,15 @@ export function JobCostingModalComponent({ bodyshop, job }) {
jobLineTotalsByProfitCenter.labor[ccVal] || Dinero({ amount: 0 }); jobLineTotalsByProfitCenter.labor[ccVal] || Dinero({ amount: 0 });
const sale_parts = const sale_parts =
jobLineTotalsByProfitCenter.parts[ccVal] || Dinero({ amount: 0 }); jobLineTotalsByProfitCenter.parts[ccVal] || Dinero({ amount: 0 });
const cost = invoiceTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 });
const cost_labor =
ticketTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 });
const cost_parts =
invoiceTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 });
const cost = (
invoiceTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 })
).add(ticketTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 }));
const totalSales = sale_labor.add(sale_parts); const totalSales = sale_labor.add(sale_parts);
const gpdollars = totalSales.subtract(cost); const gpdollars = totalSales.subtract(cost);
const gppercent = ( const gppercent = (
@@ -115,6 +144,8 @@ export function JobCostingModalComponent({ bodyshop, job }) {
summaryData.totalSales = summaryData.totalSales summaryData.totalSales = summaryData.totalSales
.add(sale_labor) .add(sale_labor)
.add(sale_parts); .add(sale_parts);
summaryData.totalLaborCost = summaryData.totalLaborCost.add(cost_labor);
summaryData.totalPartsCost = summaryData.totalPartsCost.add(cost_parts);
summaryData.totalCost = summaryData.totalCost.add(cost); summaryData.totalCost = summaryData.totalCost.add(cost);
return { return {
@@ -122,6 +153,8 @@ export function JobCostingModalComponent({ bodyshop, job }) {
cost_center: ccVal, cost_center: ccVal,
sale_labor: sale_labor && sale_labor.toFormat(), sale_labor: sale_labor && sale_labor.toFormat(),
sale_parts: sale_parts && sale_parts.toFormat(), sale_parts: sale_parts && sale_parts.toFormat(),
cost_parts: cost_parts && cost_parts.toFormat(),
cost_labor: cost_labor && cost_labor.toFormat(),
cost: cost && cost.toFormat(), cost: cost && cost.toFormat(),
gpdollars: gpdollars.toFormat(), gpdollars: gpdollars.toFormat(),
gppercent: gppercentFormatted, gppercent: gppercentFormatted,
@@ -143,8 +176,6 @@ export function JobCostingModalComponent({ bodyshop, job }) {
summaryData.gppercentFormatted = summaryData.gppercent; summaryData.gppercentFormatted = summaryData.gppercent;
} }
console.log("JobCostingModalComponent -> summaryData", summaryData);
return ( return (
<div> <div>
<JobCostingStatistics job={job} summaryData={summaryData} /> <JobCostingStatistics job={job} summaryData={summaryData} />

View File

@@ -40,12 +40,20 @@ export default function JobCostingPartsTable({ job, data }) {
state.sortedInfo.columnKey === "sale_parts" && state.sortedInfo.order, state.sortedInfo.columnKey === "sale_parts" && state.sortedInfo.order,
}, },
{ {
title: t("jobs.labels.cost"), title: t("jobs.labels.cost_labor"),
dataIndex: "cost", dataIndex: "cost_labor",
key: "cost", key: "cost_labor",
sorter: (a, b) => a.cost - b.cost, sorter: (a, b) => a.cost_labor - b.cost_labor,
sortOrder: sortOrder:
state.sortedInfo.columnKey === "cost" && state.sortedInfo.order, state.sortedInfo.columnKey === "cost_labor" && state.sortedInfo.order,
},
{
title: t("jobs.labels.cost_parts"),
dataIndex: "cost_parts",
key: "cost_parts",
sorter: (a, b) => a.cost_parts - b.cost_parts,
sortOrder:
state.sortedInfo.columnKey === "cost_parts" && state.sortedInfo.order,
}, },
{ {
title: t("jobs.labels.gpdollars"), title: t("jobs.labels.gpdollars"),

View File

@@ -20,6 +20,14 @@ export default function JobCostingStatistics({ job, summaryData }) {
value={summaryData.totalSales.toFormat()} value={summaryData.totalSales.toFormat()}
title={t("jobs.labels.total_sales")} title={t("jobs.labels.total_sales")}
/> />
<Statistic
value={summaryData.totalLaborCost.toFormat()}
title={t("jobs.labels.cost_labor")}
/>
<Statistic
value={summaryData.totalPartsCost.toFormat()}
title={t("jobs.labels.cost_parts")}
/>
<Statistic <Statistic
value={summaryData.totalCost.toFormat()} value={summaryData.totalCost.toFormat()}
title={t("jobs.labels.total_cost")} title={t("jobs.labels.total_cost")}

View File

@@ -57,9 +57,14 @@ export function TimeTicketModalContainer({
.then(handleMutationSuccess) .then(handleMutationSuccess)
.catch(handleMutationError); .catch(handleMutationError);
} else { } else {
//Get selected employee rate.
const rate = EmployeeAutoCompleteData.employees.filter(
(i) => i.id === values.employeeid
)[0].base_rate;
insertTicket({ insertTicket({
variables: { variables: {
timeTicketInput: [{ ...values, bodyshopid: bodyshop.id }], timeTicketInput: [{ ...values, rate, bodyshopid: bodyshop.id }],
}, },
}) })
.then(handleMutationSuccess) .then(handleMutationSuccess)

View File

@@ -9,7 +9,6 @@ const VendorSearchSelect = (
{ value, onChange, options, onSelect, disabled, preferredMake }, { value, onChange, options, onSelect, disabled, preferredMake },
ref ref
) => { ) => {
console.log("preferredMake", preferredMake, options);
const [option, setOption] = useState(value); const [option, setOption] = useState(value);
useEffect(() => { useEffect(() => {
@@ -25,8 +24,6 @@ const VendorSearchSelect = (
) )
: []; : [];
console.log("favorites", favorites);
return ( return (
<Select <Select
ref={ref} ref={ref}

View File

@@ -113,13 +113,7 @@ export const UPDATE_JOB_LINE = gql`
export const GET_JOB_LINES_TO_ENTER_INVOICE = gql` export const GET_JOB_LINES_TO_ENTER_INVOICE = gql`
query GET_JOB_LINES_TO_ENTER_INVOICE($id: uuid!) { query GET_JOB_LINES_TO_ENTER_INVOICE($id: uuid!) {
joblines( joblines(where: { jobid: { _eq: $id } }) {
where: {
jobid: { _eq: $id }
oem_partno: { _neq: "" }
act_price: { _gt: "0" }
}
) {
id id
line_desc line_desc
part_type part_type
@@ -136,3 +130,9 @@ export const GET_JOB_LINES_TO_ENTER_INVOICE = gql`
} }
} }
`; `;
// oem_partno: {
// _neq: "";
// }
// act_price: {
// _gt: "0";
// }

View File

@@ -202,6 +202,12 @@ export const QUERY_JOB_COSTING_DETAILS = gql`
quantity quantity
} }
} }
timetickets {
id
rate
cost_center
actualhrs
}
} }
} }
`; `;

View File

@@ -839,6 +839,8 @@
"vehicle": "Vehicle" "vehicle": "Vehicle"
}, },
"cost": "Cost", "cost": "Cost",
"cost_labor": "Cost - Labor",
"cost_parts": "Cost - Parts",
"create": { "create": {
"jobinfo": "Job Info", "jobinfo": "Job Info",
"newowner": "Create a new Owner instead. ", "newowner": "Create a new Owner instead. ",

View File

@@ -839,6 +839,8 @@
"vehicle": "Vehículo" "vehicle": "Vehículo"
}, },
"cost": "", "cost": "",
"cost_labor": "",
"cost_parts": "",
"create": { "create": {
"jobinfo": "", "jobinfo": "",
"newowner": "", "newowner": "",

View File

@@ -839,6 +839,8 @@
"vehicle": "Véhicule" "vehicle": "Véhicule"
}, },
"cost": "", "cost": "",
"cost_labor": "",
"cost_parts": "",
"create": { "create": {
"jobinfo": "", "jobinfo": "",
"newowner": "", "newowner": "",