BOD-63 Added CSS styling to prevent overflow. Added totals calculation to invoice screen. Updated packages.

This commit is contained in:
Patrick Fic
2020-05-05 16:57:17 -07:00
parent 522dc07058
commit 2317d7d385
20 changed files with 835 additions and 208 deletions

View File

@@ -46,10 +46,22 @@ export function InvoiceDetailEditContainer({ bodyshop }) {
data
? {
...data.invoices_by_pk,
invoicelines: data.invoices_by_pk.invoicelines.map((i) => {
return {
...i,
joblineid: !!i.joblineid ? i.joblineid : "noline",
applicable_taxes: {
federal:
(i.applicable_taxes && i.applicable_taxes.federal) ||
false,
state:
(i.applicable_taxes && i.applicable_taxes.state) ||
false,
local:
(i.applicable_taxes && i.applicable_taxes.local) ||
false,
},
};
}),
date: data.invoices_by_pk

View File

@@ -1,12 +1,13 @@
import { DatePicker, Form, Input, Switch } from "antd";
import React, { useState } from "react";
import { DatePicker, Form, Input, Statistic, Switch } from "antd";
import React, { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import DocumentsUploadContainer from "../documents-upload/documents-upload.container";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import JobSearchSelect from "../job-search-select/job-search-select.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import InvoiceFormLines from "./invoice-form.lines.component";
import Dinero from "dinero.js";
import { CalculateInvoiceTotal } from "./invoice-form.totals.utility";
import "./invoice-form.styles.scss";
export default function InvoiceFormComponent({
form,
@@ -25,9 +26,22 @@ export default function InvoiceFormComponent({
setDiscount(opt.discount);
};
//TODO: Test this further. Required to set discount when viewing an invoice.
useEffect(() => {
if (form.getFieldValue("vendorid") && vendorAutoCompleteOptions) {
const vendorId = form.getFieldValue("vendorid");
const matchingVendors = vendorAutoCompleteOptions.filter(
(v) => v.id === vendorId
);
if (matchingVendors.length === 1) {
setDiscount(matchingVendors[0].discount);
}
}
}, [form, setDiscount, vendorAutoCompleteOptions]);
return (
<div>
<div style={{ display: "flex" }}>
<div className="invoice-form-wrapper">
<div className="invoice-form-invoice-details">
<Form.Item
name="jobid"
label={t("invoices.fields.ro_number")}
@@ -64,7 +78,7 @@ export default function InvoiceFormComponent({
/>
</Form.Item>
</div>
<div style={{ display: "flex" }}>
<div className="invoice-form-invoice-details">
<Form.Item
label={t("invoices.fields.invoice_number")}
name="invoice_number"
@@ -158,21 +172,62 @@ export default function InvoiceFormComponent({
<Form.Item shouldUpdate>
{() => {
return (
<div>
{JSON.stringify(form.getFieldsValue(["invoicelines", "total"]))}
</div>
const totals = CalculateInvoiceTotal(
form.getFieldsValue([
"invoicelines",
"total",
"federal_tax_rate",
"state_tax_rate",
"local_tax_rate",
])
);
if (!!totals)
return (
<div className="invoice-form-totals">
<Statistic
title={t("invoices.labels.subtotal")}
value={totals.subtotal.toFormat()}
precision={2}
/>
<Statistic
title={t("invoices.labels.federal_tax")}
value={totals.federalTax.toFormat()}
precision={2}
/>
<Statistic
title={t("invoices.labels.state_tax")}
value={totals.stateTax.toFormat()}
precision={2}
/>
<Statistic
title={t("invoices.labels.local_tax")}
value={totals.localTax.toFormat()}
precision={2}
/>
<Statistic
title={t("invoices.labels.entered_total")}
value={totals.enteredTotal.toFormat()}
precision={2}
/>
<Statistic
title={t("invoices.labels.invoice_total")}
value={totals.invoiceTotal.toFormat()}
precision={2}
/>
<Statistic
title={t("invoices.labels.discrepancy")}
valueStyle={{
color:
totals.discrepancy.getAmount() === 0 ? "green" : "red",
}}
value={totals.discrepancy.toFormat()}
precision={2}
/>
</div>
);
return null;
}}
</Form.Item>
<button
onClick={() => {
console.log(form.getFieldsValue());
}}
>
Get Field Values
</button>
</div>
);
}

View File

@@ -4,6 +4,7 @@ import React from "react";
import { useTranslation } from "react-i18next";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import InvoiceLineSearchSelect from "../invoice-line-search-select/invoice-line-search-select.component";
import { WarningOutlined } from "@ant-design/icons";
export default function InvoiceEnterModalLinesComponent({
lineData,
@@ -18,10 +19,10 @@ export default function InvoiceEnterModalLinesComponent({
<Form.List name="invoicelines">
{(fields, { add, remove }) => {
return (
<div>
<div className="invoice-form-lines-wrapper">
{fields.map((field, index) => (
<Form.Item required={false} key={field.key}>
<div style={{ display: "flex" }}>
<div className="invoice-form-line">
<Form.Item
label={t("invoicelines.fields.jobline")}
key={`${index}joblinename`}
@@ -116,6 +117,23 @@ export default function InvoiceEnterModalLinesComponent({
>
<CurrencyInput />
</Form.Item>
<Form.Item shouldUpdate>
{() => {
const line = getFieldsValue(["invoicelines"])
.invoicelines[index];
if (!!!line) return null;
const lineDiscount = +(
1 -
Math.round(
(line.actual_cost / line.actual_price) * 100
) /
100
).toFixed(2);
if (lineDiscount === discount) return null;
return <WarningOutlined style={{ color: "red" }} />;
}}
</Form.Item>
<Form.Item
label={t("invoicelines.fields.cost_center")}
key={`${index}cost_center`}
@@ -136,7 +154,7 @@ export default function InvoiceEnterModalLinesComponent({
<Form.Item
label={t("invoicelines.fields.federal_tax_applicable")}
key={`${index}fedtax`}
initialValue={"true"}
initialValue={true}
valuePropName="checked"
name={[field.name, "applicable_taxes", "federal"]}
>
@@ -149,7 +167,7 @@ export default function InvoiceEnterModalLinesComponent({
name={[field.name, "applicable_taxes", "state"]}
>
<Switch />
</Form.Item>{" "}
</Form.Item>
<Form.Item
label={t("invoicelines.fields.local_tax_applicable")}
key={`${index}localtax`}

View File

@@ -0,0 +1,40 @@
.invoice-form-wrapper {
display: flex;
flex-direction: column;
justify-content: left;
}
.invoice-form-totals {
display: flex;
justify-content: space-around;
align-items: flex-start;
flex-wrap: wrap;
& > * {
padding: 5px;
}
}
.invoice-form-invoice-details {
display: flex;
align-items: flex-start;
flex-wrap: wrap;
& > * {
padding: 5px;
}
}
.invoice-form-lines-wrapper {
border: 3px ridge rgba(28, 110, 164, 0.24);
border-radius: 4px;
}
.invoice-form-line {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
justify-content: space-around;
border-bottom: 2px dashed rgba(7, 7, 7, 0.4);
F & > * {
margin: 5px;
}
}

View File

@@ -0,0 +1,49 @@
import Dinero from "dinero.js";
export const CalculateInvoiceTotal = (invoice) => {
const {
total,
invoicelines,
federal_tax_rate,
local_tax_rate,
state_tax_rate,
} = invoice;
let subtotal = Dinero({ amount: 0 });
let federalTax = Dinero({ amount: 0 });
let stateTax = Dinero({ amount: 0 });
let localTax = Dinero({ amount: 0 });
if (!!!invoicelines) return null;
invoicelines.map((i) => {
if (!!i) {
const itemTotal = Dinero({ amount: i.actual_cost * 100 || 0 }).multiply(
i.quantity || 1
);
subtotal = subtotal.add(itemTotal);
if (i.applicable_taxes.federal) {
console.log("Adding fed tax.");
federalTax = federalTax.add(
itemTotal.percentage(federal_tax_rate || 0)
);
}
if (i.applicable_taxes.state)
stateTax = stateTax.add(itemTotal.percentage(state_tax_rate || 0));
if (i.applicable_taxes.local)
localTax = localTax.add(itemTotal.percentage(local_tax_rate || 0));
}
});
const invoiceTotal = Dinero({ amount: total * 100 || 0 });
const enteredTotal = subtotal.add(federalTax).add(stateTax).add(localTax);
const discrepancy = enteredTotal.subtract(invoiceTotal);
return {
subtotal,
federalTax,
stateTax,
localTax,
enteredTotal,
invoiceTotal,
discrepancy,
};
};