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

View File

@@ -4,10 +4,9 @@ function FormItemCurrency(props, ref) {
return (
<InputNumber
{...props}
formatter={value => `$ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
parser={value => value.replace(/\$\s?|(,*)/g, "")}
//formatter={value => `$ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
// parser={value => value.replace(/\$\s?|(,*)/g, "")}
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 { useTranslation } from "react-i18next";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import InvoiceEnterModalLinesComponent from "./invoice-enter-modal.lines.component";
export default function InvoiceEnterModalComponent({
visible,
invoice,
@@ -15,15 +25,12 @@ export default function InvoiceEnterModalComponent({
handleVendorSelect,
vendorAutoCompleteOptions,
lineData,
linesState,
vendor
vendor,
job
}) {
const { t } = useTranslation();
const [form] = Form.useForm();
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 (
<Form onFinish={handleFinish} autoComplete={"off"} form={form}>
@@ -53,7 +60,11 @@ export default function InvoiceEnterModalComponent({
>
<Select
showSearch
defaultValue={
job ? (job.ro_number ? job.ro_number : job.est_number) : null
}
autoFocus
defaultOpen
style={{ width: "300px" }}
onSelect={handleRoSelect}
>
@@ -91,12 +102,16 @@ export default function InvoiceEnterModalComponent({
{vendorAutoCompleteOptions
? vendorAutoCompleteOptions.map(o => (
<Select.Option key={o.id} value={o.name}>
{o.name}
<div style={{ display: "flex" }}>
{o.name}
<Tag color="green">{`${o.discount * 100}%`}</Tag>
</div>
</Select.Option>
))
: null}
</Select>
</Form.Item>
<Button onClick={() => resetFields()}>
{t("general.actions.reset")}
</Button>
@@ -105,8 +120,11 @@ export default function InvoiceEnterModalComponent({
<Form.Item
label={t("invoices.fields.invoice_number")}
name="invoice_number"
rule={[
{ required: true, message: t("general.validation.required") }
rules={[
{
required: true,
message: t("general.validation.required")
}
]}
>
<Input />
@@ -114,8 +132,11 @@ export default function InvoiceEnterModalComponent({
<Form.Item
label={t("invoices.fields.date")}
name="date"
rule={[
{ required: true, message: t("general.validation.required") }
rules={[
{
required: true,
message: t("general.validation.required")
}
]}
>
<DatePicker />
@@ -130,8 +151,11 @@ export default function InvoiceEnterModalComponent({
<Form.Item
label={t("invoices.fields.total")}
name="total"
rule={[
{ required: true, message: t("general.validation.required") }
rules={[
{
required: true,
message: t("general.validation.required")
}
]}
>
<CurrencyInput />
@@ -142,6 +166,7 @@ export default function InvoiceEnterModalComponent({
discount={vendor && vendor.discount}
form={form}
/>
<Button onClick={() => console.log(form.getFieldsValue())}>
Field Values
</Button>

View File

@@ -69,7 +69,7 @@ function InvoiceEnterModalContainer({
const handleFinish = values => {
console.log("values", values);
alert("Closing this modal.");
// toggleModalVisible();
// toggleModalVisible();
// if (!jobLineEditModal.context.id) {
// insertJobLine({
// variables: {
@@ -141,6 +141,7 @@ function InvoiceEnterModalContainer({
)[0]
: null
}
job={invoiceEnterModal.context.job || null}
/>
);
}

View File

@@ -1,7 +1,8 @@
import { DeleteFilled } from "@ant-design/icons";
import { Button, Form, Input, Select } from "antd";
import React from "react";
import { Button, Col, Form, Input, Row, Select, Tag } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
export default function InvoiceEnterModalLinesComponent({
@@ -11,128 +12,211 @@ export default function InvoiceEnterModalLinesComponent({
}) {
const { t } = useTranslation();
const { setFieldsValue, getFieldsValue } = form;
return (
<Form.List name="invoicelines">
{(fields, { add, remove }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item required={false} key={field.key}>
<div style={{ display: "flex" }}>
<Form.Item
label={t("invoicelines.fields.line_desc")}
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
>
<Select
style={{ width: "300px" }}
onSelect={(value, opt) => {
setFieldsValue({
invoicelines: getFieldsValue([
"invoicelines"
]).invoicelines.map((item, idx) => {
if (idx === index) {
return {
...item,
joblineid: opt.key.includes("noline")
? null
: opt.key
};
}
return item;
})
});
}}
showSearch
>
<Select.Option
key={`${index}noline`}
value={t("invoicelines.labels.other")}
>
{t("invoicelines.labels.other")}
</Select.Option>
{lineData
? lineData.map(item => (
<Select.Option key={item.id} value={item.line_desc}>
{item.line_desc}
</Select.Option>
))
: null}
</Select>
</Form.Item>
{console.log(
'getFieldsValue("invoicelines").invoicelines[index]',
getFieldsValue("invoicelines").invoicelines[index]
)}
{getFieldsValue("invoicelines").invoicelines[index] &&
getFieldsValue("invoicelines").invoicelines[index]
.line_desc &&
!getFieldsValue("invoicelines").invoicelines[index].joblineid
? "Other"
: null}
<Form.Item
label={t("invoicelines.fields.actual")}
key={`${index}actual_price`}
name={[field.name, "actual_price"]}
>
<CurrencyInput
onBlur={e => {
setFieldsValue({
invoicelines: getFieldsValue(
"invoicelines"
).invoicelines.map((item, idx) => {
if (idx === index) {
return {
...item,
actual_cost:
parseFloat(e.target.value.substring(1)) * 1 -
discount
};
}
return item;
const [amounts, setAmounts] = useState({ invoiceTotal: 0, enteredAmount: 0 });
return (
<div>
<Form.List name="invoicelines">
{(fields, { add, remove }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item required={false} key={field.key}>
<div style={{ display: "flex" }}>
<Form.Item
label={t("invoicelines.fields.line_desc")}
key={`${index}joblinename`}
name={[field.name, "joblinename"]}
rules={[
{
required: true,
message: t("general.validation.required")
}
]}
>
<Select
autoFocus
name={`le${index}`}
style={{ width: "300px" }}
onSelect={(value, opt) => {
setFieldsValue({
invoicelines: getFieldsValue([
"invoicelines"
]).invoicelines.map((item, idx) => {
if (idx === index) {
console.log("opt", opt);
return {
...item,
joblineid: opt.key.includes("noline")
? null
: opt.key,
line_desc: opt.key.includes("noline")
? ""
: opt.value,
actual_price: opt.cost ? opt.cost : 0
};
}
return item;
})
});
}}
showSearch
>
<Select.Option
key={`${index}noline`}
value={t("invoicelines.labels.other")}
cost={0}
>
{t("invoicelines.labels.other")}
</Select.Option>
{lineData
? lineData.map(item => (
<Select.Option
key={item.id}
value={item.line_desc}
cost={item.act_price ? item.act_price : 0}
>
{item.line_desc}
</Select.Option>
))
: null}
</Select>
</Form.Item>
{getFieldsValue("invoicelines").invoicelines[index] &&
getFieldsValue("invoicelines").invoicelines[index]
.joblinename &&
!getFieldsValue("invoicelines").invoicelines[index]
.joblineid ? (
<Form.Item
label={t("invoicelines.fields.line_desc")}
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
>
<Input />
</Form.Item>
) : null}
<Form.Item
label={t("invoicelines.fields.actual")}
key={`${index}actual_price`}
name={[field.name, "actual_price"]}
rules={[
{
required: true,
message: t("general.validation.required")
}
]}
>
<CurrencyInput
onBlur={e => {
setFieldsValue({
invoicelines: getFieldsValue(
"invoicelines"
).invoicelines.map((item, idx) => {
if (idx === index) {
return {
...item,
actual_cost:
parseFloat(e.target.value) * (1 - discount)
};
}
return item;
})
});
}}
/>
</Form.Item>
<Form.Item
label={t("invoicelines.fields.actual_cost")}
key={`${index}actual_cost`}
name={[field.name, "actual_cost"]}
rules={[
{
required: true,
message: t("general.validation.required")
}
]}
>
<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
label={t("invoicelines.fields.cost_center")}
key={`${index}cost_center`}
name={[field.name, "cost_center"]}
rules={[
{
required: true,
message: t("general.validation.required")
}
]}
>
<Input />
</Form.Item>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
</Form.Item>
<Form.Item
label={t("invoicelines.fields.actual_cost")}
key={`${index}actual_cost`}
name={[field.name, "actual_cost"]}
>
<CurrencyInput />
</Form.Item>
<Form.Item
label={t("invoicelines.fields.cost_center")}
key={`${index}cost_center`}
name={[field.name, "cost_center"]}
>
<Input />
</Form.Item>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
</div>
</div>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "60%" }}
>
{t("invoicelines.actions.newline")}
</Button>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "60%" }}
>
New liNe
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
</div>
);
}}
</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 { createLogger } from "redux-logger";
import createSagaMiddleware from "redux-saga";
@@ -11,7 +11,19 @@ if (process.env.NODE_ENV === "development") {
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);
export const persistor = persistStore(store);

View File

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

View File

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

View File

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