From b2c892b64989199cfed35d5c110391cb184a823b Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Tue, 3 Aug 2021 16:04:31 +0200 Subject: [PATCH] fix: items entries total calculation. --- client/src/containers/Entries/components.js | 4 +- client/src/containers/Entries/utils.js | 10 +- .../Bills/BillForm/BillFormHeader.js | 2 +- .../Purchases/Bills/BillForm/utils.js | 1 + .../Estimates/EstimateForm/EstimateForm.js | 1 + .../EstimateForm/EstimateFormHeader.js | 9 +- .../EstimateForm/EstimateFormProvider.js | 22 ++- .../Sales/Estimates/EstimateForm/utils.js | 20 ++- .../Invoices/InvoiceForm/InvoiceFormHeader.js | 5 +- .../InvoiceForm/InvoiceFormProvider.js | 22 +-- .../Sales/Invoices/InvoiceForm/utils.js | 28 +++- .../Receipts/ReceiptForm/ReceiptFormHeader.js | 14 +- .../Sales/Receipts/ReceiptForm/utils.js | 1 + client/src/style/App.scss | 129 +----------------- .../src/style/components/SidebarOverlay.scss | 129 ++++++++++++++++++ 15 files changed, 222 insertions(+), 175 deletions(-) create mode 100644 client/src/style/components/SidebarOverlay.scss diff --git a/client/src/containers/Entries/components.js b/client/src/containers/Entries/components.js index 59db5385d..05380a388 100644 --- a/client/src/containers/Entries/components.js +++ b/client/src/containers/Entries/components.js @@ -75,7 +75,7 @@ export function QuantityTotalFooterCell({ rows }) { * Total footer cell. */ export function TotalFooterCell({ payload: { currencyCode }, rows }) { - const total = safeSumBy(rows, 'original.total'); + const total = safeSumBy(rows, 'original.amount'); return {formattedAmount(total, currencyCode)}; } @@ -168,7 +168,7 @@ export function useEditableItemsEntriesColumns({ landedCost }) { { Header: intl.get('total'), Footer: TotalFooterCell, - accessor: 'total', + accessor: 'amount', Cell: TotalCell, disableSortBy: true, width: 100, diff --git a/client/src/containers/Entries/utils.js b/client/src/containers/Entries/utils.js index 6024f8db3..437f7faf2 100644 --- a/client/src/containers/Entries/utils.js +++ b/client/src/containers/Entries/utils.js @@ -1,3 +1,4 @@ +import { sumBy } from 'lodash'; import { toSafeNumber } from 'utils'; /** @@ -21,7 +22,7 @@ export const calcItemEntryTotal = (discount, quantity, rate) => { export function updateItemsEntriesTotal(rows) { return rows.map((row) => ({ ...row, - total: calcItemEntryTotal(row.discount, row.quantity, row.rate), + amount: calcItemEntryTotal(row.discount, row.quantity, row.rate), })); } @@ -29,3 +30,10 @@ export const ITEM_TYPE = { SELLABLE: 'SELLABLE', PURCHASABLE: 'PURCHASABLE', }; + +/** + * Retrieve total of the given items entries. + */ +export function getEntriesTotal(entries) { + return sumBy(entries, 'amount'); +} \ No newline at end of file diff --git a/client/src/containers/Purchases/Bills/BillForm/BillFormHeader.js b/client/src/containers/Purchases/Bills/BillForm/BillFormHeader.js index 08676967b..da26f1d94 100644 --- a/client/src/containers/Purchases/Bills/BillForm/BillFormHeader.js +++ b/client/src/containers/Purchases/Bills/BillForm/BillFormHeader.js @@ -21,7 +21,7 @@ function BillFormHeader({ const { values } = useFormikContext(); // Calculate the total due amount of bill entries. - const totalDueAmount = useMemo(() => sumBy(values.entries, 'total'), [ + const totalDueAmount = useMemo(() => sumBy(values.entries, 'amount'), [ values.entries, ]); diff --git a/client/src/containers/Purchases/Bills/BillForm/utils.js b/client/src/containers/Purchases/Bills/BillForm/utils.js index 9723b4f97..531179c38 100644 --- a/client/src/containers/Purchases/Bills/BillForm/utils.js +++ b/client/src/containers/Purchases/Bills/BillForm/utils.js @@ -17,6 +17,7 @@ export const defaultBillEntry = { discount: '', quantity: '', description: '', + amount: '', landed_cost: false, }; diff --git a/client/src/containers/Sales/Estimates/EstimateForm/EstimateForm.js b/client/src/containers/Sales/Estimates/EstimateForm/EstimateForm.js index 1b8e540ce..758232be2 100644 --- a/client/src/containers/Sales/Estimates/EstimateForm/EstimateForm.js +++ b/client/src/containers/Sales/Estimates/EstimateForm/EstimateForm.js @@ -89,6 +89,7 @@ function EstimateForm({ ); const totalQuantity = sumBy(entries, (entry) => parseInt(entry.quantity)); + // Validate the entries quantity should be bigger than zero. if (totalQuantity === 0) { AppToaster.show({ message: intl.get('quantity_cannot_be_zero_or_empty'), diff --git a/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormHeader.js b/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormHeader.js index 9402d5d75..38b1f2de9 100644 --- a/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormHeader.js +++ b/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormHeader.js @@ -1,14 +1,15 @@ import React, { useMemo } from 'react'; import classNames from 'classnames'; -import { sumBy } from 'lodash'; import { useFormikContext } from 'formik'; import intl from 'react-intl-universal'; import { CLASSES } from 'common/classes'; - import EstimateFormHeaderFields from './EstimateFormHeaderFields'; -import { PageFormBigNumber } from 'components'; + import withSettings from 'containers/Settings/withSettings'; + +import { getEntriesTotal } from 'containers/Entries/utils'; +import { PageFormBigNumber } from 'components'; import { compose } from 'utils'; // Estimate form top header. @@ -20,7 +21,7 @@ function EstimateFormHeader({ // Calculate the total due amount of bill entries. const totalDueAmount = useMemo( - () => sumBy(values.entries, 'total'), + () => getEntriesTotal(values.entries), [values.entries], ); diff --git a/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormProvider.js b/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormProvider.js index 3c942de78..85f89a499 100644 --- a/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormProvider.js +++ b/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormProvider.js @@ -8,6 +8,7 @@ import { useCreateEstimate, useEditEstimate, } from 'hooks/query'; +import { ITEMS_FILTER_ROLES } from './utils'; const EstimateFormContext = createContext(); @@ -18,31 +19,24 @@ function EstimateFormProvider({ estimateId, ...props }) { const { data: estimate, isFetching: isEstimateFetching, + isLoading: isEstimateLoading, } = useEstimate(estimateId, { enabled: !!estimateId }); - // Filter all sellable items only. - const stringifiedFilterRoles = React.useMemo( - () => - JSON.stringify([ - { index: 1, fieldKey: 'sellable', value: true, condition: '&&', comparator: 'equals', }, - { index: 2, fieldKey: 'active', value: true, condition: '&&', comparator: 'equals' }, - ]), - [], - ); - // Handle fetch Items data table or list const { data: { items }, isFetching: isItemsFetching, + isLoading: isItemsLoading, } = useItems({ page_size: 10000, - stringified_filter_roles: stringifiedFilterRoles, + stringified_filter_roles: ITEMS_FILTER_ROLES, }); // Handle fetch customers data table or list const { data: { customers }, isFetch: isCustomersFetching, + isLoading: isCustomersLoading, } = useCustomers({ page_size: 10000 }); // Handle fetch settings. @@ -68,6 +62,10 @@ function EstimateFormProvider({ estimateId, ...props }) { isItemsFetching, isEstimateFetching, + isCustomersLoading, + isItemsLoading, + isEstimateLoading, + submitPayload, setSubmitPayload, @@ -77,7 +75,7 @@ function EstimateFormProvider({ estimateId, ...props }) { return ( diff --git a/client/src/containers/Sales/Estimates/EstimateForm/utils.js b/client/src/containers/Sales/Estimates/EstimateForm/utils.js index eba82d104..3056c29f7 100644 --- a/client/src/containers/Sales/Estimates/EstimateForm/utils.js +++ b/client/src/containers/Sales/Estimates/EstimateForm/utils.js @@ -17,6 +17,7 @@ export const defaultEstimateEntry = { discount: '', quantity: '', description: '', + amount: '', }; export const defaultEstimate = { @@ -55,7 +56,7 @@ export const useObserveEstimateNoSettings = (prefix, nextNumber) => { setFieldValue('estimate_number', estimateNo); }, [setFieldValue, prefix, nextNumber]); }; - + /** * Detarmines customers fast field when update. */ @@ -75,3 +76,20 @@ export const entriesFieldShouldUpdate = (newProps, oldProps) => { defaultFastFieldShouldUpdate(newProps, oldProps) ); }; + +export const ITEMS_FILTER_ROLES = JSON.stringify([ + { + index: 1, + fieldKey: 'sellable', + value: true, + condition: '&&', + comparator: 'equals', + }, + { + index: 2, + fieldKey: 'active', + value: true, + condition: '&&', + comparator: 'equals', + }, +]); diff --git a/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeader.js b/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeader.js index 18b958b18..0712a63ea 100644 --- a/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeader.js +++ b/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeader.js @@ -1,13 +1,14 @@ import React, { useMemo } from 'react'; import classNames from 'classnames'; -import { sumBy } from 'lodash'; import { useFormikContext } from 'formik'; import intl from 'react-intl-universal'; import { CLASSES } from 'common/classes'; import InvoiceFormHeaderFields from './InvoiceFormHeaderFields'; +import { getEntriesTotal } from 'containers/Entries/utils'; import { PageFormBigNumber } from 'components'; + import withSettings from 'containers/Settings/withSettings'; import { compose } from 'redux'; @@ -23,7 +24,7 @@ function InvoiceFormHeader({ // Calculate the total due amount of invoice entries. const totalDueAmount = useMemo( - () => sumBy(values.entries, 'total'), + () => getEntriesTotal(values.entries), [values.entries], ); diff --git a/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormProvider.js b/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormProvider.js index 539f09e33..d7696c146 100644 --- a/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormProvider.js +++ b/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormProvider.js @@ -2,7 +2,7 @@ import React, { createContext, useState } from 'react'; import { isEmpty, pick } from 'lodash'; import { useLocation } from 'react-router-dom'; import DashboardInsider from 'components/Dashboard/DashboardInsider'; -import { transformToEditForm } from './utils'; +import { transformToEditForm, ITEMS_FILTER_ROLES_QUERY } from './utils'; import { useInvoice, useItems, @@ -27,10 +27,10 @@ function InvoiceFormProvider({ invoiceId, ...props }) { }); // Fetches the estimate by the given id. - const { - data: estimate, - isLoading: isEstimateLoading, - } = useEstimate(estimateId, { enabled: !!estimateId }); + const { data: estimate, isLoading: isEstimateLoading } = useEstimate( + estimateId, + { enabled: !!estimateId }, + ); const newInvoice = !isEmpty(estimate) ? transformToEditForm({ @@ -38,23 +38,13 @@ function InvoiceFormProvider({ invoiceId, ...props }) { }) : []; - // Filter all sellable items only. - const stringifiedFilterRoles = React.useMemo( - () => - JSON.stringify([ - { index: 1, fieldKey: 'sellable', value: true, condition: '&&', comparator: 'equals', }, - { index: 2, fieldKey: 'active', value: true, condition: '&&', comparator: 'equals' }, - ]), - [], - ); - // Handle fetching the items table based on the given query. const { data: { items }, isLoading: isItemsLoading, } = useItems({ page_size: 10000, - stringified_filter_roles: stringifiedFilterRoles, + stringified_filter_roles: ITEMS_FILTER_ROLES_QUERY, }); // Handle fetch customers data table or list diff --git a/client/src/containers/Sales/Invoices/InvoiceForm/utils.js b/client/src/containers/Sales/Invoices/InvoiceForm/utils.js index 70787c7fa..2f0ee469c 100644 --- a/client/src/containers/Sales/Invoices/InvoiceForm/utils.js +++ b/client/src/containers/Sales/Invoices/InvoiceForm/utils.js @@ -26,7 +26,7 @@ export const defaultInvoiceEntry = { discount: '', quantity: '', description: '', - total: 0, + amount: '', }; // Default invoice object. @@ -63,6 +63,9 @@ export function transformToEditForm(invoice) { }; } +/** + * Transformes the response errors types. + */ export const transformErrors = (errors, { setErrors }) => { if (errors.some((e) => e.type === ERROR.SALE_INVOICE_NUMBER_IS_EXISTS)) { setErrors({ @@ -102,6 +105,9 @@ export const useObserveInvoiceNoSettings = (prefix, nextNumber) => { }, [setFieldValue, prefix, nextNumber]); }; +/** + * Detarmines customer name field when should update. + */ export const customerNameFieldShouldUpdate = (newProps, oldProps) => { return ( newProps.customers !== oldProps.customers || @@ -109,9 +115,29 @@ export const customerNameFieldShouldUpdate = (newProps, oldProps) => { ); }; +/** + * Detarmines invoice entries field when should update. + */ export const entriesFieldShouldUpdate = (newProps, oldProps) => { return ( newProps.items !== oldProps.items || defaultFastFieldShouldUpdate(newProps, oldProps) ); }; + +export const ITEMS_FILTER_ROLES_QUERY = JSON.stringify([ + { + index: 1, + fieldKey: 'sellable', + value: true, + condition: '&&', + comparator: 'equals', + }, + { + index: 2, + fieldKey: 'active', + value: true, + condition: '&&', + comparator: 'equals', + }, +]); diff --git a/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormHeader.js b/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormHeader.js index 87ee106e6..ef0ca3125 100644 --- a/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormHeader.js +++ b/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormHeader.js @@ -1,15 +1,15 @@ import React, { useMemo } from 'react'; import classNames from 'classnames'; -import { sumBy } from 'lodash'; import { useFormikContext } from 'formik'; - -import { CLASSES } from 'common/classes'; -import ReceiptFormHeaderFields from './ReceiptFormHeaderFields'; - -import { PageFormBigNumber } from 'components'; import intl from 'react-intl-universal'; +import { CLASSES } from 'common/classes'; +import { PageFormBigNumber } from 'components'; +import ReceiptFormHeaderFields from './ReceiptFormHeaderFields'; + import withSettings from 'containers/Settings/withSettings'; + +import { getEntriesTotal } from 'containers/Entries/utils'; import { compose } from 'redux'; /** @@ -25,7 +25,7 @@ function ReceiptFormHeader({ // Calculate the total due amount of bill entries. const totalDueAmount = useMemo( - () => sumBy(values.entries, 'total'), + () => getEntriesTotal(values.entries), [values.entries], ); diff --git a/client/src/containers/Sales/Receipts/ReceiptForm/utils.js b/client/src/containers/Sales/Receipts/ReceiptForm/utils.js index 93cbb75cd..d4b82d660 100644 --- a/client/src/containers/Sales/Receipts/ReceiptForm/utils.js +++ b/client/src/containers/Sales/Receipts/ReceiptForm/utils.js @@ -17,6 +17,7 @@ export const defaultReceiptEntry = { discount: '', quantity: '', description: '', + amount: '', }; export const defaultReceipt = { diff --git a/client/src/style/App.scss b/client/src/style/App.scss index 588e1e6c5..927ed9e04 100644 --- a/client/src/style/App.scss +++ b/client/src/style/App.scss @@ -26,6 +26,7 @@ @import 'components/PageForm'; @import 'components/Tooltip'; @import 'components/Postbox'; +@import 'components/SidebarOverlay'; // Pages @import 'pages/view-form'; @@ -201,134 +202,6 @@ html[lang^="ar"] { } } - -.sidebar-overlay { - background: #fff; - height: 100%; - width: 225px; - outline: 0; - box-shadow: 0 0 2px rgba(0, 0, 0, 0.1); - - &__scroll-wrapper { - height: 100% - } - - .ScrollbarsCustom-Track { - - &.ScrollbarsCustom-TrackY, - &.ScrollbarsCustom-TrackX { - background: rgba(0, 0, 0, 0); - } - } - - .ScrollbarsCustom-Thumb { - - &.ScrollbarsCustom-ThumbX, - &.ScrollbarsCustom-ThumbY { - background: rgba(0, 0, 0, 0); - } - } - - .ScrollbarsCustom-Content { - display: flex; - flex-direction: column; - height: 100%; - } - - &:hover { - .ScrollbarsCustom-Thumb { - - &.ScrollbarsCustom-ThumbX, - &.ScrollbarsCustom-ThumbY { - background: rgba(0, 0, 0, 0.5); - } - } - } - - &__menu { - margin: 16px 0; - } - - &__item { - font-size: 15px; - color: #00102b; - - a { - color: inherit; - display: block; - padding: 10px 22px; - text-decoration: none; - - &:hover, - &:focus{ - background: #f3f3f3; - } - } - } - - &__divider { - height: 1px; - margin: 6px 0; - background: #e2e5ec; - } - - &__label{ - text-transform: uppercase; - font-size: 12px; - padding: 14px 20px 10px; - letter-spacing: 1px; - color: #707a85; - - html[lang^="ar"] & { - font-size: 13px; - letter-spacing: 0; - font-weight: 500; - } - } - - &__label + .sidebar-overlay__divider{ - margin-top: 0; - } -} - -.sidebar-overlay-transition { - transform: translateX(-100%); - - &.bp3-overlay{ - &-appear, - &-enter { - transform: translateX(-100%) - } - - &-appear-active, - &-enter-active { - transform: translateX(0) !important; - transition: all 100ms ease-in-out; - } - - &-appear-done, - &-enter-done { - transform: translateX(0) !important; - } - - &-exit { - transform: translateX(0) !important; - } - &-exit-active { - transform: translateX(-100%) !important; - transition: all 100ms ease-in-out; - } - &-exit-done{ - transform: translateX(-100%) !important; - } - } - -} -.sidebar-overlay-backdrop{ - background-color: rgba(0, 10, 30, 0.15); -} - - .bp3-popover2{ box-shadow: 0 0 0; } diff --git a/client/src/style/components/SidebarOverlay.scss b/client/src/style/components/SidebarOverlay.scss new file mode 100644 index 000000000..dc16f7876 --- /dev/null +++ b/client/src/style/components/SidebarOverlay.scss @@ -0,0 +1,129 @@ + + +.sidebar-overlay { + background: #fff; + height: 100%; + width: 225px; + outline: 0; + box-shadow: 0 0 2px rgba(0, 0, 0, 0.1); + + &__scroll-wrapper { + height: 100% + } + + .ScrollbarsCustom-Track { + + &.ScrollbarsCustom-TrackY, + &.ScrollbarsCustom-TrackX { + background: rgba(0, 0, 0, 0); + } + } + + .ScrollbarsCustom-Thumb { + + &.ScrollbarsCustom-ThumbX, + &.ScrollbarsCustom-ThumbY { + background: rgba(0, 0, 0, 0); + } + } + + .ScrollbarsCustom-Content { + display: flex; + flex-direction: column; + height: 100%; + } + + &:hover { + .ScrollbarsCustom-Thumb { + + &.ScrollbarsCustom-ThumbX, + &.ScrollbarsCustom-ThumbY { + background: rgba(0, 0, 0, 0.5); + } + } + } + + &__menu { + margin: 16px 0; + } + + &__item { + font-size: 15px; + color: #00102b; + + a { + color: inherit; + display: block; + padding: 10px 22px; + text-decoration: none; + + &:hover, + &:focus{ + background: #f3f3f3; + } + } + } + + &__divider { + height: 1px; + margin: 6px 0; + background: #e2e5ec; + } + + &__label{ + text-transform: uppercase; + font-size: 12px; + padding: 14px 20px 10px; + letter-spacing: 1px; + color: #707a85; + + html[lang^="ar"] & { + font-size: 13px; + letter-spacing: 0; + font-weight: 500; + } + } + + &__label + .sidebar-overlay__divider{ + margin-top: 0; + } + } + + .sidebar-overlay-transition { + transform: translateX(-100%); + + &.bp3-overlay{ + &-appear, + &-enter { + transform: translateX(-100%) + } + + &-appear-active, + &-enter-active { + transform: translateX(0) !important; + transition: all 100ms ease-in-out; + } + + &-appear-done, + &-enter-done { + transform: translateX(0) !important; + } + + &-exit { + transform: translateX(0) !important; + } + &-exit-active { + transform: translateX(-100%) !important; + transition: all 100ms ease-in-out; + } + &-exit-done{ + transform: translateX(-100%) !important; + } + } + + } + .sidebar-overlay-backdrop{ + background-color: rgba(0, 10, 30, 0.15); + } + + \ No newline at end of file