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));
};