diff --git a/client/src/containers/Accounts/components.js b/client/src/containers/Accounts/components.js index 1453e4fe6..0ea6e45ea 100644 --- a/client/src/containers/Accounts/components.js +++ b/client/src/containers/Accounts/components.js @@ -115,7 +115,7 @@ export function NormalCell({ cell: { value } }) { export function BalanceCell({ cell }) { const account = cell.row.original; - return account.amount ? ( + return account.amount !== null ? ( diff --git a/client/src/containers/Alerts/Currencies/CurrencyDeleteAlert.js b/client/src/containers/Alerts/Currencies/CurrencyDeleteAlert.js index 48c40f7dd..2f89ee8fb 100644 --- a/client/src/containers/Alerts/Currencies/CurrencyDeleteAlert.js +++ b/client/src/containers/Alerts/Currencies/CurrencyDeleteAlert.js @@ -45,7 +45,13 @@ function CurrencyDeleteAlert({ }); closeAlert(name); }) - .catch(() => { + .catch(({ response: { data: { errors } } }) => { + if (errors.find(e => e.type === 'CANNOT_DELETE_BASE_CURRENCY')) { + AppToaster.show({ + intent: Intent.DANGER, + message: 'Cannot delete the base currency.' + }); + } closeAlert(name); }); }; diff --git a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyForm.js b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyForm.js index 440802aed..f3448171a 100644 --- a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyForm.js +++ b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyForm.js @@ -1,9 +1,8 @@ -import React, { useMemo, useCallback } from 'react'; +import React, { useMemo } from 'react'; import { Intent } from '@blueprintjs/core'; import { Formik } from 'formik'; import { FormattedMessage as T, useIntl } from 'react-intl'; import { AppToaster } from 'components'; -import { pick } from 'lodash'; import CurrencyFormContent from './CurrencyFormContent'; import { useCurrencyFormContext } from './CurrencyFormProvider'; @@ -18,6 +17,7 @@ import { compose, transformToForm } from 'utils'; const defaultInitialValues = { currency_name: '', currency_code: '', + currency_sign: '', }; /** @@ -59,7 +59,7 @@ function CurrencyForm({ const afterSubmit = () => { closeDialog(dialogName); }; - + // Handle the request success. const onSuccess = ({ response }) => { AppToaster.show({ message: formatMessage({ @@ -71,9 +71,14 @@ function CurrencyForm({ }); afterSubmit(response); }; - // Handle the response error. - const onError = (errors) => { + const onError = ({ response: { data: { errors } } }) => { + if (errors.find(e => e.type === 'CURRENCY_CODE_EXISTS')) { + AppToaster.show({ + message: 'The given currency code is already exists.', + intent: Intent.DANGER, + }); + } setSubmitting(false); }; if (isEditMode) { diff --git a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyForm.schema.js b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyForm.schema.js index 31c6dfe8b..556069355 100644 --- a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyForm.schema.js +++ b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyForm.schema.js @@ -10,6 +10,7 @@ const Schema = Yup.object().shape({ .max(4) .required() .label(formatMessage({ id: 'currency_code_' })), + currency_sign: Yup.string().required(), }); export const CreateCurrencyFormSchema = Schema; diff --git a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormDialogContent.js b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormDialogContent.js index d8c1dc07b..5bcba93c4 100644 --- a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormDialogContent.js +++ b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormDialogContent.js @@ -1,6 +1,5 @@ import React from 'react'; import { CurrencyFormProvider } from './CurrencyFormProvider'; -import { pick } from 'lodash'; import CurrencyForm from './CurrencyForm'; import withCurrencyDetail from 'containers/Currencies/withCurrencyDetail'; diff --git a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormFields.js b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormFields.js index 61df70c3f..c24588e34 100644 --- a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormFields.js +++ b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormFields.js @@ -1,24 +1,47 @@ import React from 'react'; -import { - Classes, - FormGroup, - InputGroup, -} from '@blueprintjs/core'; +import { Classes, FormGroup, InputGroup } from '@blueprintjs/core'; import { FastField } from 'formik'; import { FormattedMessage as T } from 'react-intl'; -import { - ErrorMessage, - FieldRequiredHint, -} from 'components'; + +import { useCurrencyFormContext } from './CurrencyFormProvider'; +import { ErrorMessage, FieldRequiredHint, ListSelect } from 'components'; import { useAutofocus } from 'hooks'; -import { inputIntent } from 'utils'; +import { inputIntent, currenciesOptions } from 'utils'; +/** + * Currency form fields. + */ export default function CurrencyFormFields() { const currencyNameFieldRef = useAutofocus(); - + + const { isEditMode } = useCurrencyFormContext(); + return (
+ + {({ + form: { setFieldValue }, + field: { value }, + meta: { error, touched }, + }) => ( + + { + setFieldValue('currency_code', currency.currency_code); + setFieldValue('currency_name', currency.name); + setFieldValue('currency_sign', currency.symbol); + }} + disabled={isEditMode} + /> + + )} + {/* ----------- Currency name ----------- */} {({ field, field: { value }, meta: { error, touched } }) => ( @@ -38,15 +61,14 @@ export default function CurrencyFormFields() { )} {/* ----------- Currency Code ----------- */} - + {({ field, field: { value }, meta: { error, touched } }) => ( } + label={} labelInfo={} - className={'form-group--currency-code'} + className={'form-group--currency-sign'} intent={inputIntent({ error, touched })} - helperText={} - // inline={true} + helperText={} > diff --git a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormProvider.js b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormProvider.js index 545b86b47..aa74f8539 100644 --- a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormProvider.js +++ b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormProvider.js @@ -1,5 +1,5 @@ import React, { createContext } from 'react'; -import { useCurrencies, useEditCurrency, useCreateCurrency } from 'hooks/query'; +import { useEditCurrency, useCreateCurrency } from 'hooks/query'; import { DialogContent } from 'components'; const CurrencyFormContext = createContext(); @@ -7,27 +7,22 @@ const CurrencyFormContext = createContext(); /** * Currency Form page provider. */ - function CurrencyFormProvider({ isEditMode, currency, dialogName, ...props }) { // Create and edit item currency mutations. const { mutateAsync: createCurrencyMutate } = useCreateCurrency(); const { mutateAsync: editCurrencyMutate } = useEditCurrency(); - // fetch Currencies list. - const { data: currencies, isLoading: isCurrenciesLoading } = useCurrencies(); - // Provider state. const provider = { createCurrencyMutate, editCurrencyMutate, dialogName, currency, - isCurrenciesLoading, isEditMode, }; return ( - + ); diff --git a/client/src/containers/Preferences/Currencies/CurrenciesDataTable.js b/client/src/containers/Preferences/Currencies/CurrenciesDataTable.js index ceadb4a8e..5bbb54cda 100644 --- a/client/src/containers/Preferences/Currencies/CurrenciesDataTable.js +++ b/client/src/containers/Preferences/Currencies/CurrenciesDataTable.js @@ -52,6 +52,7 @@ function CurrenciesDataTable({ loading={isCurrenciesLoading} progressBarLoading={isCurrenciesLoading} TableLoadingRenderer={TableSkeletonRows} + ContextMenu={ActionMenuList} noInitialFetch={true} payload={{ onDeleteCurrency: handleDeleteCurrency, diff --git a/client/src/containers/Preferences/Currencies/components.js b/client/src/containers/Preferences/Currencies/components.js index 41aa096b1..6a0a8a9c7 100644 --- a/client/src/containers/Preferences/Currencies/components.js +++ b/client/src/containers/Preferences/Currencies/components.js @@ -69,6 +69,7 @@ export function useCurrenciesTableColumns() { { Header: 'Currency sign', width: 120, + accessor: 'currency_sign' }, { id: 'actions', diff --git a/client/src/lang/en/index.js b/client/src/lang/en/index.js index 455f809c3..16ff6167e 100644 --- a/client/src/lang/en/index.js +++ b/client/src/lang/en/index.js @@ -1034,4 +1034,6 @@ export default { 'This customer cannot be deleted as it is associated with transactions.', this_vendor_cannot_be_deleted_as_it_is_associated_with_transactions: 'This vendor cannot be deleted as it is associated with transactions.', + + currency_sign: 'Currency sign', }; diff --git a/client/src/style/pages/Billing/BillingPage.scss b/client/src/style/pages/Billing/BillingPage.scss index 3583e6898..7250ddaa8 100644 --- a/client/src/style/pages/Billing/BillingPage.scss +++ b/client/src/style/pages/Billing/BillingPage.scss @@ -14,11 +14,11 @@ } .plan-radio, .period-radio{ - background: transparent; + background: #fff; border-color: #bbcad4; &.is-selected{ - background: #f1f3fb; + background: #fff; border-color: #0269ff } } diff --git a/client/src/style/pages/Currency/CurrencyFormDialog.scss b/client/src/style/pages/Currency/CurrencyFormDialog.scss index ba6b4d0a2..9440a1cdf 100644 --- a/client/src/style/pages/Currency/CurrencyFormDialog.scss +++ b/client/src/style/pages/Currency/CurrencyFormDialog.scss @@ -12,4 +12,10 @@ height: 170px; } } + + .bp3-dialog-footer{ + .bp3-button{ + min-width: 75px; + } + } } diff --git a/client/src/utils.js b/client/src/utils.js index 937cabf89..891916f12 100644 --- a/client/src/utils.js +++ b/client/src/utils.js @@ -1,5 +1,7 @@ import moment from 'moment'; import _, { castArray } from 'lodash'; +import Currencies from 'js-money/lib/currency'; + import { Intent } from '@blueprintjs/core'; import Currency from 'js-money/lib/currency'; import accounting from 'accounting'; @@ -620,3 +622,17 @@ export const updateTableRow = (rowIndex, columnId, value) => (old) => { export const transformGeneralSettings = (data) => { return _.mapKeys(data, (value, key) => _.snakeCase(key)); }; + +const getCurrenciesOptions = () => { + return Object.keys(Currencies).map((currencyCode) => { + const currency = Currencies[currencyCode]; + + return { + ...currency, + currency_code: currencyCode, + formatted_name: `${currencyCode} - ${currency.name}`, + }; + }) +} + +export const currenciesOptions = getCurrenciesOptions(); \ No newline at end of file diff --git a/server/src/api/controllers/Accounts.ts b/server/src/api/controllers/Accounts.ts index 82601b7b4..ccce9c4a8 100644 --- a/server/src/api/controllers/Accounts.ts +++ b/server/src/api/controllers/Accounts.ts @@ -23,12 +23,6 @@ export default class AccountsController extends BaseController { router() { const router = Router(); - router.post( - '/bulk/:type(activate|inactivate)', - [...this.bulkSelectIdsQuerySchema], - this.validationResult, - asyncMiddleware(this.bulkToggleActivateAccounts.bind(this)) - ); router.post( '/:id/activate', [...this.accountParamSchema], @@ -77,13 +71,6 @@ export default class AccountsController extends BaseController { this.dynamicListService.handlerErrorsToResponse, this.catchServiceErrors ); - router.delete( - '/', - [...this.bulkSelectIdsQuerySchema], - this.validationResult, - asyncMiddleware(this.deleteBulkAccounts.bind(this)), - this.catchServiceErrors - ); router.delete( '/:id', [...this.accountParamSchema], @@ -140,13 +127,6 @@ export default class AccountsController extends BaseController { ]; } - get bulkSelectIdsQuerySchema() { - return [ - query('ids').isArray({ min: 1 }), - query('ids.*').isNumeric().toInt(), - ]; - } - get closingAccountSchema() { return [ check('to_account_id').exists().isNumeric().toInt(), @@ -293,62 +273,6 @@ export default class AccountsController extends BaseController { } } - /** - * Bulk activate/inactivate accounts. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next - */ - async bulkToggleActivateAccounts( - req: Request, - res: Response, - next: Function - ) { - const { type } = req.params; - const { tenantId } = req; - const { ids: accountsIds } = req.query; - - try { - const isActive = type === 'activate' ? true : false; - await this.accountsService.activateAccounts( - tenantId, - accountsIds, - isActive - ); - - const activatedText = isActive ? 'activated' : 'inactivated'; - - return res.status(200).send({ - ids: accountsIds, - message: `The given accounts have been ${activatedText} successfully`, - }); - } catch (error) { - next(error); - } - } - - /** - * Deletes accounts in bulk. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next - */ - async deleteBulkAccounts(req: Request, res: Response, next: NextFunction) { - const { ids: accountsIds } = req.query; - const { tenantId } = req; - - try { - await this.accountsService.deleteAccounts(tenantId, accountsIds); - - return res.status(200).send({ - ids: accountsIds, - message: 'The given accounts have been deleted successfully.', - }); - } catch (error) { - next(error); - } - } - /** * Retrieve accounts datatable list. * @param {Request} req diff --git a/server/src/api/controllers/Contacts/Contacts.ts b/server/src/api/controllers/Contacts/Contacts.ts index 5a6d79866..f2c257639 100644 --- a/server/src/api/controllers/Contacts/Contacts.ts +++ b/server/src/api/controllers/Contacts/Contacts.ts @@ -304,14 +304,4 @@ export default class ContactsController extends BaseController { get specificContactSchema(): ValidationChain[] { return [param('id').exists().isNumeric().toInt()]; } - - /** - * @returns {ValidationChain[]} - */ - get bulkContactsSchema(): ValidationChain[] { - return [ - query('ids').isArray({ min: 1 }), - query('ids.*').isNumeric().toInt(), - ]; - } } diff --git a/server/src/api/controllers/Contacts/Vendors.ts b/server/src/api/controllers/Contacts/Vendors.ts index 31a4f50f7..d47ec60fd 100644 --- a/server/src/api/controllers/Contacts/Vendors.ts +++ b/server/src/api/controllers/Contacts/Vendors.ts @@ -219,28 +219,6 @@ export default class VendorsController extends ContactsController { } } - /** - * Deletes vendors in bulk. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next - */ - async deleteBulkVendors(req: Request, res: Response, next: NextFunction) { - const { ids: contactsIds } = req.query; - const { tenantId, user } = req; - - try { - await this.vendorsService.deleteBulkVendors(tenantId, contactsIds, user) - - return res.status(200).send({ - ids: contactsIds, - message: 'The vendors have been deleted successfully.', - }); - } catch (error) { - next(error); - } - } - /** * Retrieve vendors datatable list. * @param {Request} req diff --git a/server/src/api/controllers/Currencies.ts b/server/src/api/controllers/Currencies.ts index 1c8417936..f878ceea7 100644 --- a/server/src/api/controllers/Currencies.ts +++ b/server/src/api/controllers/Currencies.ts @@ -49,13 +49,17 @@ export default class CurrenciesController extends BaseController { get currencyDTOSchemaValidation(): ValidationChain[] { return [ - check('currency_name').exists().trim().escape(), - check('currency_code').exists().trim().escape(), + check('currency_name').exists().trim(), + check('currency_code').exists().trim(), + check('currency_sign').exists().trim(), ]; } get currencyEditDTOSchemaValidation(): ValidationChain[] { - return [check('currency_name').exists().trim().escape()]; + return [ + check('currency_name').exists().trim(), + check('currency_sign').exists().trim(), + ]; } get currencyIdParamSchema(): ValidationChain[] { @@ -84,7 +88,10 @@ export default class CurrenciesController extends BaseController { try { const currencies = await this.currenciesService.listCurrencies(tenantId); - return res.status(200).send({ currencies: [...currencies] }); + + return res.status(200).send({ + currencies: this.transfromToResponse(currencies), + }); } catch (error) { next(error); } @@ -142,7 +149,7 @@ export default class CurrenciesController extends BaseController { async editCurrency(req: Request, res: Response, next: Function) { const { tenantId } = req; const { id: currencyId } = req.params; - const { body: editCurrencyDTO } = req; + const editCurrencyDTO = this.matchedBodyData(req); try { const currency = await this.currenciesService.editCurrency( @@ -180,7 +187,22 @@ export default class CurrenciesController extends BaseController { } if (error.errorType === 'currency_code_exists') { return res.boom.badRequest(null, { - errors: [{ type: 'CURRENCY_CODE_EXISTS', code: 200 }], + errors: [{ + type: 'CURRENCY_CODE_EXISTS', + message: 'The given currency code is already exists.', + code: 200, + }], + }); + } + if (error.errorType === 'CANNOT_DELETE_BASE_CURRENCY') { + return res.boom.badRequest(null, { + errors: [ + { + type: 'CANNOT_DELETE_BASE_CURRENCY', + code: 300, + message: 'Cannot delete the base currency.', + }, + ], }); } } diff --git a/server/src/api/controllers/Expenses.ts b/server/src/api/controllers/Expenses.ts index 1df27843b..c30dca97e 100644 --- a/server/src/api/controllers/Expenses.ts +++ b/server/src/api/controllers/Expenses.ts @@ -121,13 +121,6 @@ export default class ExpensesController extends BaseController { return [param('id').exists().isNumeric().toInt()]; } - get bulkSelectSchema() { - return [ - query('ids').isArray({ min: 1 }), - query('ids.*').isNumeric().toInt(), - ]; - } - get expensesListSchema() { return [ query('custom_view_id').optional().isNumeric().toInt(), diff --git a/server/src/api/controllers/ItemCategories.ts b/server/src/api/controllers/ItemCategories.ts index 22aa6b20a..73b6acf8e 100644 --- a/server/src/api/controllers/ItemCategories.ts +++ b/server/src/api/controllers/ItemCategories.ts @@ -40,13 +40,6 @@ export default class ItemsCategoriesController extends BaseController { asyncMiddleware(this.newCategory.bind(this)), this.handlerServiceError ); - router.delete( - '/', - [...this.categoriesBulkValidationSchema], - this.validationResult, - asyncMiddleware(this.bulkDeleteCategories.bind(this)), - this.handlerServiceError - ); router.delete( '/:id', [...this.specificCategoryValidationSchema], @@ -103,16 +96,6 @@ export default class ItemsCategoriesController extends BaseController { ]; } - /** - * Validate items categories bulk actions. - */ - get categoriesBulkValidationSchema() { - return [ - query('ids').isArray({ min: 1 }), - query('ids.*').isNumeric().toInt(), - ]; - } - /** * Validate items categories schema. */ @@ -263,31 +246,6 @@ export default class ItemsCategoriesController extends BaseController { } } - /** - * Bulk delete the given item categories. - * @param {Request} req - - * @param {Response} res - - * @return {Response} - */ - async bulkDeleteCategories(req: Request, res: Response, next: NextFunction) { - const itemCategoriesIds = req.query.ids; - const { tenantId, user } = req; - - try { - await this.itemCategoriesService.deleteItemCategories( - tenantId, - itemCategoriesIds, - user - ); - return res.status(200).send({ - ids: itemCategoriesIds, - message: 'The item categories have been deleted successfully.', - }); - } catch (error) { - next(error); - } - } - /** * Handles service error. * @param {Error} error diff --git a/server/src/api/controllers/Items.ts b/server/src/api/controllers/Items.ts index 697addee5..67df445ef 100644 --- a/server/src/api/controllers/Items.ts +++ b/server/src/api/controllers/Items.ts @@ -172,17 +172,6 @@ export default class ItemsController extends BaseController { return [param('id').exists().isNumeric().toInt()]; } - /** - * Bulk select validation schema. - * @return {ValidationChain[]} - */ - get validateBulkSelectSchema(): ValidationChain[] { - return [ - query('ids').isArray({ min: 1 }), - query('ids.*').isNumeric().toInt(), - ]; - } - /** * Validate list query schema */ diff --git a/server/src/api/controllers/ManualJournals.ts b/server/src/api/controllers/ManualJournals.ts index 9b38ed1b4..eec84227a 100644 --- a/server/src/api/controllers/ManualJournals.ts +++ b/server/src/api/controllers/ManualJournals.ts @@ -35,13 +35,6 @@ export default class ManualJournalsController extends BaseController { asyncMiddleware(this.getManualJournal.bind(this)), this.catchServiceErrors.bind(this) ); - router.post( - '/publish', - [...this.manualJournalIdsSchema], - this.validationResult, - asyncMiddleware(this.publishManualJournals.bind(this)), - this.catchServiceErrors.bind(this) - ); router.post( '/:id/publish', [...this.manualJournalParamSchema], @@ -63,13 +56,6 @@ export default class ManualJournalsController extends BaseController { asyncMiddleware(this.deleteManualJournal.bind(this)), this.catchServiceErrors.bind(this) ); - router.delete( - '/', - [...this.manualJournalIdsSchema], - this.validationResult, - asyncMiddleware(this.deleteBulkManualJournals.bind(this)), - this.catchServiceErrors.bind(this) - ); router.post( '/', [...this.manualJournalValidationSchema], @@ -87,16 +73,6 @@ export default class ManualJournalsController extends BaseController { return [param('id').exists().isNumeric().toInt()]; } - /** - * Manual journal bulk ids validation schema. - */ - get manualJournalIdsSchema() { - return [ - query('ids').isArray({ min: 1 }), - query('ids.*').isNumeric().toInt(), - ]; - } - /** * Manual journal DTO schema. */ @@ -277,34 +253,6 @@ export default class ManualJournalsController extends BaseController { } } - /** - * Publish the given manual journals in bulk. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next - */ - async publishManualJournals(req: Request, res: Response, next: NextFunction) { - const { tenantId } = req; - const { ids: manualJournalsIds } = req.query; - - try { - const { - meta: { alreadyPublished, published, total }, - } = await this.manualJournalsService.publishManualJournals( - tenantId, - manualJournalsIds - ); - - return res.status(200).send({ - ids: manualJournalsIds, - message: 'The manual journals have been published successfully.', - meta: { alreadyPublished, published, total }, - }); - } catch (error) { - next(error); - } - } - /** * Delete the given manual journal. * @param {Request} req @@ -330,35 +278,6 @@ export default class ManualJournalsController extends BaseController { } } - /** - * Deletes manual journals in bulk. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next - */ - async deleteBulkManualJournals( - req: Request, - res: Response, - next: NextFunction - ) { - const { tenantId } = req; - const { ids: manualJournalsIds } = req.query; - - try { - await this.manualJournalsService.deleteManualJournals( - tenantId, - manualJournalsIds - ); - - return res.status(200).send({ - ids: manualJournalsIds, - message: 'Manual journal have been delete successfully.', - }); - } catch (error) { - next(error); - } - } - /** * Retrieve manual journals list. * @param {Request} req diff --git a/server/src/database/migrations/20200419171451_create_currencies_table.js b/server/src/database/migrations/20200419171451_create_currencies_table.js index c2d847140..4d06717b9 100644 --- a/server/src/database/migrations/20200419171451_create_currencies_table.js +++ b/server/src/database/migrations/20200419171451_create_currencies_table.js @@ -4,6 +4,7 @@ exports.up = function(knex) { table.increments(); table.string('currency_name').index(); table.string('currency_code', 4).index(); + table.string('currency_sign').index(); table.timestamps(); }).raw('ALTER TABLE `CURRENCIES` AUTO_INCREMENT = 1000'); }; diff --git a/server/src/interfaces/Currency.ts b/server/src/interfaces/Currency.ts index 112d62ac5..2bcfc5620 100644 --- a/server/src/interfaces/Currency.ts +++ b/server/src/interfaces/Currency.ts @@ -3,14 +3,17 @@ export interface ICurrencyDTO { currencyName: string, currencyCode: string, + currencySign: string, }; export interface ICurrencyEditDTO { currencyName: string, + currencySign: string, } export interface ICurrency { id: number, currencyName: string, currencyCode: string, + currencySign: string, createdAt: Date, updatedAt: Date, }; diff --git a/server/src/services/Currencies/CurrenciesService.ts b/server/src/services/Currencies/CurrenciesService.ts index 18e7702ed..adb94cfa5 100644 --- a/server/src/services/Currencies/CurrenciesService.ts +++ b/server/src/services/Currencies/CurrenciesService.ts @@ -16,7 +16,8 @@ import TenancyService from 'services/Tenancy/TenancyService'; const ERRORS = { CURRENCY_NOT_FOUND: 'currency_not_found', CURRENCY_CODE_EXISTS: 'currency_code_exists', - BASE_CURRENCY_INVALID: 'BASE_CURRENCY_INVALID' + BASE_CURRENCY_INVALID: 'BASE_CURRENCY_INVALID', + CANNOT_DELETE_BASE_CURRENCY: 'CANNOT_DELETE_BASE_CURRENCY' }; @Service() @@ -131,6 +132,7 @@ export default class CurrenciesService implements ICurrenciesService { tenantId, currencyDTO, }); + // Validate currency code uniquiness. await this.validateCurrencyCodeUniquiness( tenantId, currencyDTO.currencyCode @@ -174,6 +176,22 @@ export default class CurrenciesService implements ICurrenciesService { return currency; } + /** + * Validate cannot delete base currency. + * @param {number} tenantId + * @param {string} currencyCode + */ + validateCannotDeleteBaseCurrency(tenantId: number, currencyCode: string) { + const settings = this.tenancy.settings(tenantId); + const baseCurrency = settings.get({ + group: 'organization', + key: 'base_currency', + }); + if (baseCurrency === currencyCode) { + throw new ServiceError(ERRORS.CANNOT_DELETE_BASE_CURRENCY); + } + } + /** * Delete the given currency code. * @param {number} tenantId @@ -192,6 +210,9 @@ export default class CurrenciesService implements ICurrenciesService { await this.getCurrencyByCodeOrThrowError(tenantId, currencyCode); + // Validate currency code not equals base currency. + await this.validateCannotDeleteBaseCurrency(tenantId, currencyCode); + await Currency.query().where('currency_code', currencyCode).delete(); this.logger.info('[currencies] the currency deleted successfully.', { tenantId, @@ -207,10 +228,20 @@ export default class CurrenciesService implements ICurrenciesService { public async listCurrencies(tenantId: number): Promise { const { Currency } = this.tenancy.models(tenantId); + const settings = this.tenancy.settings(tenantId); + const baseCurrency = settings.get({ + group: 'organization', + key: 'base_currency', + }); + const currencies = await Currency.query().onBuild((query) => { query.orderBy('createdAt', 'ASC'); }); - return currencies; + const formattedCurrencies = currencies.map((currency) => ({ + isBaseCurrency: baseCurrency === currency.currencyCode, + ...currency, + })); + return formattedCurrencies; } /** diff --git a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetService.ts b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetService.ts index 7b4ef5526..5fb30b244 100644 --- a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetService.ts +++ b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetService.ts @@ -95,8 +95,7 @@ export default class BalanceSheetStatementService // Settings tenant service. const settings = this.tenancy.settings(tenantId); const baseCurrency = settings.get({ - group: 'organization', - key: 'base_currency', + group: 'organization', key: 'base_currency', }); const filter = {