WIP Invoice Enter

This commit is contained in:
Patrick Fic
2020-03-02 16:18:14 -08:00
parent ea000df34a
commit 181c5cbd2a
9 changed files with 346 additions and 142 deletions

View File

@@ -1750,6 +1750,32 @@
<folder_node> <folder_node>
<name>invoicelines</name> <name>invoicelines</name>
<children> <children>
<folder_node>
<name>actions</name>
<children>
<concept_node>
<name>newline</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>
</children>
</folder_node>
<folder_node> <folder_node>
<name>fields</name> <name>fields</name>
<children> <children>
@@ -1863,6 +1889,27 @@
<folder_node> <folder_node>
<name>labels</name> <name>labels</name>
<children> <children>
<concept_node>
<name>entered</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>other</name> <name>other</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -1884,6 +1931,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>unreconciled</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>
</children> </children>
</folder_node> </folder_node>
</children> </children>

View File

@@ -4,10 +4,9 @@ function FormItemCurrency(props, ref) {
return ( return (
<InputNumber <InputNumber
{...props} {...props}
formatter={value => `$ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ",")} //formatter={value => `$ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
parser={value => value.replace(/\$\s?|(,*)/g, "")} // parser={value => value.replace(/\$\s?|(,*)/g, "")}
precision={2} precision={2}
/> />
); );
} }

View File

@@ -1,8 +1,18 @@
import { Button, DatePicker, Form, Input, Modal, Select, Switch } from "antd"; import {
Button,
DatePicker,
Form,
Input,
Modal,
Select,
Switch,
Tag
} from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import InvoiceEnterModalLinesComponent from "./invoice-enter-modal.lines.component"; import InvoiceEnterModalLinesComponent from "./invoice-enter-modal.lines.component";
export default function InvoiceEnterModalComponent({ export default function InvoiceEnterModalComponent({
visible, visible,
invoice, invoice,
@@ -15,15 +25,12 @@ export default function InvoiceEnterModalComponent({
handleVendorSelect, handleVendorSelect,
vendorAutoCompleteOptions, vendorAutoCompleteOptions,
lineData, lineData,
linesState, vendor,
vendor job
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
const { resetFields } = form; const { resetFields } = form;
//Default Values to be set in form.
// {getFieldDecorator("retail", { initialValue: jobLine.act_price })(
// initialValue: jobLine.act_price * (discount ? 1 - discount : 1)
return ( return (
<Form onFinish={handleFinish} autoComplete={"off"} form={form}> <Form onFinish={handleFinish} autoComplete={"off"} form={form}>
@@ -53,7 +60,11 @@ export default function InvoiceEnterModalComponent({
> >
<Select <Select
showSearch showSearch
defaultValue={
job ? (job.ro_number ? job.ro_number : job.est_number) : null
}
autoFocus autoFocus
defaultOpen
style={{ width: "300px" }} style={{ width: "300px" }}
onSelect={handleRoSelect} onSelect={handleRoSelect}
> >
@@ -91,12 +102,16 @@ export default function InvoiceEnterModalComponent({
{vendorAutoCompleteOptions {vendorAutoCompleteOptions
? vendorAutoCompleteOptions.map(o => ( ? vendorAutoCompleteOptions.map(o => (
<Select.Option key={o.id} value={o.name}> <Select.Option key={o.id} value={o.name}>
<div style={{ display: "flex" }}>
{o.name} {o.name}
<Tag color="green">{`${o.discount * 100}%`}</Tag>
</div>
</Select.Option> </Select.Option>
)) ))
: null} : null}
</Select> </Select>
</Form.Item> </Form.Item>
<Button onClick={() => resetFields()}> <Button onClick={() => resetFields()}>
{t("general.actions.reset")} {t("general.actions.reset")}
</Button> </Button>
@@ -105,8 +120,11 @@ export default function InvoiceEnterModalComponent({
<Form.Item <Form.Item
label={t("invoices.fields.invoice_number")} label={t("invoices.fields.invoice_number")}
name="invoice_number" name="invoice_number"
rule={[ rules={[
{ required: true, message: t("general.validation.required") } {
required: true,
message: t("general.validation.required")
}
]} ]}
> >
<Input /> <Input />
@@ -114,8 +132,11 @@ export default function InvoiceEnterModalComponent({
<Form.Item <Form.Item
label={t("invoices.fields.date")} label={t("invoices.fields.date")}
name="date" name="date"
rule={[ rules={[
{ required: true, message: t("general.validation.required") } {
required: true,
message: t("general.validation.required")
}
]} ]}
> >
<DatePicker /> <DatePicker />
@@ -130,8 +151,11 @@ export default function InvoiceEnterModalComponent({
<Form.Item <Form.Item
label={t("invoices.fields.total")} label={t("invoices.fields.total")}
name="total" name="total"
rule={[ rules={[
{ required: true, message: t("general.validation.required") } {
required: true,
message: t("general.validation.required")
}
]} ]}
> >
<CurrencyInput /> <CurrencyInput />
@@ -142,6 +166,7 @@ export default function InvoiceEnterModalComponent({
discount={vendor && vendor.discount} discount={vendor && vendor.discount}
form={form} form={form}
/> />
<Button onClick={() => console.log(form.getFieldsValue())}> <Button onClick={() => console.log(form.getFieldsValue())}>
Field Values Field Values
</Button> </Button>

View File

@@ -141,6 +141,7 @@ function InvoiceEnterModalContainer({
)[0] )[0]
: null : null
} }
job={invoiceEnterModal.context.job || null}
/> />
); );
} }

View File

@@ -1,7 +1,8 @@
import { DeleteFilled } from "@ant-design/icons"; import { DeleteFilled } from "@ant-design/icons";
import { Button, Form, Input, Select } from "antd"; import { Button, Col, Form, Input, Row, Select, Tag } from "antd";
import React from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component";
export default function InvoiceEnterModalLinesComponent({ export default function InvoiceEnterModalLinesComponent({
@@ -11,7 +12,11 @@ export default function InvoiceEnterModalLinesComponent({
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { setFieldsValue, getFieldsValue } = form; const { setFieldsValue, getFieldsValue } = form;
const [amounts, setAmounts] = useState({ invoiceTotal: 0, enteredAmount: 0 });
return ( return (
<div>
<Form.List name="invoicelines"> <Form.List name="invoicelines">
{(fields, { add, remove }) => { {(fields, { add, remove }) => {
return ( return (
@@ -21,10 +26,18 @@ export default function InvoiceEnterModalLinesComponent({
<div style={{ display: "flex" }}> <div style={{ display: "flex" }}>
<Form.Item <Form.Item
label={t("invoicelines.fields.line_desc")} label={t("invoicelines.fields.line_desc")}
key={`${index}line_desc`} key={`${index}joblinename`}
name={[field.name, "line_desc"]} name={[field.name, "joblinename"]}
rules={[
{
required: true,
message: t("general.validation.required")
}
]}
> >
<Select <Select
autoFocus
name={`le${index}`}
style={{ width: "300px" }} style={{ width: "300px" }}
onSelect={(value, opt) => { onSelect={(value, opt) => {
setFieldsValue({ setFieldsValue({
@@ -32,11 +45,16 @@ export default function InvoiceEnterModalLinesComponent({
"invoicelines" "invoicelines"
]).invoicelines.map((item, idx) => { ]).invoicelines.map((item, idx) => {
if (idx === index) { if (idx === index) {
console.log("opt", opt);
return { return {
...item, ...item,
joblineid: opt.key.includes("noline") joblineid: opt.key.includes("noline")
? null ? null
: opt.key : opt.key,
line_desc: opt.key.includes("noline")
? ""
: opt.value,
actual_price: opt.cost ? opt.cost : 0
}; };
} }
return item; return item;
@@ -48,33 +66,47 @@ export default function InvoiceEnterModalLinesComponent({
<Select.Option <Select.Option
key={`${index}noline`} key={`${index}noline`}
value={t("invoicelines.labels.other")} value={t("invoicelines.labels.other")}
cost={0}
> >
{t("invoicelines.labels.other")} {t("invoicelines.labels.other")}
</Select.Option> </Select.Option>
{lineData {lineData
? lineData.map(item => ( ? lineData.map(item => (
<Select.Option key={item.id} value={item.line_desc}> <Select.Option
key={item.id}
value={item.line_desc}
cost={item.act_price ? item.act_price : 0}
>
{item.line_desc} {item.line_desc}
</Select.Option> </Select.Option>
)) ))
: null} : null}
</Select> </Select>
</Form.Item> </Form.Item>
{console.log(
'getFieldsValue("invoicelines").invoicelines[index]',
getFieldsValue("invoicelines").invoicelines[index]
)}
{getFieldsValue("invoicelines").invoicelines[index] && {getFieldsValue("invoicelines").invoicelines[index] &&
getFieldsValue("invoicelines").invoicelines[index] getFieldsValue("invoicelines").invoicelines[index]
.line_desc && .joblinename &&
!getFieldsValue("invoicelines").invoicelines[index].joblineid !getFieldsValue("invoicelines").invoicelines[index]
? "Other" .joblineid ? (
: null} <Form.Item
label={t("invoicelines.fields.line_desc")}
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
>
<Input />
</Form.Item>
) : null}
<Form.Item <Form.Item
label={t("invoicelines.fields.actual")} label={t("invoicelines.fields.actual")}
key={`${index}actual_price`} key={`${index}actual_price`}
name={[field.name, "actual_price"]} name={[field.name, "actual_price"]}
rules={[
{
required: true,
message: t("general.validation.required")
}
]}
> >
<CurrencyInput <CurrencyInput
onBlur={e => { onBlur={e => {
@@ -86,8 +118,7 @@ export default function InvoiceEnterModalLinesComponent({
return { return {
...item, ...item,
actual_cost: actual_cost:
parseFloat(e.target.value.substring(1)) * 1 - parseFloat(e.target.value) * (1 - discount)
discount
}; };
} }
return item; return item;
@@ -100,13 +131,44 @@ export default function InvoiceEnterModalLinesComponent({
label={t("invoicelines.fields.actual_cost")} label={t("invoicelines.fields.actual_cost")}
key={`${index}actual_cost`} key={`${index}actual_cost`}
name={[field.name, "actual_cost"]} name={[field.name, "actual_cost"]}
rules={[
{
required: true,
message: t("general.validation.required")
}
]}
> >
<CurrencyInput /> <CurrencyInput
onBlur={() =>
setAmounts({
invoiceTotal: getFieldsValue().total,
enteredTotal: getFieldsValue("invoicelines")
.invoicelines
? getFieldsValue(
"invoicelines"
).invoicelines.reduce(
(acc, value) =>
acc +
(value && value.actual_cost
? value.actual_cost
: 0),
0
)
: 0
})
}
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("invoicelines.fields.cost_center")} label={t("invoicelines.fields.cost_center")}
key={`${index}cost_center`} key={`${index}cost_center`}
name={[field.name, "cost_center"]} name={[field.name, "cost_center"]}
rules={[
{
required: true,
message: t("general.validation.required")
}
]}
> >
<Input /> <Input />
</Form.Item> </Form.Item>
@@ -127,12 +189,34 @@ export default function InvoiceEnterModalLinesComponent({
}} }}
style={{ width: "60%" }} style={{ width: "60%" }}
> >
New liNe {t("invoicelines.actions.newline")}
</Button> </Button>
</Form.Item> </Form.Item>
</div> </div>
); );
}} }}
</Form.List> </Form.List>
<Row>
<Col span={4}>
{t("invoicelines.labels.entered")}
<CurrencyFormatter>{amounts.enteredTotal}</CurrencyFormatter>
</Col>
<Col span={4}>
<Tag
color={
amounts.invoiceTotal - amounts.enteredTotal === 0
? "green"
: "red"
}
>
{t("invoicelines.labels.unreconciled")}:
<CurrencyFormatter>
{amounts.invoiceTotal - amounts.enteredTotal}
</CurrencyFormatter>
</Tag>
</Col>
</Row>
</div>
); );
} }

View File

@@ -1,4 +1,4 @@
import { createStore, applyMiddleware } from "redux"; import { createStore, applyMiddleware, compose } from "redux";
import { persistStore } from "redux-persist"; import { persistStore } from "redux-persist";
import { createLogger } from "redux-logger"; import { createLogger } from "redux-logger";
import createSagaMiddleware from "redux-saga"; import createSagaMiddleware from "redux-saga";
@@ -11,7 +11,19 @@ if (process.env.NODE_ENV === "development") {
middlewares.push(createLogger({ collapsed: true, diff: true })); middlewares.push(createLogger({ collapsed: true, diff: true }));
} }
export const store = createStore(rootReducer, applyMiddleware(...middlewares)); const composeEnhancers =
typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extensions options like name, actionsBlacklist, actionsCreators, serialize...
})
: compose;
const enhancer = composeEnhancers(
applyMiddleware(...middlewares)
// other store enhancers if any
);
export const store = createStore(rootReducer, enhancer);
sagaMiddleWare.run(rootSaga); sagaMiddleWare.run(rootSaga);
export const persistor = persistStore(store); export const persistor = persistStore(store);

View File

@@ -145,6 +145,9 @@
} }
}, },
"invoicelines": { "invoicelines": {
"actions": {
"newline": "New Line"
},
"fields": { "fields": {
"actual": "Actual", "actual": "Actual",
"actual_cost": "Actual Cost", "actual_cost": "Actual Cost",
@@ -153,7 +156,9 @@
"retail": "Retail" "retail": "Retail"
}, },
"labels": { "labels": {
"other": "Other Item" "entered": "Entered",
"other": "--Not On Estimate--",
"unreconciled": "Unreconciled"
} }
}, },
"invoices": { "invoices": {

View File

@@ -145,6 +145,9 @@
} }
}, },
"invoicelines": { "invoicelines": {
"actions": {
"newline": ""
},
"fields": { "fields": {
"actual": "", "actual": "",
"actual_cost": "", "actual_cost": "",
@@ -153,7 +156,9 @@
"retail": "" "retail": ""
}, },
"labels": { "labels": {
"other": "" "entered": "",
"other": "",
"unreconciled": ""
} }
}, },
"invoices": { "invoices": {

View File

@@ -145,6 +145,9 @@
} }
}, },
"invoicelines": { "invoicelines": {
"actions": {
"newline": ""
},
"fields": { "fields": {
"actual": "", "actual": "",
"actual_cost": "", "actual_cost": "",
@@ -153,7 +156,9 @@
"retail": "" "retail": ""
}, },
"labels": { "labels": {
"other": "" "entered": "",
"other": "",
"unreconciled": ""
} }
}, },
"invoices": { "invoices": {