diff --git a/client/src/components/DataTableCells/CheckBoxFieldCell.js b/client/src/components/DataTableCells/CheckBoxFieldCell.js index 15772baa2..7391a3a1d 100644 --- a/client/src/components/DataTableCells/CheckBoxFieldCell.js +++ b/client/src/components/DataTableCells/CheckBoxFieldCell.js @@ -1,27 +1,32 @@ import React from 'react'; import classNames from 'classnames'; +import { get } from 'lodash'; import { Classes, Checkbox, FormGroup, Intent } from '@blueprintjs/core'; const CheckboxEditableCell = ({ - row: { index }, - column: { id }, + row: { index, original }, + column: { id, disabledAccessor, checkboxProps }, cell: { value: initialValue }, payload, }) => { const [value, setValue] = React.useState(initialValue); const onChange = (e) => { - setValue(e.target.checked); - }; - const onBlur = () => { - payload.updateData(index, id, value); + const newValue = e.target.checked; + + setValue(newValue); + payload.updateData(index, id, newValue); }; + React.useEffect(() => { setValue(initialValue); }, [initialValue]); const error = payload.errors?.[index]?.[id]; + // Detarmines whether the checkbox is disabled. + const disabled = disabledAccessor ? get(original, disabledAccessor) : false; + return ( ); diff --git a/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesTable.js b/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesTable.js index 5828aa31f..89b2fbd8f 100644 --- a/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesTable.js +++ b/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesTable.js @@ -6,7 +6,7 @@ import { updateMinEntriesLines, updateRemoveLineByIndex, updateAutoAddNewLine, - updateTableRow, + updateTableCell, } from 'utils'; import { useMakeJournalFormContext } from './MakeJournalProvider'; import { useJournalTableEntriesColumns } from './components'; @@ -38,7 +38,7 @@ export default function MakeJournalEntriesTable({ // Update items entries total. updateAdjustEntries(rowIndex, columnId, value), // Update entry of the given row index and column id. - updateTableRow(rowIndex, columnId, value), + updateTableCell(rowIndex, columnId, value), )(entries); saveInvoke(onChange, newRows); diff --git a/client/src/containers/Accounting/MakeJournal/utils.js b/client/src/containers/Accounting/MakeJournal/utils.js index 2c39d14eb..67bb68990 100644 --- a/client/src/containers/Accounting/MakeJournal/utils.js +++ b/client/src/containers/Accounting/MakeJournal/utils.js @@ -5,7 +5,7 @@ import moment from 'moment'; import * as R from 'ramda'; import { transactionNumber, - updateTableRow, + updateTableCell, repeatValue, transformToForm, defaultFastFieldShouldUpdate, @@ -100,10 +100,10 @@ export const updateAdjustEntries = (rowIndex, columnId, value) => (rows) => { const adjustment = adjustmentEntries(rows); if (adjustment.credit) { - newRows = updateTableRow(rowIndex, 'credit', adjustment.credit)(newRows); + newRows = updateTableCell(rowIndex, 'credit', adjustment.credit)(newRows); } if (adjustment.debit) { - newRows = updateTableRow(rowIndex, 'debit', adjustment.debit)(newRows); + newRows = updateTableCell(rowIndex, 'debit', adjustment.debit)(newRows); } } return newRows; diff --git a/client/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostEntriesTable.js b/client/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostEntriesTable.js index cb0b41aaa..bd307ddbb 100644 --- a/client/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostEntriesTable.js +++ b/client/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostEntriesTable.js @@ -1,7 +1,7 @@ import React from 'react'; import intl from 'react-intl-universal'; import { MoneyFieldCell, DataTableEditable } from 'components'; -import { compose, updateTableRow } from 'utils'; +import { compose, updateTableCell } from 'utils'; /** * Allocate landed cost entries table. @@ -51,7 +51,7 @@ export default function AllocateLandedCostEntriesTable({ // Handle update data. const handleUpdateData = React.useCallback( (rowIndex, columnId, value) => { - const newRows = compose(updateTableRow(rowIndex, columnId, value))( + const newRows = compose(updateTableCell(rowIndex, columnId, value))( entries, ); onUpdateData(newRows); diff --git a/client/src/containers/Entries/ItemsEntriesTable.js b/client/src/containers/Entries/ItemsEntriesTable.js index b24207136..fae35fe87 100644 --- a/client/src/containers/Entries/ItemsEntriesTable.js +++ b/client/src/containers/Entries/ItemsEntriesTable.js @@ -1,6 +1,5 @@ import React, { useEffect, useCallback } from 'react'; import classNames from 'classnames'; -import { useItem } from 'hooks/query'; import { CLASSES } from 'common/classes'; import { DataTableEditable } from 'components'; @@ -9,12 +8,13 @@ import { useEditableItemsEntriesColumns } from './components'; import { saveInvoke, compose, - updateTableRow, + updateTableCell, updateMinEntriesLines, updateAutoAddNewLine, updateRemoveLineByIndex, } from 'utils'; -import { updateItemsEntriesTotal, ITEM_TYPE } from './utils'; +import { updateItemsEntriesTotal, useFetchItemRow } from './utils'; +import { updateTableRow } from '../../utils'; /** * Items entries table. @@ -30,62 +30,9 @@ function ItemsEntriesTable({ linesNumber, currencyCode, itemType, // sellable or purchasable - landedCost = false + landedCost = false, }) { const [rows, setRows] = React.useState(initialEntries); - const [rowItem, setRowItem] = React.useState(null); - const [cellsLoading, setCellsLoading] = React.useState(null); - - // Fetches the item details. - const { - data: item, - isFetching: isItemFetching, - isSuccess: isItemSuccess, - } = useItem(rowItem && rowItem.itemId, { - enabled: !!(rowItem && rowItem.itemId), - }); - - // Once the item start loading give the table cells loading state. - useEffect(() => { - if (rowItem && isItemFetching) { - setCellsLoading([ - [rowItem.rowIndex, 'rate'], - [rowItem.rowIndex, 'description'], - [rowItem.rowIndex, 'quantity'], - [rowItem.rowIndex, 'discount'], - ]); - } else { - setCellsLoading(null); - } - }, [isItemFetching, setCellsLoading, rowItem]); - - // Once the item selected and fetched set the initial details to the table. - useEffect(() => { - if (isItemSuccess && item && rowItem) { - const { rowIndex } = rowItem; - const price = - itemType === ITEM_TYPE.PURCHASABLE - ? item.cost_price - : item.sell_price; - - const description = - itemType === ITEM_TYPE.PURCHASABLE - ? item.cost_description - : item.sell_description; - - // Update the rate, description and quantity data of the row. - const newRows = compose( - updateItemsEntriesTotal, - updateTableRow(rowIndex, 'rate', price), - updateTableRow(rowIndex, 'description', description), - updateTableRow(rowIndex, 'quantity', 1), - )(rows); - - setRows(newRows); - setRowItem(null); - saveInvoke(onUpdateData, newRows); - } - }, [item, rowItem, rows, itemType, onUpdateData, isItemSuccess]); // Allows to observes `entries` to make table rows outside controlled. useEffect(() => { @@ -97,22 +44,38 @@ function ItemsEntriesTable({ // Editiable items entries columns. const columns = useEditableItemsEntriesColumns({ landedCost }); - // Handles the editor data update. - const handleUpdateData = useCallback( - (rowIndex, columnId, value) => { - if (columnId === 'item_id') { - setRowItem({ rowIndex, columnId, itemId: value }); - } + // Handle the fetch item row details. + const { setItemRow, cellsLoading, isItemFetching } = useFetchItemRow({ + landedCost, + itemType, + notifyNewRow: (newRow, rowIndex) => { + // Update the rate, description and quantity data of the row. const newRows = compose( - updateAutoAddNewLine(defaultEntry, ['item_id']), updateItemsEntriesTotal, - updateTableRow(rowIndex, columnId, value), + updateTableRow(rowIndex, newRow), )(rows); setRows(newRows); onUpdateData(newRows); }, - [rows, defaultEntry, onUpdateData], + }); + + // Handles the editor data update. + const handleUpdateData = useCallback( + (rowIndex, columnId, value) => { + if (columnId === 'item_id') { + setItemRow({ rowIndex, columnId, itemId: value }); + } + const newRows = compose( + updateAutoAddNewLine(defaultEntry, ['item_id']), + updateItemsEntriesTotal, + updateTableCell(rowIndex, columnId, value), + )(rows); + + setRows(newRows); + onUpdateData(newRows); + }, + [rows, defaultEntry, onUpdateData, setItemRow], ); // Handle table rows removing by index. diff --git a/client/src/containers/Entries/components.js b/client/src/containers/Entries/components.js index 68992f8e0..85c057e04 100644 --- a/client/src/containers/Entries/components.js +++ b/client/src/containers/Entries/components.js @@ -179,6 +179,7 @@ export function useEditableItemsEntriesColumns({ landedCost }) { accessor: 'landed_cost', Cell: CheckBoxFieldCell, width: 100, + disabledAccessor: 'landed_cost_disabled', disableSortBy: true, disableResizing: true, className: 'landed-cost', diff --git a/client/src/containers/Entries/utils.js b/client/src/containers/Entries/utils.js index 4cc79a8b4..a0dd5981e 100644 --- a/client/src/containers/Entries/utils.js +++ b/client/src/containers/Entries/utils.js @@ -1,6 +1,9 @@ +import React from 'react'; +import * as R from 'ramda'; import { sumBy, isEmpty, last } from 'lodash'; -import * as R from 'ramda'; -import { toSafeNumber } from 'utils'; + +import { useItem } from 'hooks/query'; +import { toSafeNumber, saveInvoke } from 'utils'; /** * Retrieve item entry total from the given rate, quantity and discount. @@ -52,4 +55,79 @@ export const ensureEntriesHaveEmptyLine = R.curry((defaultEntry, entries) => { return [...entries, defaultEntry]; } return entries; -}); \ No newline at end of file +}); + +export const isLandedCostDisabled = (item) => + ['service', 'non-inventory'].indexOf(item.type) !== -1; + +/** + * Handle fetch item row details and retrieves the new table row. + */ +export function useFetchItemRow({ landedCost, itemType, notifyNewRow }) { + const [itemRow, setItemRow] = React.useState(null); + const [cellsLoading, setCellsLoading] = React.useState(null); + + // Fetches the item details. + const { + data: item, + isFetching: isItemFetching, + isSuccess: isItemSuccess, + } = useItem(itemRow && itemRow.itemId, { + enabled: !!(itemRow && itemRow.itemId), + }); + + // Once the item start loading give the table cells loading state. + React.useEffect(() => { + if (itemRow && isItemFetching) { + setCellsLoading([ + [itemRow.rowIndex, 'rate'], + [itemRow.rowIndex, 'description'], + [itemRow.rowIndex, 'quantity'], + [itemRow.rowIndex, 'discount'], + ]); + } else { + setCellsLoading(null); + } + }, [isItemFetching, setCellsLoading, itemRow]); + + // Once the item selected and fetched set the initial details to the table. + React.useEffect(() => { + if (isItemSuccess && item && itemRow) { + const { rowIndex } = itemRow; + const price = + itemType === ITEM_TYPE.PURCHASABLE ? item.cost_price : item.sell_price; + + const description = + itemType === ITEM_TYPE.PURCHASABLE + ? item.cost_description + : item.sell_description; + + // Detarmines whether the landed cost checkbox should be disabled. + const landedCostDisabled = isLandedCostDisabled(item); + + // The new row. + const newRow = { + rate: price, + description, + quantity: 1, + ...(landedCost + ? { + landed_cost: false, + landed_cost_disabled: landedCostDisabled, + } + : {}), + }; + setItemRow(null); + saveInvoke(notifyNewRow, newRow, rowIndex); + } + }, [item, itemRow, itemType, isItemSuccess, landedCost, notifyNewRow]); + + return { + isItemFetching, + isItemSuccess, + item, + setItemRow, + itemRow, + cellsLoading, + }; +} diff --git a/client/src/containers/Expenses/ExpenseForm/ExpenseFormEntriesTable.js b/client/src/containers/Expenses/ExpenseForm/ExpenseFormEntriesTable.js index 90ec018af..f0b2b82d4 100644 --- a/client/src/containers/Expenses/ExpenseForm/ExpenseFormEntriesTable.js +++ b/client/src/containers/Expenses/ExpenseForm/ExpenseFormEntriesTable.js @@ -6,7 +6,7 @@ import { useExpenseFormTableColumns } from './components'; import { saveInvoke, compose, - updateTableRow, + updateTableCell, updateMinEntriesLines, updateAutoAddNewLine, updateRemoveLineByIndex, @@ -38,7 +38,7 @@ export default function ExpenseFormEntriesTable({ // Update auto-adding new line. updateAutoAddNewLine(defaultEntry, ['expense_account_id']), // Update the row value of the given row index and column id. - updateTableRow(rowIndex, columnId, value), + updateTableCell(rowIndex, columnId, value), )(entries); saveInvoke(onChange, newRows); diff --git a/client/src/containers/FinancialStatements/FinancialStatementDateRange.js b/client/src/containers/FinancialStatements/FinancialStatementDateRange.js index 1d882996f..7b5479464 100644 --- a/client/src/containers/FinancialStatements/FinancialStatementDateRange.js +++ b/client/src/containers/FinancialStatements/FinancialStatementDateRange.js @@ -12,8 +12,6 @@ import { dateRangeOptions } from 'containers/FinancialStatements/common'; * Financial statement - Date range select. */ export default function FinancialStatementDateRange() { - - return ( <> diff --git a/client/src/containers/Items/utils.js b/client/src/containers/Items/utils.js index d9fd04db4..b4614a98c 100644 --- a/client/src/containers/Items/utils.js +++ b/client/src/containers/Items/utils.js @@ -37,10 +37,10 @@ export const useItemFormInitialValues = (item) => { return useMemo( () => ({ ...defaultInitialValues, - cost_account_id: defaultTo(itemsSettings.preferredCostAccount, ''), - sell_account_id: defaultTo(itemsSettings.preferredSellAccount, ''), + cost_account_id: defaultTo(itemsSettings?.preferredCostAccount, ''), + sell_account_id: defaultTo(itemsSettings?.preferredSellAccount, ''), inventory_account_id: defaultTo( - itemsSettings.preferredInventoryAccount, + itemsSettings?.preferredInventoryAccount, '', ), /** diff --git a/client/src/containers/Purchases/Bills/BillForm/BillForm.js b/client/src/containers/Purchases/Bills/BillForm/BillForm.js index 2df2fb588..3df70d1af 100644 --- a/client/src/containers/Purchases/Bills/BillForm/BillForm.js +++ b/client/src/containers/Purchases/Bills/BillForm/BillForm.js @@ -5,7 +5,7 @@ import classNames from 'classnames'; import * as R from 'ramda'; import intl from 'react-intl-universal'; import { useHistory } from 'react-router-dom'; -import { isEmpty, omit } from 'lodash'; +import { isEmpty } from 'lodash'; import { CLASSES } from 'common/classes'; import { EditBillFormSchema, CreateBillFormSchema } from './BillForm.schema'; @@ -18,8 +18,12 @@ import { AppToaster } from 'components'; import { ERROR } from 'common/errors'; import { useBillFormContext } from './BillFormProvider'; -import { compose, orderingLinesIndexes, safeSumBy } from 'utils'; -import { defaultBill, transformToEditForm } from './utils'; +import { compose, safeSumBy } from 'utils'; +import { + defaultBill, + transformToEditForm, + transformEntriesToSubmit, +} from './utils'; import withCurrentOrganization from 'containers/Organization/withCurrentOrganization'; /** @@ -81,7 +85,7 @@ function BillForm({ const form = { ...values, open: submitPayload.status, - entries: R.compose(orderingLinesIndexes)(entries), + entries: transformEntriesToSubmit(entries), }; // Handle the request success. const onSuccess = (response) => { diff --git a/client/src/containers/Purchases/Bills/BillForm/BillFormProvider.js b/client/src/containers/Purchases/Bills/BillForm/BillFormProvider.js index 401ea1622..2d1018626 100644 --- a/client/src/containers/Purchases/Bills/BillForm/BillFormProvider.js +++ b/client/src/containers/Purchases/Bills/BillForm/BillFormProvider.js @@ -12,6 +12,12 @@ import { const BillFormContext = createContext(); +// Filter all purchasable items only. +const stringifiedFilterRoles = JSON.stringify([ + { index: 1, fieldKey: 'purchasable', value: true, condition: '&&', comparator: 'equals' }, + { index: 2, fieldKey: 'active', value: true, condition: '&&', comparator: 'equals' }, +]); + /** * Bill form provider. */ @@ -25,16 +31,6 @@ function BillFormProvider({ billId, ...props }) { isLoading: isVendorsLoading, } = useVendors({ page_size: 10000 }); - // Filter all purchasable items only. - const stringifiedFilterRoles = React.useMemo( - () => - JSON.stringify([ - { index: 1, fieldKey: 'purchasable', value: true, condition: '&&', comparator: 'equals' }, - { index: 2, fieldKey: 'active', value: true, condition: '&&', comparator: 'equals' }, - ]), - [], - ); - // Handle fetch Items data table or list const { data: { items }, diff --git a/client/src/containers/Purchases/Bills/BillForm/utils.js b/client/src/containers/Purchases/Bills/BillForm/utils.js index ce31668bc..3408e1e38 100644 --- a/client/src/containers/Purchases/Bills/BillForm/utils.js +++ b/client/src/containers/Purchases/Bills/BillForm/utils.js @@ -7,14 +7,17 @@ import { defaultFastFieldShouldUpdate, transformToForm, repeatValue, + orderingLinesIndexes, } from 'utils'; import { updateItemsEntriesTotal, ensureEntriesHaveEmptyLine, } from 'containers/Entries/utils'; +import { isLandedCostDisabled } from '../../../Entries/utils'; export const MIN_LINES_NUMBER = 4; +// Default bill entry. export const defaultBillEntry = { index: 0, item_id: '', @@ -26,6 +29,7 @@ export const defaultBillEntry = { landed_cost: false, }; +// Default bill. export const defaultBill = { vendor_id: '', bill_number: '', @@ -37,10 +41,14 @@ export const defaultBill = { entries: [...repeatValue(defaultBillEntry, MIN_LINES_NUMBER)], }; +/** + * Transformes the bill to initial values of edit form. + */ export const transformToEditForm = (bill) => { const initialEntries = [ ...bill.entries.map((entry) => ({ ...transformToForm(entry, defaultBillEntry), + landed_cost_disabled: isLandedCostDisabled(entry.item), })), ...repeatValue( defaultBillEntry, @@ -58,7 +66,18 @@ export const transformToEditForm = (bill) => { }; }; -// handle delete errors. +/** + * Transformes bill entries to submit request. + */ +export const transformEntriesToSubmit = (entries) => { + const transformBillEntry = R.curry(transformToForm)(R.__, defaultBillEntry); + + return R.compose(orderingLinesIndexes, R.map(transformBillEntry))(entries); +}; + +/** + * Handle delete errors. + */ export const handleDeleteErrors = (errors) => { if ( errors.find((error) => error.type === 'BILL_HAS_ASSOCIATED_PAYMENT_ENTRIES') diff --git a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeEntriesTable.js b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeEntriesTable.js index feb2615ff..e1d961402 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeEntriesTable.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeEntriesTable.js @@ -8,7 +8,7 @@ import { DataTableEditable } from 'components'; import { usePaymentMadeEntriesTableColumns } from './components'; import { usePaymentMadeInnerContext } from './PaymentMadeInnerProvider'; -import { compose, updateTableRow } from 'utils'; +import { compose, updateTableCell } from 'utils'; import { useFormikContext } from 'formik'; /** @@ -33,7 +33,7 @@ export default function PaymentMadeEntriesTable({ // Handle update data. const handleUpdateData = useCallback( (rowIndex, columnId, value) => { - const newRows = compose(updateTableRow(rowIndex, columnId, value))( + const newRows = compose(updateTableCell(rowIndex, columnId, value))( entries, ); onUpdateData(newRows); diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.js index 60b792364..c01012125 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.js @@ -8,7 +8,7 @@ import { CLASSES } from 'common/classes'; import { usePaymentReceiveInnerContext } from './PaymentReceiveInnerProvider'; import { DataTableEditable } from 'components'; import { usePaymentReceiveEntriesColumns } from './components'; -import { compose, updateTableRow } from 'utils'; +import { compose, updateTableCell } from 'utils'; /** * Payment receive items table. @@ -39,7 +39,7 @@ export default function PaymentReceiveItemsTable({ // Handle update data. const handleUpdateData = useCallback( (rowIndex, columnId, value) => { - const newRows = compose(updateTableRow(rowIndex, columnId, value))( + const newRows = compose(updateTableCell(rowIndex, columnId, value))( entries, ); diff --git a/client/src/style/components/DataTable/DataTableEditable.scss b/client/src/style/components/DataTable/DataTableEditable.scss index 703afb089..d5dc2c865 100644 --- a/client/src/style/components/DataTable/DataTableEditable.scss +++ b/client/src/style/components/DataTable/DataTableEditable.scss @@ -65,7 +65,7 @@ .bp3-control-indicator{ height: 18px; width: 18px; - border-color: #e0e0e0; + border-color: #dbdbdb; } } } diff --git a/client/src/utils.js b/client/src/utils.js index d910002f7..dd144d1a7 100644 --- a/client/src/utils.js +++ b/client/src/utils.js @@ -643,7 +643,7 @@ export const updateRemoveLineByIndex = (rowIndex) => (entries) => { return entries.filter((row, index) => index !== removeIndex); }; -export const updateTableRow = (rowIndex, columnId, value) => (old) => { +export const updateTableCell = (rowIndex, columnId, value) => (old) => { return old.map((row, index) => { if (index === rowIndex) { return { @@ -654,6 +654,18 @@ export const updateTableRow = (rowIndex, columnId, value) => (old) => { return row; }); }; + +export const updateTableRow = (rowIndex, value) => (old) => { + return old.map((row, index) => { + if (index === rowIndex) { + return { + ...old[rowIndex], + ...value, + }; + } + return row; + }); +}; export const transformGeneralSettings = (data) => { return _.mapKeys(data, (value, key) => _.snakeCase(key)); };