mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
fix: BIG-148 items entries ordered by index.
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -35,6 +35,7 @@ export function useGeneralLedgerTableColumns() {
|
||||
return row.date;
|
||||
},
|
||||
className: 'date',
|
||||
textOverview: true,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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) };
|
||||
};
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -73,6 +73,7 @@ function InvoiceFormProvider({ invoiceId, ...props }) {
|
||||
customers,
|
||||
newInvoice,
|
||||
estimateId,
|
||||
invoiceId,
|
||||
submitPayload,
|
||||
|
||||
isInvoiceLoading,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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 || '.';
|
||||
|
||||
Reference in New Issue
Block a user