diff --git a/server/package.json b/server/package.json index e68ae1703..dd5625f35 100644 --- a/server/package.json +++ b/server/package.json @@ -20,6 +20,7 @@ "agenda": "^3.1.0", "agendash": "^1.0.0", "app-root-path": "^3.0.0", + "async": "^3.2.0", "axios": "^0.20.0", "bcryptjs": "^2.4.3", "compression": "^1.7.4", diff --git a/server/src/api/controllers/BaseController.ts b/server/src/api/controllers/BaseController.ts index 4d3bb8b3e..e891ea893 100644 --- a/server/src/api/controllers/BaseController.ts +++ b/server/src/api/controllers/BaseController.ts @@ -5,7 +5,6 @@ import { mapKeysDeep } from 'utils' import asyncMiddleware from 'api/middleware/asyncMiddleware'; export default class BaseController { - /** * Converts plain object keys to cameCase style. * @param {Object} data @@ -75,6 +74,10 @@ export default class BaseController { return response; } + /** + * Async middleware. + * @param {function} callback + */ asyncMiddleware(callback) { return asyncMiddleware(callback); } diff --git a/server/src/api/controllers/ManualJournals.ts b/server/src/api/controllers/ManualJournals.ts index 266bddcaf..5bff2449c 100644 --- a/server/src/api/controllers/ManualJournals.ts +++ b/server/src/api/controllers/ManualJournals.ts @@ -245,7 +245,7 @@ export default class ManualJournalsController extends BaseController { manualJournalId ); return res.status(200).send({ - manual_journal: manualJournal + manual_journal: manualJournal, }); } catch (error) { next(error); @@ -449,14 +449,25 @@ export default class ManualJournalsController extends BaseController { ], }); } + if (error.errorType === 'CONTACTS_SHOULD_ASSIGN_WITH_VALID_ACCOUNT') { + return res.boom.badRequest('', { + errors: [ + { + type: 'CONTACTS_SHOULD_ASSIGN_WITH_VALID_ACCOUNT', + code: 700, + meta: this.transfromToResponse(error.payload), + }, + ], + }); + } if (error.errorType === 'contacts_not_found') { return res.boom.badRequest('', { - errors: [{ type: 'CONTACTS_NOT_FOUND', code: 700 }], + errors: [{ type: 'CONTACTS_NOT_FOUND', code: 800 }], }); } if (error.errorType === 'MANUAL_JOURNAL_ALREADY_PUBLISHED') { return res.boom.badRequest('', { - errors: [{ type: 'MANUAL_JOURNAL_ALREADY_PUBLISHED', code: 800 }], + errors: [{ type: 'MANUAL_JOURNAL_ALREADY_PUBLISHED', code: 900 }], }); } } diff --git a/server/src/api/controllers/Sales/SalesInvoices.ts b/server/src/api/controllers/Sales/SalesInvoices.ts index ff8216b66..8db65f6c6 100644 --- a/server/src/api/controllers/Sales/SalesInvoices.ts +++ b/server/src/api/controllers/Sales/SalesInvoices.ts @@ -106,8 +106,14 @@ export default class SaleInvoicesController extends BaseController { check('entries.*.item_id').exists().isNumeric().toInt(), check('entries.*.rate').exists().isNumeric().toFloat(), check('entries.*.quantity').exists().isNumeric().toFloat(), - check('entries.*.discount').optional({ nullable: true }).isNumeric().toFloat(), - check('entries.*.description').optional({ nullable: true }).trim().escape(), + check('entries.*.discount') + .optional({ nullable: true }) + .isNumeric() + .toFloat(), + check('entries.*.description') + .optional({ nullable: true }) + .trim() + .escape(), ]; } @@ -230,7 +236,11 @@ export default class SaleInvoicesController extends BaseController { try { // Deletes the sale invoice with associated entries and journal transaction. - await this.saleInvoiceService.deleteSaleInvoice(tenantId, saleInvoiceId, user); + await this.saleInvoiceService.deleteSaleInvoice( + tenantId, + saleInvoiceId, + user + ); return res.status(200).send({ id: saleInvoiceId, @@ -402,15 +412,23 @@ export default class SaleInvoicesController extends BaseController { } if (error.errorType === 'SALE_ESTIMATE_NOT_FOUND') { return res.boom.badRequest(null, { - errors: [ - { type: 'FROM_SALE_ESTIMATE_NOT_FOUND', code: 1200 }, - ], + errors: [{ type: 'FROM_SALE_ESTIMATE_NOT_FOUND', code: 1200 }], }); } if (error.errorType === 'SALE_ESTIMATE_CONVERTED_TO_INVOICE') { return res.boom.badRequest(null, { errors: [ - { type: 'SALE_ESTIMATE_IS_ALREADY_CONVERTED_TO_INVOICE', code: 1300 }, + { + type: 'SALE_ESTIMATE_IS_ALREADY_CONVERTED_TO_INVOICE', + code: 1300, + }, + ], + }); + } + if (error.errorType === 'INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT') { + return res.boom.badRequest(null, { + errors: [ + { type: 'INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT', code: 1400 }, ], }); } diff --git a/server/src/api/controllers/Subscription/Licenses.ts b/server/src/api/controllers/Subscription/Licenses.ts index ace83a13a..6db07831e 100644 --- a/server/src/api/controllers/Subscription/Licenses.ts +++ b/server/src/api/controllers/Subscription/Licenses.ts @@ -28,7 +28,6 @@ export default class LicensesController extends BaseController { challenge: true, }) ); - router.post( '/generate', this.generateLicenseSchema, diff --git a/server/src/loaders/events.ts b/server/src/loaders/events.ts index 2b1f8d323..e0478145f 100644 --- a/server/src/loaders/events.ts +++ b/server/src/loaders/events.ts @@ -7,12 +7,10 @@ import 'subscribers/manualJournals'; import 'subscribers/expenses'; import 'subscribers/Bills'; -import 'subscribers/Bills/SyncVendorsBalances'; import 'subscribers/Bills/WriteJournalEntries'; import 'subscribers/Bills/WriteInventoryTransactions'; import 'subscribers/SaleInvoices'; -import 'subscribers/SaleInvoices/SyncCustomersBalance'; import 'subscribers/SaleInvoices/WriteInventoryTransactions'; import 'subscribers/SaleInvoices/WriteJournalEntries'; diff --git a/server/src/models/Contact.js b/server/src/models/Contact.js index fb92f8471..fb4953ef5 100644 --- a/server/src/models/Contact.js +++ b/server/src/models/Contact.js @@ -20,7 +20,25 @@ export default class Contact extends TenantModel { * Defined virtual attributes. */ static get virtualAttributes() { - return ['closingBalance']; + return ['contactNormal', 'closingBalance']; + } + + /** + * Retrieve the contact normal by the given contact type. + */ + static getContactNormalByType(contactType) { + const types = { + 'vendor': 'credit', + 'customer': 'debit', + }; + return types[contactType]; + } + + /** + * Retrieve the contact noraml; + */ + get contactNormal() { + return Contact.getContactNormalByType(this.contactService); } /** diff --git a/server/src/models/Customer.js b/server/src/models/Customer.js index 65137b63f..70c9092a1 100644 --- a/server/src/models/Customer.js +++ b/server/src/models/Customer.js @@ -41,7 +41,7 @@ export default class Customer extends TenantModel { * Defined virtual attributes. */ static get virtualAttributes() { - return ['closingBalance']; + return ['closingBalance', 'contactNormal']; } /** @@ -51,6 +51,13 @@ export default class Customer extends TenantModel { return this.openingBalance + this.balance; } + /** + * Retrieve the contact noraml; + */ + get contactNormal() { + return 'debit'; + } + /** * Model modifiers. */ diff --git a/server/src/models/Vendor.js b/server/src/models/Vendor.js index 5e5ee96fb..d56d6f390 100644 --- a/server/src/models/Vendor.js +++ b/server/src/models/Vendor.js @@ -40,7 +40,7 @@ export default class Vendor extends TenantModel { * Defined virtual attributes. */ static get virtualAttributes() { - return ['closingBalance']; + return ['closingBalance', 'contactNormal']; } /** @@ -50,6 +50,13 @@ export default class Vendor extends TenantModel { return this.openingBalance + this.balance; } + /** + * Retrieve the contact noraml; + */ + get contactNormal() { + return 'debit'; + } + /** * Model modifiers. */ diff --git a/server/src/services/Accounting/JournalCommands.ts b/server/src/services/Accounting/JournalCommands.ts index 9453a34d7..5a3135fa5 100644 --- a/server/src/services/Accounting/JournalCommands.ts +++ b/server/src/services/Accounting/JournalCommands.ts @@ -17,7 +17,6 @@ import { export default class JournalCommands { journal: JournalPoster; - models: any; repositories: any; @@ -77,7 +76,6 @@ export default class JournalCommands { credit: bill.amount, account: payableAccount.id, contactId: bill.vendorId, - contactType: 'Vendor', index: 1, }); this.journal.credit(payableEntry); @@ -113,8 +111,8 @@ export default class JournalCommands { ) { const { accountRepository } = this.repositories; - const openingBalanceAccount = await accountRepository.findOne({ - slug: 'opening-balance', + const incomeAccount = await accountRepository.findOne({ + slug: 'other-income', }); const receivableAccount = await accountRepository.findOne({ slug: 'accounts-receivable', @@ -123,8 +121,6 @@ export default class JournalCommands { const commonEntry = { referenceType: 'CustomerOpeningBalance', referenceId: customerId, - contactType: 'Customer', - contactId: customerId, date: openingBalanceAt, userId, }; @@ -133,13 +129,14 @@ export default class JournalCommands { credit: 0, debit: openingBalance, account: receivableAccount.id, + contactId: customerId, index: 1, }); const creditEntry = new JournalEntry({ ...commonEntry, credit: openingBalance, debit: 0, - account: openingBalanceAccount.id, + account: incomeAccount.id, index: 2, }); this.journal.debit(debitEntry); @@ -171,8 +168,6 @@ export default class JournalCommands { const commonEntry = { referenceType: 'VendorOpeningBalance', referenceId: vendorId, - contactType: 'Vendor', - contactId: vendorId, date: openingBalanceAt, userId: authorizedUserId, }; @@ -182,6 +177,7 @@ export default class JournalCommands { credit: openingBalance, debit: 0, index: 1, + contactId: vendorId, }); const debitEntry = new JournalEntry({ ...commonEntry, @@ -297,7 +293,6 @@ export default class JournalCommands { account: entry.accountId, referenceType: 'Journal', referenceId: manualJournalObj.id, - contactType: entry.contactType, contactId: entry.contactId, note: entry.note, date: manualJournalObj.date, @@ -379,6 +374,7 @@ export default class JournalCommands { ...commonEntry, debit: saleInvoice.balance, account: receivableAccountId, + contactId: saleInvoice.customerId, index: 1, }); this.journal.debit(receivableEntry); diff --git a/server/src/services/Accounting/JournalContacts.ts b/server/src/services/Accounting/JournalContacts.ts new file mode 100644 index 000000000..6ae2b22c5 --- /dev/null +++ b/server/src/services/Accounting/JournalContacts.ts @@ -0,0 +1,74 @@ +import async from 'async'; + +export default class JournalContacts { + saveContactBalanceQueue: any; + contactsBalanceTable: { + [key: number]: { credit: number; debit: number }; + } = {}; + + constructor(journal) { + this.journal = journal; + this.saveContactBalanceQueue = async.queue( + this.saveContactBalanceChangeTask.bind(this), + 10 + ); + } + /** + * Sets the contact balance change. + */ + private getContactsBalanceChanges(entry) { + if (!entry.contactId) { + return; + } + const change = { + debit: entry.debit, + credit: entry.credit, + }; + if (!this.contactsBalanceTable[entry.contactId]) { + this.contactsBalanceTable[entry.contactId] = { credit: 0, debit: 0 }; + } + if (change.credit) { + this.contactsBalanceTable[entry.contactId].credit += change.credit; + } + if (change.debit) { + this.contactsBalanceTable[entry.contactId].debit += change.debit; + } + } + + /** + * Save contacts balance change. + */ + saveContactsBalance() { + const balanceChanges = Object.entries( + this.contactsBalanceTable + ).map(([contactId, { credit, debit }]) => ({ contactId, credit, debit })); + + return this.saveContactBalanceQueue.pushAsync(balanceChanges); + } + + /** + * Saves contact balance change task. + * @param {number} contactId - Contact id. + * @param {number} credit - Credit amount. + * @param {number} debit - Debit amount. + */ + async saveContactBalanceChangeTask({ contactId, credit, debit }, callback) { + const { contactRepository } = this.repositories; + + const contact = await contactRepository.findOneById(contactId); + let balanceChange = 0; + + if (contact.contactNormal === 'credit') { + balanceChange += credit - debit; + } else { + balanceChange += debit - credit; + } + // Contact change balance. + await contactRepository.changeNumber( + { id: contactId }, + 'balance', + balanceChange + ); + callback(); + } +} diff --git a/server/src/services/Accounting/JournalFinancial.ts b/server/src/services/Accounting/JournalFinancial.ts index 6de40e8f8..8cdaeea8a 100644 --- a/server/src/services/Accounting/JournalFinancial.ts +++ b/server/src/services/Accounting/JournalFinancial.ts @@ -14,6 +14,4 @@ export default class JournalFinancial { this.journal = journal; this.accountsDepGraph = this.journal.accountsDepGraph; } - - } \ No newline at end of file diff --git a/server/src/services/Accounting/JournalPoster.ts b/server/src/services/Accounting/JournalPoster.ts index c1fa61623..9ed95cdfa 100644 --- a/server/src/services/Accounting/JournalPoster.ts +++ b/server/src/services/Accounting/JournalPoster.ts @@ -1,6 +1,7 @@ -import { omit, get } from 'lodash'; +import { omit, get, chain } from 'lodash'; import moment from 'moment'; import { Container } from 'typedi'; +import async from 'async'; import JournalEntry from 'services/Accounting/JournalEntry'; import TenancyService from 'services/Tenancy/TenancyService'; import { @@ -11,6 +12,19 @@ import { TEntryType, } from 'interfaces'; +const CONTACTS_CONFIG = [ + { + accountBySlug: 'accounts-receivable', + contactService: 'customer', + assignRequired: true, + }, + { + accountBySlug: 'accounts-payable', + contactService: 'vendor', + assignRequired: true, + }, +]; + export default class JournalPoster implements IJournalPoster { tenantId: number; tenancy: TenancyService; @@ -24,6 +38,10 @@ export default class JournalPoster implements IJournalPoster { accountsDepGraph: IAccountsChange; accountsBalanceTable: { [key: number]: number } = {}; + contactsBalanceTable: { + [key: number]: { credit: number; debit: number }[]; + } = {}; + saveContactBalanceQueue: any; /** * Journal poster constructor. @@ -39,6 +57,10 @@ export default class JournalPoster implements IJournalPoster { if (accountsGraph) { this.accountsDepGraph = accountsGraph; } + this.saveContactBalanceQueue = async.queue( + this.saveContactBalanceChangeTask.bind(this), + 10 + ); } /** @@ -69,7 +91,7 @@ export default class JournalPoster implements IJournalPoster { } /** - * + * Detarmines the ledger is empty. */ public isEmpty() { return this.entries.length === 0; @@ -85,6 +107,7 @@ export default class JournalPoster implements IJournalPoster { } this.entries.push(entryModel.entry); this.setAccountBalanceChange(entryModel.entry); + this.setContactBalanceChange(entryModel.entry); } /** @@ -97,6 +120,83 @@ export default class JournalPoster implements IJournalPoster { } this.entries.push(entryModel.entry); this.setAccountBalanceChange(entryModel.entry); + this.setContactBalanceChange(entryModel.entry); + } + + /** + * Sets the contact balance change. + */ + private setContactBalanceChange(entry) { + if (!entry.contactId) { + return; + } + const change = { + debit: entry.debit || 0, + credit: entry.credit || 0, + account: entry.account, + }; + if (!this.contactsBalanceTable[entry.contactId]) { + this.contactsBalanceTable[entry.contactId] = []; + } + this.contactsBalanceTable[entry.contactId].push(change); + } + + /** + * Save contacts balance change. + */ + async saveContactsBalance() { + await this.initAccountsDepGraph(); + + const balanceChanges = Object.entries(this.contactsBalanceTable).map( + ([contactId, entries]) => ({ + contactId, + entries: entries.filter((entry) => { + const account = this.accountsDepGraph.getNodeData(entry.account); + + return ( + account && + CONTACTS_CONFIG.some((config) => { + return config.accountBySlug === account.slug; + }) + ); + }), + }) + ); + + const balanceEntries = chain(balanceChanges) + .map((change) => change.entries.map(entry => ({ + ...entry, + contactId: change.contactId + }))) + .flatten() + .value(); + + return this.saveContactBalanceQueue.pushAsync(balanceEntries); + } + + /** + * Saves contact balance change task. + * @param {number} contactId - Contact id. + * @param {number} credit - Credit amount. + * @param {number} debit - Debit amount. + */ + async saveContactBalanceChangeTask({ contactId, credit, debit }) { + const { contactRepository } = this.repositories; + + const contact = await contactRepository.findOneById(contactId); + let balanceChange = 0; + + if (contact.contactNormal === 'credit') { + balanceChange += credit - debit; + } else { + balanceChange += debit - credit; + } + // Contact change balance. + await contactRepository.changeNumber( + { id: contactId }, + 'balance', + balanceChange + ); } /** @@ -321,7 +421,8 @@ export default class JournalPoster implements IJournalPoster { entry.credit = -1 * entry.credit; entry.debit = -1 * entry.debit; - this.setAccountBalanceChange(entry, entry.accountNormal); + this.setAccountBalanceChange(entry); + this.setContactBalanceChange(entry); }); this.deletedEntriesIds.push(...removeEntries.map((entry) => entry.id)); } diff --git a/server/src/services/Contacts/ContactsService.ts b/server/src/services/Contacts/ContactsService.ts index e398bcc2f..3b962bd0b 100644 --- a/server/src/services/Contacts/ContactsService.ts +++ b/server/src/services/Contacts/ContactsService.ts @@ -240,7 +240,10 @@ export default class ContactsService { journal.fromTransactions(contactsTransactions); journal.removeEntries(); - await Promise.all([journal.saveBalance(), journal.deleteEntries()]); + await Promise.all([ + journal.saveBalance(), + journal.deleteEntries(), + ]); } /** diff --git a/server/src/services/Contacts/CustomersService.ts b/server/src/services/Contacts/CustomersService.ts index 8b05da20e..5157582e2 100644 --- a/server/src/services/Contacts/CustomersService.ts +++ b/server/src/services/Contacts/CustomersService.ts @@ -268,7 +268,11 @@ export default class CustomersService { openingBalanceAt, authorizedUserId ); - await Promise.all([journal.saveBalance(), journal.saveEntries()]); + await Promise.all([ + journal.saveBalance(), + journal.saveEntries(), + journal.saveContactsBalance(), + ]); } /** diff --git a/server/src/services/Contacts/VendorsService.ts b/server/src/services/Contacts/VendorsService.ts index 178498fd5..052505823 100644 --- a/server/src/services/Contacts/VendorsService.ts +++ b/server/src/services/Contacts/VendorsService.ts @@ -192,7 +192,11 @@ export default class VendorsService { openingBalanceAt, user ); - await Promise.all([journal.saveBalance(), journal.saveEntries()]); + await Promise.all([ + journal.saveBalance(), + journal.saveEntries(), + journal.saveContactsBalance(), + ]); } /** diff --git a/server/src/services/Items/ItemsService.ts b/server/src/services/Items/ItemsService.ts index 813aa0c77..f6d91557a 100644 --- a/server/src/services/Items/ItemsService.ts +++ b/server/src/services/Items/ItemsService.ts @@ -10,29 +10,12 @@ import DynamicListingService from 'services/DynamicListing/DynamicListService'; import TenancyService from 'services/Tenancy/TenancyService'; import { ServiceError } from 'exceptions'; import InventoryService from 'services/Inventory/Inventory'; -import { ACCOUNT_PARENT_TYPE, ACCOUNT_ROOT_TYPE, ACCOUNT_TYPE } from 'data/AccountTypes' - -const ERRORS = { - NOT_FOUND: 'NOT_FOUND', - ITEMS_NOT_FOUND: 'ITEMS_NOT_FOUND', - - ITEM_NAME_EXISTS: 'ITEM_NAME_EXISTS', - ITEM_CATEOGRY_NOT_FOUND: 'ITEM_CATEOGRY_NOT_FOUND', - COST_ACCOUNT_NOT_COGS: 'COST_ACCOUNT_NOT_COGS', - COST_ACCOUNT_NOT_FOUMD: 'COST_ACCOUNT_NOT_FOUMD', - SELL_ACCOUNT_NOT_FOUND: 'SELL_ACCOUNT_NOT_FOUND', - SELL_ACCOUNT_NOT_INCOME: 'SELL_ACCOUNT_NOT_INCOME', - - INVENTORY_ACCOUNT_NOT_FOUND: 'INVENTORY_ACCOUNT_NOT_FOUND', - INVENTORY_ACCOUNT_NOT_INVENTORY: 'INVENTORY_ACCOUNT_NOT_INVENTORY', - - ITEMS_HAVE_ASSOCIATED_TRANSACTIONS: 'ITEMS_HAVE_ASSOCIATED_TRANSACTIONS', - ITEM_HAS_ASSOCIATED_TRANSACTINS: 'ITEM_HAS_ASSOCIATED_TRANSACTINS', - - ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT: - 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT', -}; - +import { + ACCOUNT_PARENT_TYPE, + ACCOUNT_ROOT_TYPE, + ACCOUNT_TYPE, +} from 'data/AccountTypes'; +import { ERRORS } from './constants'; @Service() export default class ItemsService implements IItemsService { @Inject() @@ -130,7 +113,7 @@ export default class ItemsService implements IItemsService { }); throw new ServiceError(ERRORS.COST_ACCOUNT_NOT_FOUMD); - // Detarmines the cost of goods sold account. + // Detarmines the cost of goods sold account. } else if (!foundAccount.isParentType(ACCOUNT_PARENT_TYPE.EXPENSE)) { this.logger.info('[items] validate cost account not COGS type.', { tenantId, @@ -149,9 +132,7 @@ export default class ItemsService implements IItemsService { tenantId: number, sellAccountId: number ) { - const { - accountRepository, - } = this.tenancy.repositories(tenantId); + const { accountRepository } = this.tenancy.repositories(tenantId); this.logger.info('[items] validate sell account existance.', { tenantId, @@ -166,7 +147,6 @@ export default class ItemsService implements IItemsService { sellAccountId, }); throw new ServiceError(ERRORS.SELL_ACCOUNT_NOT_FOUND); - } else if (!foundAccount.isParentType(ACCOUNT_ROOT_TYPE.INCOME)) { this.logger.info('[items] sell account not income type.', { tenantId, @@ -185,9 +165,7 @@ export default class ItemsService implements IItemsService { tenantId: number, inventoryAccountId: number ) { - const { - accountRepository, - } = this.tenancy.repositories(tenantId); + const { accountRepository } = this.tenancy.repositories(tenantId); this.logger.info('[items] validate inventory account existance.', { tenantId, @@ -203,7 +181,6 @@ export default class ItemsService implements IItemsService { inventoryAccountId, }); throw new ServiceError(ERRORS.INVENTORY_ACCOUNT_NOT_FOUND); - } else if (!foundAccount.isAccountType(ACCOUNT_TYPE.INVENTORY)) { this.logger.info('[items] inventory account not inventory type.', { tenantId, diff --git a/server/src/services/Items/constants.ts b/server/src/services/Items/constants.ts new file mode 100644 index 000000000..927137e8a --- /dev/null +++ b/server/src/services/Items/constants.ts @@ -0,0 +1,21 @@ + +export const ERRORS = { + NOT_FOUND: 'NOT_FOUND', + ITEMS_NOT_FOUND: 'ITEMS_NOT_FOUND', + + ITEM_NAME_EXISTS: 'ITEM_NAME_EXISTS', + ITEM_CATEOGRY_NOT_FOUND: 'ITEM_CATEOGRY_NOT_FOUND', + COST_ACCOUNT_NOT_COGS: 'COST_ACCOUNT_NOT_COGS', + COST_ACCOUNT_NOT_FOUMD: 'COST_ACCOUNT_NOT_FOUMD', + SELL_ACCOUNT_NOT_FOUND: 'SELL_ACCOUNT_NOT_FOUND', + SELL_ACCOUNT_NOT_INCOME: 'SELL_ACCOUNT_NOT_INCOME', + + INVENTORY_ACCOUNT_NOT_FOUND: 'INVENTORY_ACCOUNT_NOT_FOUND', + INVENTORY_ACCOUNT_NOT_INVENTORY: 'INVENTORY_ACCOUNT_NOT_INVENTORY', + + ITEMS_HAVE_ASSOCIATED_TRANSACTIONS: 'ITEMS_HAVE_ASSOCIATED_TRANSACTIONS', + ITEM_HAS_ASSOCIATED_TRANSACTINS: 'ITEM_HAS_ASSOCIATED_TRANSACTINS', + + ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT: + 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT', +}; diff --git a/server/src/services/ManualJournals/ManualJournalsService.ts b/server/src/services/ManualJournals/ManualJournalsService.ts index d46cdaa01..be7737b95 100644 --- a/server/src/services/ManualJournals/ManualJournalsService.ts +++ b/server/src/services/ManualJournals/ManualJournalsService.ts @@ -9,6 +9,7 @@ import { ISystemUser, IManualJournal, IPaginationMeta, + IManualJournalEntry, } from 'interfaces'; import TenancyService from 'services/Tenancy/TenancyService'; import DynamicListingService from 'services/DynamicListing/DynamicListService'; @@ -20,30 +21,7 @@ import { import JournalPoster from 'services/Accounting/JournalPoster'; import JournalCommands from 'services/Accounting/JournalCommands'; import JournalPosterService from 'services/Sales/JournalPosterService'; - -const ERRORS = { - NOT_FOUND: 'manual_journal_not_found', - CREDIT_DEBIT_NOT_EQUAL_ZERO: 'credit_debit_not_equal_zero', - CREDIT_DEBIT_NOT_EQUAL: 'credit_debit_not_equal', - ACCCOUNTS_IDS_NOT_FOUND: 'acccounts_ids_not_found', - JOURNAL_NUMBER_EXISTS: 'journal_number_exists', - ENTRIES_SHOULD_ASSIGN_WITH_CONTACT: 'ENTRIES_SHOULD_ASSIGN_WITH_CONTACT', - CONTACTS_NOT_FOUND: 'contacts_not_found', - MANUAL_JOURNAL_ALREADY_PUBLISHED: 'MANUAL_JOURNAL_ALREADY_PUBLISHED', -}; - -const CONTACTS_CONFIG = [ - { - accountBySlug: 'accounts-receivable', - contactService: 'customer', - assignRequired: false, - }, - { - accountBySlug: 'accounts-payable', - contactService: 'vendor', - assignRequired: true, - }, -]; +import { ERRORS } from './constants'; @Service() export default class ManualJournalsService implements IManualJournalsService { @@ -159,8 +137,7 @@ export default class ManualJournalsService implements IManualJournalsService { const { Account } = this.tenancy.models(tenantId); const manualAccountsIds = manualJournalDTO.entries.map((e) => e.accountId); - const accounts = await Account.query() - .whereIn('id', manualAccountsIds); + const accounts = await Account.query().whereIn('id', manualAccountsIds); const storedAccountsIds = accounts.map((account) => account.id); @@ -203,29 +180,37 @@ export default class ManualJournalsService implements IManualJournalsService { * @param {string} accountBySlug * @param {string} contactType */ - private async validateAccountsWithContactType( + private async validateAccountWithContactType( tenantId: number, - manualJournalDTO: IManualJournalDTO, - accountBySlug: string, - contactType: string, - contactRequired: boolean = true - ): Promise { - const { accountRepository } = this.tenancy.repositories(tenantId); - const payableAccount = await accountRepository.findOne({ - slug: accountBySlug, - }); + entriesDTO: IManualJournalEntry[], - const entriesHasNoVendorContact = manualJournalDTO.entries.filter( - (e) => - e.accountId === payableAccount.id && - ((!e.contactId && contactRequired) || e.contactType !== contactType) + accountBySlug: string, + contactType: string + ): Promise { + const { Account, Contact } = this.tenancy.models(tenantId); + + // Retrieve account meta by the given account slug. + const account = await Account.query().findOne('slug', accountBySlug); + + // Filter all entries of the given account. + const accountEntries = entriesDTO.filter( + (entry) => entry.accountId === account.id ); - if (entriesHasNoVendorContact.length > 0) { - const indexes = entriesHasNoVendorContact.map((e) => e.index); + // Can't continue if there is no entry that associate to the given account. + if (accountEntries.length === 0) { + return; + } + // Filter entries that have no contact type or not equal the valid type. + const entriesNoContact = accountEntries.filter( + (entry) => !entry.contactType || entry.contactType !== contactType + ); + // Throw error in case one of entries that has invalid contact type. + if (entriesNoContact.length > 0) { + const indexes = entriesNoContact.map(e => e.index); throw new ServiceError(ERRORS.ENTRIES_SHOULD_ASSIGN_WITH_CONTACT, '', { + accountSlug: accountBySlug, contactType, - accountBySlug, indexes, }); } @@ -238,19 +223,22 @@ export default class ManualJournalsService implements IManualJournalsService { */ private async dynamicValidateAccountsWithContactType( tenantId: number, - manualJournalDTO: IManualJournalDTO + entriesDTO: IManualJournalEntry[] ): Promise { - return Promise.all( - CONTACTS_CONFIG.map(({ accountBySlug, contactService, assignRequired }) => - this.validateAccountsWithContactType( - tenantId, - manualJournalDTO, - accountBySlug, - contactService, - assignRequired - ) - ) - ); + return Promise.all([ + this.validateAccountWithContactType( + tenantId, + entriesDTO, + 'accounts-receivable', + 'customer' + ), + this.validateAccountWithContactType( + tenantId, + entriesDTO, + 'accounts-payable', + 'vendor' + ), + ]); } /** @@ -273,9 +261,9 @@ export default class ManualJournalsService implements IManualJournalsService { const entriesContactsIds = entriesContactPairs.map( (entry) => entry.contactId ); - // Retrieve all stored contacts on the storage from contacts entries. - const storedContacts = await contactRepository.findByIds( + const storedContacts = await contactRepository.findWhereIn( + 'id', entriesContactsIds ); // Converts the stored contacts to map with id as key and entry as value. @@ -287,8 +275,7 @@ export default class ManualJournalsService implements IManualJournalsService { entriesContactPairs.forEach((contactEntry) => { const storedContact = storedContactsMap.get(contactEntry.contactId); - // in case the contact id not found or contact type no equals pushed to - // not found contacts. + // in case the contact id not found. if ( !storedContact || storedContact.contactService !== contactEntry.contactType @@ -378,7 +365,7 @@ export default class ManualJournalsService implements IManualJournalsService { // Validate accounts with contact type from the given config. await this.dynamicValidateAccountsWithContactType( tenantId, - manualJournalDTO + manualJournalDTO.entries ); this.logger.info( '[manual_journal] trying to save manual journal to the storage.', @@ -446,7 +433,7 @@ export default class ManualJournalsService implements IManualJournalsService { // Validate accounts with contact type from the given config. await this.dynamicValidateAccountsWithContactType( tenantId, - manualJournalDTO + manualJournalDTO.entries ); // Transform manual journal DTO to model. const manualJournalObj = this.transformEditDTOToModel( @@ -525,7 +512,7 @@ export default class ManualJournalsService implements IManualJournalsService { tenantId: number, manualJournalsIds: number[] ): Promise<{ - oldManualJournals: IManualJournal[] + oldManualJournals: IManualJournal[]; }> { const { ManualJournal, ManualJournalEntry } = this.tenancy.models(tenantId); @@ -817,6 +804,7 @@ export default class ManualJournalsService implements IManualJournalsService { journal.saveBalance(), journal.deleteEntries(), journal.saveEntries(), + journal.saveContactsBalance(), ]); this.logger.info( '[manual_journal] the journal entries saved successfully.', diff --git a/server/src/services/ManualJournals/constants.ts b/server/src/services/ManualJournals/constants.ts new file mode 100644 index 000000000..788323e03 --- /dev/null +++ b/server/src/services/ManualJournals/constants.ts @@ -0,0 +1,24 @@ +export const ERRORS = { + NOT_FOUND: 'manual_journal_not_found', + CREDIT_DEBIT_NOT_EQUAL_ZERO: 'credit_debit_not_equal_zero', + CREDIT_DEBIT_NOT_EQUAL: 'credit_debit_not_equal', + ACCCOUNTS_IDS_NOT_FOUND: 'acccounts_ids_not_found', + JOURNAL_NUMBER_EXISTS: 'journal_number_exists', + ENTRIES_SHOULD_ASSIGN_WITH_CONTACT: 'ENTRIES_SHOULD_ASSIGN_WITH_CONTACT', + CONTACTS_NOT_FOUND: 'contacts_not_found', + ENTRIES_CONTACTS_NOT_FOUND: 'ENTRIES_CONTACTS_NOT_FOUND', + MANUAL_JOURNAL_ALREADY_PUBLISHED: 'MANUAL_JOURNAL_ALREADY_PUBLISHED', +}; + +export const CONTACTS_CONFIG = [ + { + accountBySlug: 'accounts-receivable', + contactService: 'customer', + assignRequired: true, + }, + { + accountBySlug: 'accounts-payable', + contactService: 'vendor', + assignRequired: true, + }, +]; diff --git a/server/src/services/Purchases/BillPayments.ts b/server/src/services/Purchases/BillPayments.ts index b126b3144..d229bcddc 100644 --- a/server/src/services/Purchases/BillPayments.ts +++ b/server/src/services/Purchases/BillPayments.ts @@ -27,7 +27,6 @@ import DynamicListingService from 'services/DynamicListing/DynamicListService'; import { entriesAmountDiff, formatDateFields } from 'utils'; import { ServiceError } from 'exceptions'; import { ACCOUNT_PARENT_TYPE } from 'data/AccountTypes'; -import PayableAgingSummaryService from 'services/FinancialStatements/AgingSummary/APAgingSummaryService'; const ERRORS = { BILL_VENDOR_NOT_FOUND: 'VENDOR_NOT_FOUND', @@ -452,6 +451,7 @@ export default class BillPaymentsService { .delete(); await BillPayment.query().where('id', billPaymentId).delete(); + // Triggers `onBillPaymentDeleted` event. await this.eventDispatcher.dispatch(events.billPayment.onDeleted, { tenantId, billPaymentId, @@ -490,7 +490,8 @@ export default class BillPaymentsService { */ public async recordJournalEntries( tenantId: number, - billPayment: IBillPayment + billPayment: IBillPayment, + override: boolean = false, ) { const { AccountTransaction } = this.tenancy.models(tenantId); const { accountRepository } = this.tenancy.repositories(tenantId); @@ -498,7 +499,7 @@ export default class BillPaymentsService { const paymentAmount = sumBy(billPayment.entries, 'paymentAmount'); const formattedDate = moment(billPayment.paymentDate).format('YYYY-MM-DD'); - // Retrieve AP account from the storage. + // Retrieve A/P account from the storage. const payableAccount = await accountRepository.findOne({ slug: 'accounts-payable', }); @@ -511,26 +512,27 @@ export default class BillPaymentsService { referenceType: 'BillPayment', date: formattedDate, }; - if (billPayment.id) { + if (override) { const transactions = await AccountTransaction.query() .whereIn('reference_type', ['BillPayment']) .where('reference_id', billPayment.id) .withGraphFetched('account'); - journal.loadEntries(transactions); + journal.fromTransactions(transactions); journal.removeEntries(); } const debitReceivable = new JournalEntry({ ...commonJournal, debit: paymentAmount, - contactType: 'Vendor', contactId: billPayment.vendorId, account: payableAccount.id, + index: 1, }); const creditPaymentAccount = new JournalEntry({ ...commonJournal, credit: paymentAmount, account: billPayment.paymentAccountId, + index: 2, }); journal.debit(debitReceivable); journal.credit(creditPaymentAccount); @@ -539,6 +541,7 @@ export default class BillPaymentsService { journal.deleteEntries(), journal.saveEntries(), journal.saveBalance(), + journal.saveContactsBalance(), ]); } @@ -554,7 +557,11 @@ export default class BillPaymentsService { await journalCommands.revertJournalEntries(billPaymentId, 'BillPayment'); - return Promise.all([journal.saveBalance(), journal.deleteEntries()]); + return Promise.all([ + journal.saveBalance(), + journal.deleteEntries(), + journal.saveContactsBalance(), + ]); } /** diff --git a/server/src/services/Purchases/Bills.ts b/server/src/services/Purchases/Bills.ts index beeef28f4..1e44b3cdc 100644 --- a/server/src/services/Purchases/Bills.ts +++ b/server/src/services/Purchases/Bills.ts @@ -527,6 +527,7 @@ export default class BillsService extends SalesInvoicesCost { journal.deleteEntries(), journal.saveEntries(), journal.saveBalance(), + journal.saveContactsBalance(), ]); } diff --git a/server/src/services/Sales/JournalPosterService.ts b/server/src/services/Sales/JournalPosterService.ts index 403f75524..0be67c3d5 100644 --- a/server/src/services/Sales/JournalPosterService.ts +++ b/server/src/services/Sales/JournalPosterService.ts @@ -27,7 +27,8 @@ export default class JournalPosterService { await Promise.all([ journal.deleteEntries(), - journal.saveBalance() + journal.saveBalance(), + journal.saveContactsBalance(), ]); } } \ No newline at end of file diff --git a/server/src/services/Sales/PaymentsReceives.ts b/server/src/services/Sales/PaymentsReceives.ts index 99be07bd3..621bd4de6 100644 --- a/server/src/services/Sales/PaymentsReceives.ts +++ b/server/src/services/Sales/PaymentsReceives.ts @@ -16,7 +16,6 @@ import { IPaymentReceiveEntryDTO, IPaymentReceivesFilter, ISaleInvoice, - ISystemService, ISystemUser, IPaymentReceivePageEntry, } from 'interfaces'; @@ -496,7 +495,7 @@ export default class PaymentReceiveService { paymentReceiveId: number, authorizedUser: ISystemUser ): Promise<{ - paymentReceive: Omit; + paymentReceive: Omit; entries: IPaymentReceivePageEntry[]; }> { const { PaymentReceive, SaleInvoice } = this.tenancy.models(tenantId); @@ -665,7 +664,6 @@ export default class PaymentReceiveService { const creditReceivable = new JournalEntry({ ...commonJournal, credit: paymentAmount, - contactType: 'Customer', contactId: paymentReceive.customerId, account: receivableAccount.id, index: 1, @@ -683,6 +681,7 @@ export default class PaymentReceiveService { journal.deleteEntries(), journal.saveEntries(), journal.saveBalance(), + journal.saveContactsBalance(), ]); } @@ -705,7 +704,11 @@ export default class PaymentReceiveService { await commands.revertJournalEntries(paymentReceiveId, 'PaymentReceive'); - await Promise.all([journal.saveBalance(), journal.deleteEntries()]); + await Promise.all([ + journal.saveBalance(), + journal.deleteEntries(), + journal.saveContactsBalance(), + ]); } /** diff --git a/server/src/services/Sales/SalesInvoices.ts b/server/src/services/Sales/SalesInvoices.ts index ceff4c9a8..bbfa3a863 100644 --- a/server/src/services/Sales/SalesInvoices.ts +++ b/server/src/services/Sales/SalesInvoices.ts @@ -29,6 +29,7 @@ import ItemsEntriesService from 'services/Items/ItemsEntriesService'; import CustomersService from 'services/Contacts/CustomersService'; import SaleEstimateService from 'services/Sales/SalesEstimate'; import JournalPosterService from './JournalPosterService'; +import SaleInvoiceRepository from 'repositories/SaleInvoiceRepository'; const ERRORS = { INVOICE_NUMBER_NOT_UNIQUE: 'INVOICE_NUMBER_NOT_UNIQUE', @@ -37,6 +38,8 @@ const ERRORS = { ENTRIES_ITEMS_IDS_NOT_EXISTS: 'ENTRIES_ITEMS_IDS_NOT_EXISTS', NOT_SELLABLE_ITEMS: 'NOT_SELLABLE_ITEMS', SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE', + INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT: + 'INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT', INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES: 'INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES', }; @@ -130,6 +133,20 @@ export default class SaleInvoicesService { return entries; } + /** + * Validate the invoice amount is bigger than payment amount before edit the invoice. + * @param {number} saleInvoiceAmount + * @param {number} paymentAmount + */ + validateInvoiceAmountBiggerPaymentAmount( + saleInvoiceAmount: number, + paymentAmount: number + ) { + if (saleInvoiceAmount < paymentAmount) { + throw new ServiceError(ERRORS.INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT); + } + } + /** * Validate whether sale invoice exists on the storage. * @param {Request} req @@ -267,7 +284,7 @@ export default class SaleInvoicesService { saleInvoiceDTO: any, authorizedUser: ISystemUser ): Promise { - const { SaleInvoice } = this.tenancy.models(tenantId); + const { saleInvoiceRepository } = this.tenancy.repositories(tenantId); const oldSaleInvoice = await this.getInvoiceOrThrowError( tenantId, @@ -309,12 +326,16 @@ export default class SaleInvoicesService { 'SaleInvoice', saleInvoiceDTO.entries ); + // Validate the invoice amount is not smaller than the invoice payment amount. + this.validateInvoiceAmountBiggerPaymentAmount( + saleInvoiceObj.balance, + oldSaleInvoice.paymentAmount + ); + this.logger.info('[sale_invoice] trying to update sale invoice.'); - const saleInvoice: ISaleInvoice = await SaleInvoice.query().upsertGraphAndFetch( - { - id: saleInvoiceId, - ...saleInvoiceObj, - } + const saleInvoice: ISaleInvoice = await saleInvoiceRepository.update( + { ...omit(saleInvoiceObj, ['paymentAmount']) }, + { id: saleInvoiceId } ); // Triggers `onSaleInvoiceEdited` event. await this.eventDispatcher.dispatch(events.saleInvoice.onEdited, { @@ -470,6 +491,7 @@ export default class SaleInvoicesService { await Promise.all([ journal.deleteEntries(), journal.saveBalance(), + journal.saveContactsBalance(), journal.saveEntries(), ]); } diff --git a/server/src/subscribers/Bills/SyncVendorsBalances.ts b/server/src/subscribers/Bills/SyncVendorsBalances.ts deleted file mode 100644 index 020e3bde7..000000000 --- a/server/src/subscribers/Bills/SyncVendorsBalances.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Container } from 'typedi'; -import { EventSubscriber, On } from 'event-dispatch'; -import events from 'subscribers/events'; -import TenancyService from 'services/Tenancy/TenancyService'; - -@EventSubscriber() -export default class BillSubscriber { - tenancy: TenancyService; - logger: any; - - /** - * Constructor method. - */ - constructor() { - this.tenancy = Container.get(TenancyService); - this.logger = Container.get('logger'); - } - - /** - * Handles vendor balance increment once bill created. - */ - @On(events.bill.onCreated) - async handleVendorBalanceIncrement({ tenantId, billId, bill }) { - const { vendorRepository } = this.tenancy.repositories(tenantId); - - // Increments vendor balance. - this.logger.info('[bill] trying to increment vendor balance.', { - tenantId, - billId, - }); - await vendorRepository.changeBalance(bill.vendorId, bill.amount); - } - - /** - * Handles vendor balance decrement once bill deleted. - */ - @On(events.bill.onDeleted) - async handleVendorBalanceDecrement({ tenantId, billId, oldBill }) { - const { vendorRepository } = this.tenancy.repositories(tenantId); - - // Decrements vendor balance. - this.logger.info('[bill] trying to decrement vendor balance.', { - tenantId, - billId, - }); - await vendorRepository.changeBalance(oldBill.vendorId, oldBill.amount * -1); - } - - /** - * Handles vendor balance difference change. - */ - @On(events.bill.onEdited) - async handleVendorBalanceDiffChange({ tenantId, billId, oldBill, bill }) { - const { vendorRepository } = this.tenancy.repositories(tenantId); - - // Changes the diff vendor balance between old and new amount. - this.logger.info('[bill[ change vendor the different balance.', { - tenantId, - billId, - }); - await vendorRepository.changeDiffBalance( - bill.vendorId, - bill.amount, - oldBill.amount, - oldBill.vendorId - ); - } -} diff --git a/server/src/subscribers/SaleInvoices/SyncCustomersBalance.ts b/server/src/subscribers/SaleInvoices/SyncCustomersBalance.ts deleted file mode 100644 index 015a85ead..000000000 --- a/server/src/subscribers/SaleInvoices/SyncCustomersBalance.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Container } from 'typedi'; -import { On, EventSubscriber } from 'event-dispatch'; -import events from 'subscribers/events'; -import TenancyService from 'services/Tenancy/TenancyService'; - -@EventSubscriber() -export default class SaleInvoiceSubscriber { - logger: any; - tenancy: TenancyService; - - /** - * Constructor method. - */ - constructor() { - this.logger = Container.get('logger'); - this.tenancy = Container.get(TenancyService); - } - - /** - * Handles customer balance increment once sale invoice created. - */ - @On(events.saleInvoice.onCreated) - public async handleCustomerBalanceIncrement({ - tenantId, - saleInvoice, - saleInvoiceId, - }) { - const { customerRepository } = this.tenancy.repositories(tenantId); - - this.logger.info('[sale_invoice] trying to increment customer balance.', { - tenantId, - }); - await customerRepository.changeBalance( - saleInvoice.customerId, - saleInvoice.balance - ); - } - - /** - * Handles customer balance diff balnace change once sale invoice edited. - */ - @On(events.saleInvoice.onEdited) - public async onSaleInvoiceEdited({ - tenantId, - saleInvoice, - oldSaleInvoice, - saleInvoiceId, - }) { - const { customerRepository } = this.tenancy.repositories(tenantId); - - this.logger.info('[sale_invoice] trying to change diff customer balance.', { - tenantId, - }); - await customerRepository.changeDiffBalance( - saleInvoice.customerId, - saleInvoice.balance, - oldSaleInvoice.balance, - oldSaleInvoice.customerId - ); - } - - /** - * Handles customer balance decrement once sale invoice deleted. - */ - @On(events.saleInvoice.onDeleted) - public async handleCustomerBalanceDecrement({ - tenantId, - saleInvoiceId, - oldSaleInvoice, - }) { - const { customerRepository } = this.tenancy.repositories(tenantId); - - this.logger.info('[sale_invoice] trying to decrement customer balance.', { - tenantId, - }); - await customerRepository.changeBalance( - oldSaleInvoice.customerId, - oldSaleInvoice.balance * -1 - ); - } -} diff --git a/server/src/subscribers/paymentMades.ts b/server/src/subscribers/paymentMades.ts index 32a5fee09..171cf3b95 100644 --- a/server/src/subscribers/paymentMades.ts +++ b/server/src/subscribers/paymentMades.ts @@ -21,12 +21,20 @@ export default class PaymentMadesSubscriber { */ @On(events.billPayment.onCreated) @On(events.billPayment.onEdited) - async handleBillsIncrementPaymentAmount({ tenantId, billPayment, oldBillPayment, billPaymentId }) { - this.logger.info('[payment_made] trying to change bills payment amount.', { tenantId, billPaymentId }); + async handleBillsIncrementPaymentAmount({ + tenantId, + billPayment, + oldBillPayment, + billPaymentId, + }) { + this.logger.info('[payment_made] trying to change bills payment amount.', { + tenantId, + billPaymentId, + }); this.billPaymentsService.saveChangeBillsPaymentAmount( tenantId, billPayment.entries, - oldBillPayment?.entries || null, + oldBillPayment?.entries || null ); } @@ -34,51 +42,53 @@ export default class PaymentMadesSubscriber { * Handle revert bill payment amount once bill payment deleted. */ @On(events.billPayment.onDeleted) - handleBillDecrementPaymentAmount({ tenantId, billPaymentId, oldBillPayment }) { - this.logger.info('[payment_made] tring to revert bill payment amount.', { tenantId, billPaymentId }); + handleBillDecrementPaymentAmount({ + tenantId, + billPaymentId, + oldBillPayment, + }) { + this.logger.info('[payment_made] tring to revert bill payment amount.', { + tenantId, + billPaymentId, + }); this.billPaymentsService.saveChangeBillsPaymentAmount( tenantId, oldBillPayment.entries.map((entry) => ({ ...entry, paymentAmount: 0 })), - oldBillPayment.entries, + oldBillPayment.entries ); } /** - * Handle vendor balance increment once payment made created. - */ - @On(events.billPayment.onCreated) - async handleVendorIncrement({ tenantId, billPayment, billPaymentId }) { - const { vendorRepository } = this.tenancy.repositories(tenantId); - - // Increment the vendor balance after bills payments. - this.logger.info('[bill_payment] trying to increment vendor balance.', { tenantId }); - await vendorRepository.changeBalance( - billPayment.vendorId, - billPayment.amount * -1, - ); - } - - /** - * Handle bill payment writing journal entries once created. + * Handle bill payment writing journal entries once created. */ @On(events.billPayment.onCreated) async handleWriteJournalEntries({ tenantId, billPayment }) { // Records the journal transactions after bills payment // and change diff acoount balance. - this.logger.info('[bill_payment] trying to write journal entries.', { tenantId, billPaymentId: billPayment.id }); + this.logger.info('[bill_payment] trying to write journal entries.', { + tenantId, + billPaymentId: billPayment.id, + }); await this.billPaymentsService.recordJournalEntries(tenantId, billPayment); } /** - * Decrements the vendor balance once bill payment deleted. + * Handle bill payment re-writing journal entries once the payment transaction be edited. */ - @On(events.billPayment.onDeleted) - async handleVendorDecrement({ tenantId, paymentMadeId, oldPaymentMade }) { - const { vendorRepository } = this.tenancy.repositories(tenantId); - - await vendorRepository.changeBalance( - oldPaymentMade.vendorId, - oldPaymentMade.amount, + @On(events.billPayment.onEdited) + async handleRewriteJournalEntriesOncePaymentEdited({ + tenantId, + billPayment, + }) { + // Records the journal transactions after bills payment be edited. + this.logger.info('[bill_payment] trying to rewrite journal entries.', { + tenantId, + billPaymentId: billPayment.id, + }); + await this.billPaymentsService.recordJournalEntries( + tenantId, + billPayment, + true ); } @@ -88,24 +98,8 @@ export default class PaymentMadesSubscriber { @On(events.billPayment.onDeleted) async handleRevertJournalEntries({ tenantId, billPaymentId }) { await this.billPaymentsService.revertJournalEntries( - tenantId, billPaymentId, + tenantId, + billPaymentId ); } - - /** - * Change the vendor balance different between old and new once - * bill payment edited. - */ - @On(events.billPayment.onEdited) - async handleVendorChangeDiffBalance({ tenantId, paymentMadeId, billPayment, oldBillPayment }) { - const { vendorRepository } = this.tenancy.repositories(tenantId); - - // Change the different vendor balance between the new and old one. - await vendorRepository.changeDiffBalance( - billPayment.vendorId, - oldBillPayment.vendorId, - billPayment.amount * -1, - oldBillPayment.amount * -1, - ); - } -} \ No newline at end of file +} diff --git a/server/src/subscribers/paymentReceives.ts b/server/src/subscribers/paymentReceives.ts index 9d34df629..e975dbd39 100644 --- a/server/src/subscribers/paymentReceives.ts +++ b/server/src/subscribers/paymentReceives.ts @@ -39,61 +39,6 @@ export default class PaymentReceivesSubscriber { ); } - /** - * Handle customer balance decrement once payment receive created. - */ - @On(events.paymentReceive.onCreated) - async handleCustomerBalanceDecrement({ - tenantId, - paymentReceiveId, - paymentReceive, - }) { - const { customerRepository } = this.tenancy.repositories(tenantId); - - this.logger.info('[payment_receive] trying to decrement customer balance.'); - await customerRepository.changeBalance( - paymentReceive.customerId, - paymentReceive.amount * -1 - ); - } - - /** - * Handle customer balance increment once payment receive deleted. - */ - @On(events.paymentReceive.onDeleted) - async handleCustomerBalanceIncrement({ - tenantId, - paymentReceiveId, - oldPaymentReceive, - }) { - const { customerRepository } = this.tenancy.repositories(tenantId); - - this.logger.info('[payment_receive] trying to increment customer balance.'); - await customerRepository.changeBalance( - oldPaymentReceive.customerId, - oldPaymentReceive.amount - ); - } - - /** - * Handles customer balance diff balance change once payment receive edited. - */ - @On(events.paymentReceive.onEdited) - async handleCustomerBalanceDiffChange({ - tenantId, - paymentReceiveId, - paymentReceive, - oldPaymentReceive, - }) { - const { customerRepository } = this.tenancy.repositories(tenantId); - - await customerRepository.changeDiffBalance( - paymentReceive.customerId, - paymentReceive.amount * -1, - oldPaymentReceive.amount * -1, - ); - } - /** * Handle journal entries writing once the payment receive edited. */