BIG-125: prevent items entries from send amount computed attribute.

This commit is contained in:
a.bouhuolia
2021-09-28 18:37:10 +02:00
parent 96269ccafb
commit 0aca6d9af7
10 changed files with 225 additions and 100 deletions

View File

@@ -6,7 +6,6 @@ import { Formik, Form } from 'formik';
import classNames from 'classnames'; import classNames from 'classnames';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
import * as R from 'ramda';
import ExpenseFormHeader from './ExpenseFormHeader'; import ExpenseFormHeader from './ExpenseFormHeader';
import ExpenseFormBody from './ExpenseFormBody'; import ExpenseFormBody from './ExpenseFormBody';
@@ -25,8 +24,13 @@ import {
CreateExpenseFormSchema, CreateExpenseFormSchema,
EditExpenseFormSchema, EditExpenseFormSchema,
} from './ExpenseForm.schema'; } from './ExpenseForm.schema';
import { transformErrors, defaultExpense, transformToEditForm } from './utils'; import {
import { compose, orderingLinesIndexes } from 'utils'; transformErrors,
defaultExpense,
transformToEditForm,
transformFormValuesToRequest,
} from './utils';
import { compose } from 'utils';
/** /**
* Expense form. * Expense form.
@@ -79,15 +83,10 @@ function ExpenseForm({
}); });
return; return;
} }
// Filter expense entries that has no amount or expense account.
const categories = values.categories.filter(
(category) => category.amount && category.expense_account_id,
);
const form = { const form = {
...values, ...transformFormValuesToRequest(values),
publish: submitPayload.publish, publish: submitPayload.publish,
categories: R.compose(orderingLinesIndexes)(categories),
}; };
// Handle request success. // Handle request success.
const handleSuccess = (response) => { const handleSuccess = (response) => {

View File

@@ -8,6 +8,7 @@ import {
transformToForm, transformToForm,
repeatValue, repeatValue,
ensureEntriesHasEmptyLine, ensureEntriesHasEmptyLine,
orderingLinesIndexes
} from 'utils'; } from 'utils';
const ERROR = { const ERROR = {
@@ -104,3 +105,25 @@ export const accountsFieldShouldUpdate = (newProps, oldProps) => {
defaultFastFieldShouldUpdate(newProps, oldProps) defaultFastFieldShouldUpdate(newProps, oldProps)
); );
}; };
/**
* Filter expense entries that has no amount or expense account.
*/
export const filterNonZeroEntries = (categories) => {
return categories.filter(
(category) => category.amount && category.expense_account_id,
);
}
/**
* Transformes the form values to request body.
*/
export const transformFormValuesToRequest = (values) => {
const categories = filterNonZeroEntries(values.categories);
return {
...values,
categories: R.compose(orderingLinesIndexes)(categories),
};
};

View File

@@ -2,7 +2,6 @@ import React, { useMemo } from 'react';
import { Formik, Form } from 'formik'; import { Formik, Form } from 'formik';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
import classNames from 'classnames'; import classNames from 'classnames';
import * as R from 'ramda';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
@@ -16,13 +15,14 @@ import BillItemsEntriesEditor from './BillItemsEntriesEditor';
import { AppToaster } from 'components'; import { AppToaster } from 'components';
import { ERROR } from 'common/errors';
import { useBillFormContext } from './BillFormProvider'; import { useBillFormContext } from './BillFormProvider';
import { compose, safeSumBy } from 'utils'; import { compose, safeSumBy } from 'utils';
import { import {
defaultBill, defaultBill,
filterNonZeroEntries,
transformToEditForm, transformToEditForm,
transformEntriesToSubmit, transformFormValuesToRequest,
handleErrors,
} from './utils'; } from './utils';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization'; import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
@@ -55,35 +55,12 @@ function BillForm({
[bill, base_currency], [bill, base_currency],
); );
// Transform response error to fields.
const handleErrors = (errors, { setErrors }) => {
if (errors.some((e) => e.type === ERROR.BILL_NUMBER_EXISTS)) {
setErrors({
bill_number: intl.get('bill_number_exists'),
});
}
if (
errors.some(
(e) => e.type === ERROR.ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED,
)
) {
setErrors(
AppToaster.show({
intent: Intent.DANGER,
message: 'ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED',
}),
);
}
};
// Handles form submit. // Handles form submit.
const handleFormSubmit = ( const handleFormSubmit = (
values, values,
{ setSubmitting, setErrors, resetForm }, { setSubmitting, setErrors, resetForm },
) => { ) => {
const entries = values.entries.filter( const entries = filterNonZeroEntries(values.entries);
(item) => item.item_id && item.quantity,
);
const totalQuantity = safeSumBy(entries, 'quantity'); const totalQuantity = safeSumBy(entries, 'quantity');
if (totalQuantity === 0) { if (totalQuantity === 0) {
@@ -95,9 +72,8 @@ function BillForm({
return; return;
} }
const form = { const form = {
...values, ...transformFormValuesToRequest(values),
open: submitPayload.status, open: submitPayload.status,
entries: transformEntriesToSubmit(entries),
}; };
// Handle the request success. // Handle the request success.
const onSuccess = (response) => { const onSuccess = (response) => {

View File

@@ -41,6 +41,12 @@ export const defaultBill = {
entries: [...repeatValue(defaultBillEntry, MIN_LINES_NUMBER)], entries: [...repeatValue(defaultBillEntry, MIN_LINES_NUMBER)],
}; };
export const ERRORS = {
// Bills
BILL_NUMBER_EXISTS: 'BILL.NUMBER.EXISTS',
ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED:
'ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED',
};
/** /**
* Transformes the bill to initial values of edit form. * Transformes the bill to initial values of edit form.
*/ */
@@ -70,11 +76,33 @@ export const transformToEditForm = (bill) => {
* Transformes bill entries to submit request. * Transformes bill entries to submit request.
*/ */
export const transformEntriesToSubmit = (entries) => { export const transformEntriesToSubmit = (entries) => {
const transformBillEntry = R.curry(transformToForm)(R.__, defaultBillEntry); const transformBillEntry = R.compose(
R.omit(['amount']),
R.curry(transformToForm)(R.__, defaultBillEntry),
);
return R.compose(orderingLinesIndexes, R.map(transformBillEntry))(entries); return R.compose(orderingLinesIndexes, R.map(transformBillEntry))(entries);
}; };
/**
* Filters the givne non-zero entries.
*/
export const filterNonZeroEntries = (entries) => {
return entries.filter((item) => item.item_id && item.quantity);
};
/**
* Transformes form values to request body.
*/
export const transformFormValuesToRequest = (values) => {
const entries = filterNonZeroEntries(values.entries);
return {
...values,
entries: transformEntriesToSubmit(entries),
open: false,
};
};
/** /**
* Handle delete errors. * Handle delete errors.
*/ */
@@ -118,3 +146,24 @@ export const entriesFieldShouldUpdate = (newProps, oldProps) => {
defaultFastFieldShouldUpdate(newProps, oldProps) defaultFastFieldShouldUpdate(newProps, oldProps)
); );
}; };
// Transform response error to fields.
export const handleErrors = (errors, { setErrors }) => {
if (errors.some((e) => e.type === ERRORS.BILL_NUMBER_EXISTS)) {
setErrors({
bill_number: intl.get('bill_number_exists'),
});
}
if (
errors.some(
(e) => e.type === ERRORS.ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED,
)
) {
setErrors(
AppToaster.show({
intent: Intent.DANGER,
message: 'ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED',
}),
);
}
};

View File

@@ -1,9 +1,8 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { Formik, Form } from 'formik'; import { Formik, Form } from 'formik';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { omit, sumBy, isEmpty } from 'lodash'; import { sumBy, isEmpty } from 'lodash';
import classNames from 'classnames'; import classNames from 'classnames';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
@@ -23,10 +22,14 @@ import withSettings from 'containers/Settings/withSettings';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization'; import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
import { AppToaster } from 'components'; import { AppToaster } from 'components';
import { ERROR } from 'common/errors';
import { compose, transactionNumber, orderingLinesIndexes } from 'utils'; import { compose, transactionNumber, orderingLinesIndexes } from 'utils';
import { useEstimateFormContext } from './EstimateFormProvider'; import { useEstimateFormContext } from './EstimateFormProvider';
import { transformToEditForm, defaultEstimate } from './utils'; import {
transformToEditForm,
defaultEstimate,
transfromsFormValuesToRequest,
handleErrors
} from './utils';
/** /**
* Estimate form. * Estimate form.
@@ -68,25 +71,9 @@ function EstimateForm({
currency_code: base_currency, currency_code: base_currency,
}), }),
}), }),
[estimate, estimateNumber, estimateIncrementMode], [estimate, estimateNumber, estimateIncrementMode, base_currency],
); );
// Transform response errors to fields.
const handleErrors = (errors, { setErrors }) => {
if (errors.some((e) => e.type === ERROR.ESTIMATE_NUMBER_IS_NOT_UNQIUE)) {
setErrors({
estimate_number: intl.get('estimate_number_is_not_unqiue'),
});
}
if (
errors.some((error) => error.type === ERROR.SALE_ESTIMATE_NO_IS_REQUIRED)
) {
setErrors({
estimate_number: intl.get('estimate.field.error.estimate_number_required'),
});
}
};
// Handles form submit. // Handles form submit.
const handleFormSubmit = ( const handleFormSubmit = (
values, values,
@@ -109,13 +96,10 @@ function EstimateForm({
return; return;
} }
const form = { const form = {
...omit(values, ['estimate_number_manually', 'estimate_number']), ...transfromsFormValuesToRequest(values),
...(values.estimate_number_manually && {
estimate_number: values.estimate_number,
}),
delivered: submitPayload.deliver, delivered: submitPayload.deliver,
entries: entries.map((entry) => ({ ...omit(entry, ['total']) })),
}; };
// Handle the request success.
const onSuccess = (response) => { const onSuccess = (response) => {
AppToaster.show({ AppToaster.show({
message: intl.get( message: intl.get(
@@ -135,7 +119,7 @@ function EstimateForm({
resetForm(); resetForm();
} }
}; };
// Handle the request error.
const onError = ({ const onError = ({
response: { response: {
data: { errors }, data: { errors },

View File

@@ -2,6 +2,8 @@ import React from 'react';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import moment from 'moment'; import moment from 'moment';
import * as R from 'ramda'; import * as R from 'ramda';
import { omit } from 'lodash';
import intl from 'react-intl-universal';
import { import {
defaultFastFieldShouldUpdate, defaultFastFieldShouldUpdate,
transactionNumber, transactionNumber,
@@ -37,6 +39,11 @@ export const defaultEstimate = {
entries: [...repeatValue(defaultEstimateEntry, MIN_LINES_NUMBER)], entries: [...repeatValue(defaultEstimateEntry, MIN_LINES_NUMBER)],
}; };
const ERRORS = {
ESTIMATE_NUMBER_IS_NOT_UNQIUE: 'ESTIMATE.NUMBER.IS.NOT.UNQIUE',
SALE_ESTIMATE_NO_IS_REQUIRED: 'SALE_ESTIMATE_NO_IS_REQUIRED',
};
export const transformToEditForm = (estimate) => { export const transformToEditForm = (estimate) => {
const initialEntries = [ const initialEntries = [
...estimate.entries.map((estimate) => ({ ...estimate.entries.map((estimate) => ({
@@ -54,8 +61,8 @@ export const transformToEditForm = (estimate) => {
return { return {
...transformToForm(estimate, defaultEstimate), ...transformToForm(estimate, defaultEstimate),
entries entries,
} };
}; };
/** /**
@@ -106,3 +113,41 @@ export const ITEMS_FILTER_ROLES = JSON.stringify([
comparator: 'equals', comparator: 'equals',
}, },
]); ]);
/**
* Transform response errors to fields.
* @param {*} errors
* @param {*} param1
*/
export const handleErrors = (errors, { setErrors }) => {
if (errors.some((e) => e.type === ERRORS.ESTIMATE_NUMBER_IS_NOT_UNQIUE)) {
setErrors({
estimate_number: intl.get('estimate_number_is_not_unqiue'),
});
}
if (
errors.some((error) => error.type === ERRORS.SALE_ESTIMATE_NO_IS_REQUIRED)
) {
setErrors({
estimate_number: intl.get(
'estimate.field.error.estimate_number_required',
),
});
}
};
/**
* Transform the form values to request body.
*/
export const transfromsFormValuesToRequest = (values) => {
const entries = values.entries.filter(
(item) => item.item_id && item.quantity,
);
return {
...omit(values, ['estimate_number_manually', 'estimate_number']),
...(values.estimate_number_manually && {
estimate_number: values.estimate_number,
}),
entries: entries.map((entry) => ({ ...omit(entry, ['amount']) })),
};
};

View File

@@ -25,7 +25,12 @@ import withCurrentOrganization from 'containers/Organization/withCurrentOrganiza
import { AppToaster } from 'components'; import { AppToaster } from 'components';
import { compose, orderingLinesIndexes, transactionNumber } from 'utils'; import { compose, orderingLinesIndexes, transactionNumber } from 'utils';
import { useInvoiceFormContext } from './InvoiceFormProvider'; import { useInvoiceFormContext } from './InvoiceFormProvider';
import { transformToEditForm, defaultInvoice, transformErrors } from './utils'; import {
transformToEditForm,
defaultInvoice,
transformErrors,
transformValueToRequest,
} from './utils';
/** /**
* Invoice form. * Invoice form.
@@ -72,7 +77,7 @@ function InvoiceForm({
currency_code: base_currency, currency_code: base_currency,
}), }),
}), }),
[invoice, newInvoice, invoiceNumber, invoiceIncrementMode], [invoice, newInvoice, invoiceNumber, invoiceIncrementMode, base_currency],
); );
// Handles form submit. // Handles form submit.
@@ -93,15 +98,13 @@ function InvoiceForm({
setSubmitting(false); setSubmitting(false);
return; return;
} }
// Transformes the values of the form to request.
const form = { const form = {
...omit(values, ['invoice_no', 'invoice_no_manually']), ...transformValueToRequest(values),
...(values.invoice_no_manually && {
invoice_no: values.invoice_no,
}),
delivered: submitPayload.deliver, delivered: submitPayload.deliver,
from_estimate_id: estimateId, from_estimate_id: estimateId,
entries: entries.map((entry) => ({ ...omit(entry, ['total']) })),
}; };
// Handle the request success. // Handle the request success.
const onSuccess = () => { const onSuccess = () => {
AppToaster.show({ AppToaster.show({

View File

@@ -1,5 +1,7 @@
import React from 'react'; import React from 'react';
import moment from 'moment'; import moment from 'moment';
import { Intent } from '@blueprintjs/core';
import { omit } from 'lodash';
import { import {
compose, compose,
transformToForm, transformToForm,
@@ -7,7 +9,6 @@ import {
transactionNumber, transactionNumber,
} from 'utils'; } from 'utils';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import { Intent } from '@blueprintjs/core';
import { defaultFastFieldShouldUpdate } from 'utils'; import { defaultFastFieldShouldUpdate } from 'utils';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
@@ -146,3 +147,20 @@ export const ITEMS_FILTER_ROLES_QUERY = JSON.stringify([
comparator: 'equals', comparator: 'equals',
}, },
]); ]);
/**
* Transformes the form values to request body values.
*/
export function transformValueToRequest(values) {
const entries = values.entries.filter(
(item) => item.item_id && item.quantity,
);
return {
...omit(values, ['invoice_no', 'invoice_no_manually']),
...(values.invoice_no_manually && {
invoice_no: values.invoice_no,
}),
entries: entries.map((entry) => ({ ...omit(entry, ['amount']) })),
delivered: false,
};
}

View File

@@ -2,12 +2,11 @@ import React, { useMemo } from 'react';
import { Formik, Form } from 'formik'; import { Formik, Form } from 'formik';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { omit, sumBy, isEmpty } from 'lodash'; import { sumBy, isEmpty } from 'lodash';
import classNames from 'classnames'; import classNames from 'classnames';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
import { ERROR } from 'common/errors';
import { import {
EditReceiptFormSchema, EditReceiptFormSchema,
CreateReceiptFormSchema, CreateReceiptFormSchema,
@@ -27,7 +26,12 @@ import withCurrentOrganization from 'containers/Organization/withCurrentOrganiza
import { AppToaster } from 'components'; import { AppToaster } from 'components';
import { compose, orderingLinesIndexes, transactionNumber } from 'utils'; import { compose, orderingLinesIndexes, transactionNumber } from 'utils';
import { transformToEditForm, defaultReceipt } from './utils'; import {
transformToEditForm,
defaultReceipt,
handleErrors,
transformFormValuesToRequest,
} from './utils';
/** /**
* Receipt form. * Receipt form.
@@ -76,20 +80,6 @@ function ReceiptForm({
[receipt, preferredDepositAccount, nextReceiptNumber, receiptAutoIncrement], [receipt, preferredDepositAccount, nextReceiptNumber, receiptAutoIncrement],
); );
// Transform response error to fields.
const handleErrors = (errors, { setErrors }) => {
if (errors.some((e) => e.type === ERROR.SALE_RECEIPT_NUMBER_NOT_UNIQUE)) {
setErrors({
receipt_number: intl.get('sale_receipt_number_not_unique'),
});
}
if (errors.some((e) => e.type === ERROR.SALE_RECEIPT_NO_IS_REQUIRED)) {
setErrors({
receipt_number: intl.get('receipt.field.error.receipt_number_required'),
});
}
};
// Handle the form submit. // Handle the form submit.
const handleFormSubmit = ( const handleFormSubmit = (
values, values,
@@ -109,13 +99,8 @@ function ReceiptForm({
return; return;
} }
const form = { const form = {
...omit(values, ['receipt_number_manually', 'receipt_number']), ...transformFormValuesToRequest(values),
...(values.receipt_number_manually && {
receipt_number: values.receipt_number,
currency_code: base_currency,
}),
closed: submitPayload.status, closed: submitPayload.status,
entries: entries.map((entry) => ({ ...omit(entry, ['total']) })),
}; };
// Handle the request success. // Handle the request success.
const onSuccess = (response) => { const onSuccess = (response) => {

View File

@@ -2,6 +2,8 @@ import React from 'react';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import moment from 'moment'; import moment from 'moment';
import * as R from 'ramda'; import * as R from 'ramda';
import intl from 'react-intl-universal';
import { omit } from 'lodash';
import { import {
defaultFastFieldShouldUpdate, defaultFastFieldShouldUpdate,
transactionNumber, transactionNumber,
@@ -37,6 +39,11 @@ export const defaultReceipt = {
entries: [...repeatValue(defaultReceiptEntry, MIN_LINES_NUMBER)], entries: [...repeatValue(defaultReceiptEntry, MIN_LINES_NUMBER)],
}; };
const ERRORS = {
SALE_RECEIPT_NUMBER_NOT_UNIQUE: 'SALE_RECEIPT_NUMBER_NOT_UNIQUE',
SALE_RECEIPT_NO_IS_REQUIRED: 'SALE_RECEIPT_NO_IS_REQUIRED',
};
/** /**
* Transform to form in edit mode. * Transform to form in edit mode.
*/ */
@@ -99,3 +106,39 @@ export const customersFieldShouldUpdate = (newProps, oldProps) => {
defaultFastFieldShouldUpdate(newProps, oldProps) defaultFastFieldShouldUpdate(newProps, oldProps)
); );
}; };
/**
* Transform response error to fields.
*/
export const handleErrors = (errors, { setErrors }) => {
if (errors.some((e) => e.type === ERRORS.SALE_RECEIPT_NUMBER_NOT_UNIQUE)) {
setErrors({
receipt_number: intl.get('sale_receipt_number_not_unique'),
});
}
if (errors.some((e) => e.type === ERRORS.SALE_RECEIPT_NO_IS_REQUIRED)) {
setErrors({
receipt_number: intl.get('receipt.field.error.receipt_number_required'),
});
}
};
/**
* Transformes the form values to request body.
* @param {*} values
* @returns
*/
export const transformFormValuesToRequest = (values) => {
const entries = values.entries.filter(
(item) => item.item_id && item.quantity,
);
return {
...omit(values, ['receipt_number_manually', 'receipt_number']),
...(values.receipt_number_manually && {
receipt_number: values.receipt_number,
}),
entries: entries.map((entry) => ({ ...omit(entry, ['amount']) })),
closed: false,
};
};