fix: BIG-148 items entries ordered by index.

This commit is contained in:
a.bouhuolia
2021-10-31 13:24:12 +02:00
parent cbce9f6d50
commit 9211e963c6
16 changed files with 200 additions and 94 deletions

View File

@@ -50,7 +50,6 @@ function InventoryAdjustmentFloatingActions({
</Button>
<Button
disabled={isSubmitting}
loading={isSubmitting && !submitPayload.publish}
style={{ minWidth: '75px' }}
type="submit"
@@ -61,7 +60,6 @@ function InventoryAdjustmentFloatingActions({
<Button
intent={Intent.PRIMARY}
disabled={isSubmitting}
loading={isSubmitting && submitPayload.publish}
style={{ minWidth: '75px' }}
type="submit"

View File

@@ -14,19 +14,12 @@ const PaymentMadeDetailContext = React.createContext();
*/
function PaymentMadeDetailProvider({ paymentMadeId, ...props }) {
// Handle fetch specific payment made details.
const { data: paymentMade, isLoading: isPaymentMadeLoading } =
usePaymentMade(paymentMadeId, {
const { data: paymentMade, isLoading: isPaymentMadeLoading } = usePaymentMade(
paymentMadeId,
{
enabled: !!paymentMadeId,
});
// Handle fetch specific payment made details.
const {
data: { entries: paymentEntries },
isLoading: isPaymentLoading,
} = usePaymentMadeEditPage(paymentMadeId, {
enabled: !!paymentMadeId,
});
},
);
// Handle fetch transaction by reference.
const {
data: { transactions },
@@ -44,11 +37,9 @@ function PaymentMadeDetailProvider({ paymentMadeId, ...props }) {
transactions,
paymentMadeId,
paymentMade,
paymentEntries,
};
const loading =
isTransactionLoading || isPaymentMadeLoading || isPaymentLoading;
const loading = isTransactionLoading || isPaymentMadeLoading;
return (
<DrawerLoading loading={loading}>

View File

@@ -12,18 +12,17 @@ import PaymentDrawerCls from './PaymentMadeDrawer.module.scss';
* Payment made read-only details table.
*/
export default function PaymentMadeDetailTable() {
// Retrieve payment made entries columns.
const columns = usePaymentMadeEntriesColumns();
// Payment made details context.
const { paymentEntries } = usePaymentMadeDetailContext();
const { paymentMade } = usePaymentMadeDetailContext();
return (
<div className={clsx(PaymentDrawerCls.detail_panel_table)}>
<DataTable
columns={columns}
data={paymentEntries}
data={paymentMade.entries}
className={'table-constrant'}
/>
</div>

View File

@@ -8,13 +8,14 @@ import { useEditableItemsEntriesColumns } from './components';
import {
saveInvoke,
compose,
updateTableCell,
updateMinEntriesLines,
updateAutoAddNewLine,
updateRemoveLineByIndex,
} from 'utils';
import { updateItemsEntriesTotal, useFetchItemRow } from './utils';
import { updateTableRow } from '../../utils';
import {
useFetchItemRow,
composeRowsOnNewRow,
composeRowsOnEditCell,
} from './utils';
/**
* Items entries table.
@@ -50,10 +51,7 @@ function ItemsEntriesTable({
itemType,
notifyNewRow: (newRow, rowIndex) => {
// Update the rate, description and quantity data of the row.
const newRows = compose(
updateItemsEntriesTotal,
updateTableRow(rowIndex, newRow),
)(rows);
const newRows = composeRowsOnNewRow(rowIndex, newRow, rows);
setRows(newRows);
onUpdateData(newRows);
@@ -66,11 +64,8 @@ function ItemsEntriesTable({
if (columnId === 'item_id') {
setItemRow({ rowIndex, columnId, itemId: value });
}
const newRows = compose(
updateAutoAddNewLine(defaultEntry, ['item_id']),
updateItemsEntriesTotal,
updateTableCell(rowIndex, columnId, value),
)(rows);
const composeEditCell = composeRowsOnEditCell(rowIndex, columnId);
const newRows = composeEditCell(value, defaultEntry, rows);
setRows(newRows);
onUpdateData(newRows);

View File

@@ -3,7 +3,15 @@ import * as R from 'ramda';
import { sumBy, isEmpty, last } from 'lodash';
import { useItem } from 'hooks/query';
import { toSafeNumber, saveInvoke } from 'utils';
import {
toSafeNumber,
saveInvoke,
compose,
updateTableCell,
updateAutoAddNewLine,
orderingLinesIndexes,
updateTableRow,
} from 'utils';
/**
* Retrieve item entry total from the given rate, quantity and discount.
@@ -131,3 +139,28 @@ export function useFetchItemRow({ landedCost, itemType, notifyNewRow }) {
cellsLoading,
};
}
/**
* Compose table rows when edit specific row index of table rows.
*/
export const composeRowsOnEditCell = R.curry(
(rowIndex, columnId, value, defaultEntry, rows) => {
return compose(
orderingLinesIndexes,
updateAutoAddNewLine(defaultEntry, ['item_id']),
updateItemsEntriesTotal,
updateTableCell(rowIndex, columnId, value),
)(rows);
},
);
/**
* Compose table rows when insert a new row to table rows.
*/
export const composeRowsOnNewRow = R.curry((rowIndex, newRow, rows) => {
return compose(
orderingLinesIndexes,
updateItemsEntriesTotal,
updateTableRow(rowIndex, newRow),
)(rows);
});

View File

@@ -35,6 +35,7 @@ export function useGeneralLedgerTableColumns() {
return row.date;
},
className: 'date',
textOverview: true,
width: 120,
},
{

View File

@@ -14,7 +14,10 @@ import ItemFormBody from './ItemFormBody';
import ItemFormFloatingActions from './ItemFormFloatingActions';
import ItemFormInventorySection from './ItemFormInventorySection';
import { useItemFormInitialValues } from './utils';
import {
transformSubmitRequestErrors,
useItemFormInitialValues,
} from './utils';
import { EditItemFormSchema, CreateItemFormSchema } from './ItemForm.schema';
import { useItemFormContext } from './ItemFormProvider';
@@ -40,27 +43,6 @@ export default function ItemForm() {
// Initial values in create and edit mode.
const initialValues = useItemFormInitialValues(item);
// Transform API errors.
const transformApiErrors = (error) => {
const {
response: {
data: { errors },
},
} = error;
const fields = {};
if (errors.find((e) => e.type === 'ITEM.NAME.ALREADY.EXISTS')) {
fields.name = intl.get('the_name_used_before');
}
if (errors.find((e) => e.type === 'INVENTORY_ACCOUNT_CANNOT_MODIFIED')) {
AppToaster.show({
message: intl.get('cannot_change_item_inventory_account'),
intent: Intent.DANGER,
});
}
return fields;
};
// Handles the form submit.
const handleFormSubmit = (
values,
@@ -94,7 +76,7 @@ export default function ItemForm() {
const onError = (errors) => {
setSubmitting(false);
if (errors) {
const _errors = transformApiErrors(errors);
const _errors = transformSubmitRequestErrors(errors);
setErrors({ ..._errors });
}
};

View File

@@ -1,6 +1,7 @@
import React, { useEffect, createContext, useState } from 'react';
import intl from 'react-intl-universal';
import { useLocation } from 'react-router-dom';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import {
useItem,
@@ -11,6 +12,7 @@ import {
useAccounts,
} from 'hooks/query';
import { useDashboardPageTitle } from 'hooks/state';
import { useWatchItemError } from './utils';
const ItemFormContext = createContext();
@@ -32,12 +34,14 @@ function ItemFormProvider({ itemId, ...props }) {
} = useItemsCategories();
// Fetches the given item details.
const { isLoading: isItemLoading, data: item } = useItem(
itemId || duplicateId,
{
enabled: !!itemId || !!duplicateId,
},
);
const itemQuery = useItem(itemId || duplicateId, {
enabled: !!itemId || !!duplicateId,
});
const { isLoading: isItemLoading, data: item } = itemQuery;
// Watches and handles item not found response error.
useWatchItemError(itemQuery);
// Fetches item settings.
const {

View File

@@ -1,7 +1,8 @@
import { useMemo } from 'react';
import intl from 'react-intl-universal';
import { Intent } from '@blueprintjs/core';
import { defaultTo } from 'lodash';
import { defaultTo, includes } from 'lodash';
import { useHistory } from 'react-router-dom';
import { AppToaster } from 'components';
import {
transformTableStateToQuery,
@@ -10,6 +11,7 @@ import {
import { transformToForm } from 'utils';
import { useSettingsSelector } from '../../hooks/state';
import { transformItemFormData } from './ItemForm.schema';
import { useWatch } from 'hooks/utils';
const defaultInitialValues = {
active: 1,
@@ -182,3 +184,59 @@ export function transformItemsTableState(tableState) {
inactive_mode: tableState.inactiveMode,
};
}
/**
* Transform API errors.
*/
export const transformSubmitRequestErrors = (error) => {
const {
response: {
data: { errors },
},
} = error;
const fields = {};
if (errors.find((e) => e.type === 'ITEM.NAME.ALREADY.EXISTS')) {
fields.name = intl.get('the_name_used_before');
}
if (errors.find((e) => e.type === 'INVENTORY_ACCOUNT_CANNOT_MODIFIED')) {
AppToaster.show({
message: intl.get('cannot_change_item_inventory_account'),
intent: Intent.DANGER,
});
}
if (
errors.find(
(e) => e.type === 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS',
)
) {
AppToaster.show({
message: intl.get(
'item.error.type_cannot_change_with_item_has_transactions',
),
intent: Intent.DANGER,
});
}
return fields;
};
/**
* Watches and handles item response not found error.
* @param {*} itemQuery
*/
export function useWatchItemError(itemQuery) {
const { error, isError } = itemQuery;
// History context.
const history = useHistory();
useWatch(() => {
if (isError && includes([400, 404], error.response.status)) {
AppToaster.show({
message: 'The given item not found.',
intent: Intent.DANGER,
});
history.push('/items');
}
}, isError);
}

View File

@@ -23,7 +23,12 @@ import {
} from './PaymentMadeForm.schema';
import { compose, orderingLinesIndexes } from 'utils';
import { usePaymentMadeFormContext } from './PaymentMadeFormProvider';
import { defaultPaymentMade, transformToEditForm, ERRORS } from './utils';
import {
defaultPaymentMade,
transformToEditForm,
ERRORS,
transformFormToRequest,
} from './utils';
/**
* Payment made form component.
@@ -71,15 +76,8 @@ function PaymentMadeForm({
{ setSubmitting, resetForm, setFieldError },
) => {
setSubmitting(true);
// Filters entries that have no `bill_id` or `payment_amount`.
const entries = values.entries
.filter((item) => item.bill_id && item.payment_amount)
.map((entry) => ({
...pick(entry, ['payment_amount', 'bill_id']),
}));
// Total payment amount of entries.
const totalPaymentAmount = sumBy(entries, 'payment_amount');
const totalPaymentAmount = sumBy(values.entries, 'payment_amount');
if (totalPaymentAmount <= 0) {
AppToaster.show({
@@ -88,7 +86,8 @@ function PaymentMadeForm({
});
return;
}
const form = { ...values, entries };
// Transformes the form values to request body.
const form = transformFormToRequest(values);
// Triggers once the save request success.
const onSaved = (response) => {

View File

@@ -1,8 +1,10 @@
import moment from 'moment';
import { pick } from 'lodash';
import {
defaultFastFieldShouldUpdate,
safeSumBy,
transformToForm,
orderingLinesIndexes,
} from 'utils';
export const ERRORS = {
@@ -57,7 +59,7 @@ export const transformToNewPageEntries = (entries) => {
};
/**
* Detarmines vendors fast field when update.
* Detarmines vendors fast field when update.
*/
export const vendorsFieldShouldUpdate = (newProps, oldProps) => {
return (
@@ -75,3 +77,17 @@ export const accountsFieldShouldUpdate = (newProps, oldProps) => {
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Transformes the form values to request body.
*/
export const transformFormToRequest = (form) => {
// Filters entries that have no `bill_id` or `payment_amount`.
const entries = form.entries
.filter((item) => item.bill_id && item.payment_amount)
.map((entry) => ({
...pick(entry, ['payment_amount', 'bill_id']),
}));
return { ...form, entries: orderingLinesIndexes(entries) };
};

View File

@@ -2,7 +2,7 @@ import React, { useMemo } from 'react';
import { Formik, Form } from 'formik';
import { Intent } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import { sumBy, omit, isEmpty } from 'lodash';
import { sumBy, isEmpty } from 'lodash';
import classNames from 'classnames';
import { useHistory } from 'react-router-dom';
import { CLASSES } from 'common/classes';
@@ -144,7 +144,10 @@ function InvoiceForm({
}
};
// Create invoice form schema.
const CreateInvoiceFormSchema = getCreateInvoiceFormSchema();
// Edit invoice form schema.
const EditInvoiceFormSchema = getEditInvoiceFormSchema();
return (

View File

@@ -73,6 +73,7 @@ function InvoiceFormProvider({ invoiceId, ...props }) {
customers,
newInvoice,
estimateId,
invoiceId,
submitPayload,
isInvoiceLoading,

View File

@@ -28,7 +28,7 @@ import { AppToaster } from 'components';
import { transactionNumber, compose } from 'utils';
import { usePaymentReceiveFormContext } from './PaymentReceiveFormProvider';
import { defaultPaymentReceive, transformToEditForm } from './utils';
import { defaultPaymentReceive, transformToEditForm, transformFormToRequest } from './utils';
/**
* Payment Receive form.
@@ -91,15 +91,8 @@ function PaymentReceiveForm({
) => {
setSubmitting(true);
// Filters entries that have no `invoice_id` and `payment_amount`.
const entries = values.entries
.filter((entry) => entry.invoice_id && entry.payment_amount)
.map((entry) => ({
...pick(entry, ['invoice_id', 'payment_amount']),
}));
// Calculates the total payment amount of entries.
const totalPaymentAmount = sumBy(entries, 'payment_amount');
const totalPaymentAmount = sumBy(values.entries, 'payment_amount');
if (totalPaymentAmount <= 0) {
AppToaster.show({
@@ -108,13 +101,8 @@ function PaymentReceiveForm({
});
return;
}
const form = {
...omit(values, ['payment_receive_no_manually', 'payment_receive_no']),
...(values.payment_receive_no_manually && {
payment_receive_no: values.payment_receive_no,
}),
entries,
};
// Transformes the form values to request body.
const form = transformFormToRequest(values);
// Handle request response success.
const onSaved = (response) => {

View File

@@ -1,15 +1,18 @@
import React from 'react';
import { useFormikContext } from 'formik';
import moment from 'moment';
import { omit, pick } from 'lodash';
import {
defaultFastFieldShouldUpdate,
transactionNumber,
transformToForm,
safeSumBy,
orderingLinesIndexes
} from 'utils';
// Default payment receive entry.
export const defaultPaymentReceiveEntry = {
index: '',
payment_amount: '',
invoice_id: '',
invoice_no: '',
@@ -32,8 +35,23 @@ export const defaultPaymentReceive = {
entries: [],
};
export const defaultRequestPaymentEntry = {
index: '',
payment_amount: '',
invoice_id: '',
};
export const defaultRequestPayment = {
customer_id: '',
deposit_account_id: '',
payment_date: '',
payment_receive_no: '',
statement: '',
entries: []
};
/**
*
* Transformes the edit payment receive to initial values of the form.
*/
export const transformToEditForm = (paymentReceive, paymentReceiveEntries) => ({
...transformToForm(paymentReceive, defaultPaymentReceive),
@@ -124,3 +142,23 @@ export const accountsFieldShouldUpdate = (newProps, oldProps) => {
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Tranformes form values to request.
*/
export const transformFormToRequest = (form) => {
// Filters entries that have no `invoice_id` and `payment_amount`.
const entries = form.entries
.filter((entry) => entry.invoice_id && entry.payment_amount)
.map((entry) => ({
...pick(entry, Object.keys(defaultRequestPaymentEntry)),
}));
return {
...omit(form, ['payment_receive_no_manually', 'payment_receive_no']),
...(form.payment_receive_no_manually && {
payment_receive_no: form.payment_receive_no,
}),
entries: orderingLinesIndexes(entries),
};
};

View File

@@ -852,7 +852,7 @@ function keyIdentity(key) {
return key;
}
export function flatten(target, opts) {
export function flatten(opts, target) {
opts = opts || {};
const delimiter = opts.delimiter || '.';