feat: writing journal entries of payment receive.

This commit is contained in:
a.bouhuolia
2021-01-02 18:29:34 +02:00
parent 53ccd56f2b
commit f18ab184e2
4 changed files with 142 additions and 48 deletions

View File

@@ -138,13 +138,14 @@ export default class PaymentReceivesController extends BaseController {
* Records payment receive to the given customer with associated invoices. * Records payment receive to the given customer with associated invoices.
*/ */
async newPaymentReceive(req: Request, res: Response, next: NextFunction) { async newPaymentReceive(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId, user } = req;
const paymentReceive: IPaymentReceiveDTO = this.matchedBodyData(req); const paymentReceive: IPaymentReceiveDTO = this.matchedBodyData(req);
try { try {
const storedPaymentReceive = await this.paymentReceiveService.createPaymentReceive( const storedPaymentReceive = await this.paymentReceiveService.createPaymentReceive(
tenantId, tenantId,
paymentReceive paymentReceive,
user
); );
return res.status(200).send({ return res.status(200).send({
id: storedPaymentReceive.id, id: storedPaymentReceive.id,
@@ -162,7 +163,7 @@ export default class PaymentReceivesController extends BaseController {
* @return {Response} * @return {Response}
*/ */
async editPaymentReceive(req: Request, res: Response, next: NextFunction) { async editPaymentReceive(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId, user } = req;
const { id: paymentReceiveId } = req.params; const { id: paymentReceiveId } = req.params;
const paymentReceive: IPaymentReceiveDTO = this.matchedBodyData(req); const paymentReceive: IPaymentReceiveDTO = this.matchedBodyData(req);
@@ -171,7 +172,8 @@ export default class PaymentReceivesController extends BaseController {
await this.paymentReceiveService.editPaymentReceive( await this.paymentReceiveService.editPaymentReceive(
tenantId, tenantId,
paymentReceiveId, paymentReceiveId,
paymentReceive paymentReceive,
user
); );
return res.status(200).send({ return res.status(200).send({
id: paymentReceiveId, id: paymentReceiveId,
@@ -188,13 +190,14 @@ export default class PaymentReceivesController extends BaseController {
* @param {Response} res * @param {Response} res
*/ */
async deletePaymentReceive(req: Request, res: Response, next: NextFunction) { async deletePaymentReceive(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId, user } = req;
const { id: paymentReceiveId } = req.params; const { id: paymentReceiveId } = req.params;
try { try {
await this.paymentReceiveService.deletePaymentReceive( await this.paymentReceiveService.deletePaymentReceive(
tenantId, tenantId,
paymentReceiveId paymentReceiveId,
user
); );
return res.status(200).send({ return res.status(200).send({
@@ -223,7 +226,8 @@ export default class PaymentReceivesController extends BaseController {
paymentReceiveInvoices, paymentReceiveInvoices,
} = await this.paymentReceiveService.getPaymentReceive( } = await this.paymentReceiveService.getPaymentReceive(
tenantId, tenantId,
paymentReceiveId paymentReceiveId,
user
); );
return res.status(200).send({ return res.status(200).send({

View File

@@ -11,13 +11,13 @@ import {
IFilterMeta, IFilterMeta,
IPaginationMeta, IPaginationMeta,
IPaymentReceive, IPaymentReceive,
IPaymentReceiveDTO,
IPaymentReceiveCreateDTO, IPaymentReceiveCreateDTO,
IPaymentReceiveEditDTO, IPaymentReceiveEditDTO,
IPaymentReceiveEntry, IPaymentReceiveEntry,
IPaymentReceiveEntryDTO, IPaymentReceiveEntryDTO,
IPaymentReceivesFilter, IPaymentReceivesFilter,
ISaleInvoice, ISaleInvoice,
ISystemUser,
} from 'interfaces'; } from 'interfaces';
import AccountsService from 'services/Accounts/AccountsService'; import AccountsService from 'services/Accounts/AccountsService';
import JournalPoster from 'services/Accounting/JournalPoster'; import JournalPoster from 'services/Accounting/JournalPoster';
@@ -29,6 +29,7 @@ import { formatDateFields, entriesAmountDiff } from 'utils';
import { ServiceError } from 'exceptions'; import { ServiceError } from 'exceptions';
import CustomersService from 'services/Contacts/CustomersService'; import CustomersService from 'services/Contacts/CustomersService';
import ItemsEntriesService from 'services/Items/ItemsEntriesService'; import ItemsEntriesService from 'services/Items/ItemsEntriesService';
import JournalCommands from 'services/Accounting/JournalCommands';
const ERRORS = { const ERRORS = {
PAYMENT_RECEIVE_NO_EXISTS: 'PAYMENT_RECEIVE_NO_EXISTS', PAYMENT_RECEIVE_NO_EXISTS: 'PAYMENT_RECEIVE_NO_EXISTS',
@@ -270,7 +271,8 @@ export default class PaymentReceiveService {
*/ */
public async createPaymentReceive( public async createPaymentReceive(
tenantId: number, tenantId: number,
paymentReceiveDTO: IPaymentReceiveCreateDTO paymentReceiveDTO: IPaymentReceiveCreateDTO,
authorizedUser: ISystemUser
) { ) {
const { PaymentReceive } = this.tenancy.models(tenantId); const { PaymentReceive } = this.tenancy.models(tenantId);
const paymentAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount'); const paymentAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount');
@@ -323,6 +325,7 @@ export default class PaymentReceiveService {
tenantId, tenantId,
paymentReceive, paymentReceive,
paymentReceiveId: paymentReceive.id, paymentReceiveId: paymentReceive.id,
authorizedUser,
}); });
this.logger.info('[payment_receive] updated successfully.', { this.logger.info('[payment_receive] updated successfully.', {
tenantId, tenantId,
@@ -350,7 +353,8 @@ export default class PaymentReceiveService {
public async editPaymentReceive( public async editPaymentReceive(
tenantId: number, tenantId: number,
paymentReceiveId: number, paymentReceiveId: number,
paymentReceiveDTO: IPaymentReceiveEditDTO paymentReceiveDTO: IPaymentReceiveEditDTO,
authorizedUser: ISystemUser
) { ) {
const { PaymentReceive } = this.tenancy.models(tenantId); const { PaymentReceive } = this.tenancy.models(tenantId);
const paymentAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount'); const paymentAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount');
@@ -417,6 +421,7 @@ export default class PaymentReceiveService {
paymentReceiveId, paymentReceiveId,
paymentReceive, paymentReceive,
oldPaymentReceive, oldPaymentReceive,
authorizedUser,
}); });
this.logger.info('[payment_receive] upserted successfully.', { this.logger.info('[payment_receive] upserted successfully.', {
tenantId, tenantId,
@@ -438,7 +443,11 @@ export default class PaymentReceiveService {
* @param {Integer} paymentReceiveId - Payment receive id. * @param {Integer} paymentReceiveId - Payment receive id.
* @param {IPaymentReceive} paymentReceive - Payment receive object. * @param {IPaymentReceive} paymentReceive - Payment receive object.
*/ */
async deletePaymentReceive(tenantId: number, paymentReceiveId: number) { async deletePaymentReceive(
tenantId: number,
paymentReceiveId: number,
authorizedUser: ISystemUser
) {
const { PaymentReceive, PaymentReceiveEntry } = this.tenancy.models( const { PaymentReceive, PaymentReceiveEntry } = this.tenancy.models(
tenantId tenantId
); );
@@ -460,6 +469,7 @@ export default class PaymentReceiveService {
tenantId, tenantId,
paymentReceiveId, paymentReceiveId,
oldPaymentReceive, oldPaymentReceive,
authorizedUser,
}); });
this.logger.info('[payment_receive] deleted successfully.', { this.logger.info('[payment_receive] deleted successfully.', {
tenantId, tenantId,
@@ -474,7 +484,7 @@ export default class PaymentReceiveService {
*/ */
public async getPaymentReceive( public async getPaymentReceive(
tenantId: number, tenantId: number,
paymentReceiveId: number paymentReceiveId: number,
): Promise<{ ): Promise<{
paymentReceive: IPaymentReceive; paymentReceive: IPaymentReceive;
receivableInvoices: ISaleInvoice[]; receivableInvoices: ISaleInvoice[];
@@ -595,55 +605,57 @@ export default class PaymentReceiveService {
* -------- * --------
* - Account receivable -> Debit * - Account receivable -> Debit
* - Payment account [current asset] -> Credit * - Payment account [current asset] -> Credit
* @async
* @param {number} tenantId - Tenant id.
* @param {IPaymentReceive} paymentReceive
* @param {Number} paymentReceiveId
*/ */
private async recordPaymentReceiveJournalEntries( public async recordPaymentReceiveJournalEntries(
tenantId: number, tenantId: number,
paymentReceive: any, paymentReceive: IPaymentReceive,
paymentReceiveId?: number authorizedUserId: number,
) { override: boolean = false
const { Account, AccountTransaction } = this.tenancy.models(tenantId); ): Promise<void> {
const {
accountRepository,
transactionsRepository,
} = this.tenancy.repositories(tenantId);
const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount'); const paymentAmount = sumBy(paymentReceive.entries, 'paymentAmount');
const formattedDate = moment(paymentReceive.payment_date).format(
'YYYY-MM-DD' // Retrieve the receivable account.
); const receivableAccount = await accountRepository.findOne({
const receivableAccount = await this.accountsService.getAccountByType( slug: 'accounts-receivable',
tenantId, });
'accounts_receivable' // Accounts dependency graph.
); const accountsDepGraph = await accountRepository.getDependencyGraph();
const accountsDepGraph = await Account.depGraph().query();
const journal = new JournalPoster(accountsDepGraph); const journal = new JournalPoster(tenantId, accountsDepGraph);
const commonJournal = { const commonJournal = {
debit: 0, debit: 0,
credit: 0, credit: 0,
referenceId: paymentReceive.id, referenceId: paymentReceive.id,
referenceType: 'PaymentReceive', referenceType: 'PaymentReceive',
date: formattedDate, date: paymentReceive.paymentDate,
userId: authorizedUserId,
}; };
if (paymentReceiveId) { if (override) {
const transactions = await AccountTransaction.query() const transactions = await transactionsRepository.journal({
.whereIn('reference_type', ['PaymentReceive']) referenceType: ['PaymentReceive'],
.where('reference_id', paymentReceiveId) referenceId: [paymentReceive.id],
.withGraphFetched('account.type'); });
journal.fromTransactions(transactions);
journal.loadEntries(transactions);
journal.removeEntries(); journal.removeEntries();
} }
const creditReceivable = new JournalEntry({ const creditReceivable = new JournalEntry({
...commonJournal, ...commonJournal,
credit: paymentAmount, credit: paymentAmount,
contactType: 'Customer', contactType: 'Customer',
contactId: paymentReceive.customer_id, contactId: paymentReceive.customerId,
account: receivableAccount.id, account: receivableAccount.id,
index: 1,
}); });
const debitDepositAccount = new JournalEntry({ const debitDepositAccount = new JournalEntry({
...commonJournal, ...commonJournal,
debit: paymentAmount, debit: paymentAmount,
account: paymentReceive.deposit_account_id, account: paymentReceive.depositAccountId,
index: 2,
}); });
journal.credit(creditReceivable); journal.credit(creditReceivable);
journal.debit(debitDepositAccount); journal.debit(debitDepositAccount);
@@ -655,6 +667,28 @@ export default class PaymentReceiveService {
]); ]);
} }
/**
* Reverts the given payment receive journal entries.
* @param {number} tenantId
* @param {number} paymentReceiveId
*/
async revertPaymentReceiveJournalEntries(
tenantId: number,
paymentReceiveId: number,
) {
const { accountRepository } = this.tenancy.repositories(tenantId);
// Accounts dependency graph.
const accountsDepGraph = await accountRepository.getDependencyGraph();
const journal = new JournalPoster(tenantId, accountsDepGraph);
const commands = new JournalCommands(journal);
await commands.revertJournalEntries(paymentReceiveId, 'PaymentReceive');
await Promise.all([journal.saveBalance(), journal.deleteEntries()]);
}
/** /**
* Saves difference changing between old and new invoice payment amount. * Saves difference changing between old and new invoice payment amount.
* @async * @async

View File

@@ -13,7 +13,6 @@ import {
IPaginationMeta, IPaginationMeta,
IFilterMeta, IFilterMeta,
ISystemUser, ISystemUser,
ISystemService,
} from 'interfaces'; } from 'interfaces';
import events from 'subscribers/events'; import events from 'subscribers/events';
import InventoryService from 'services/Inventory/Inventory'; import InventoryService from 'services/Inventory/Inventory';
@@ -239,19 +238,19 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
const saleInvoiceObj = this.transformDTOToModel( const saleInvoiceObj = this.transformDTOToModel(
tenantId, tenantId,
saleInvoiceDTO, saleInvoiceDTO,
oldSaleInvoice oldSaleInvoice,
); );
// Validate customer existance. // Validate customer existance.
await this.customersService.getCustomerByIdOrThrowError( await this.customersService.getCustomerByIdOrThrowError(
tenantId, tenantId,
saleInvoiceDTO.customerId saleInvoiceDTO.customerId,
); );
// Validate sale invoice number uniquiness. // Validate sale invoice number uniquiness.
if (saleInvoiceDTO.invoiceNo) { if (saleInvoiceDTO.invoiceNo) {
await this.validateInvoiceNumberUnique( await this.validateInvoiceNumberUnique(
tenantId, tenantId,
saleInvoiceDTO.invoiceNo, saleInvoiceDTO.invoiceNo,
saleInvoiceId saleInvoiceId,
); );
} }
// Validate items ids existance. // Validate items ids existance.
@@ -262,7 +261,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
// Validate non-sellable entries items. // Validate non-sellable entries items.
await this.itemsEntriesService.validateNonSellableEntriesItems( await this.itemsEntriesService.validateNonSellableEntriesItems(
tenantId, tenantId,
saleInvoiceDTO.entries saleInvoiceDTO.entries,
); );
// Validate the items entries existance. // Validate the items entries existance.
await this.itemsEntriesService.validateEntriesIdsExistance( await this.itemsEntriesService.validateEntriesIdsExistance(
@@ -502,7 +501,6 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
const { inventoryTransactionRepository } = this.tenancy.repositories( const { inventoryTransactionRepository } = this.tenancy.repositories(
tenantId tenantId
); );
// Retrieve the inventory transactions of the given sale invoice. // Retrieve the inventory transactions of the given sale invoice.
const oldInventoryTransactions = await inventoryTransactionRepository.find({ const oldInventoryTransactions = await inventoryTransactionRepository.find({
transactionId: saleInvoiceId, transactionId: saleInvoiceId,

View File

@@ -10,7 +10,6 @@ export default class PaymentReceivesSubscriber {
tenancy: TenancyService; tenancy: TenancyService;
logger: any; logger: any;
paymentReceivesService: PaymentReceiveService; paymentReceivesService: PaymentReceiveService;
settingsService: SettingsService; settingsService: SettingsService;
constructor() { constructor() {
@@ -20,6 +19,26 @@ export default class PaymentReceivesSubscriber {
this.settingsService = Container.get(SettingsService); this.settingsService = Container.get(SettingsService);
} }
/**
* Handle journal entries writing once the payment receive created.
*/
@On(events.paymentReceive.onCreated)
async handleWriteJournalEntriesOnceCreated({
tenantId,
paymentReceiveId,
paymentReceive,
authorizedUser,
}) {
this.logger.info('[payment_receive] trying to write journal entries.', {
tenantId, paymentReceiveId,
});
await this.paymentReceivesService.recordPaymentReceiveJournalEntries(
tenantId,
paymentReceive,
authorizedUser.id,
);
}
/** /**
* Handle customer balance decrement once payment receive created. * Handle customer balance decrement once payment receive created.
*/ */
@@ -75,6 +94,27 @@ export default class PaymentReceivesSubscriber {
); );
} }
/**
* Handle journal entries writing once the payment receive edited.
*/
@On(events.paymentReceive.onEdited)
async handleOverwriteJournalEntriesOnceEdited({
tenantId,
paymentReceiveId,
paymentReceive,
authorizedUser,
}) {
this.logger.info('[payment_receive] trying to overwrite journal entries.', {
tenantId, paymentReceiveId,
});
await this.paymentReceivesService.recordPaymentReceiveJournalEntries(
tenantId,
paymentReceive,
authorizedUser.id,
true
);
}
/** /**
* Handle sale invoice increment/decrement payment amount once created, edited or deleted. * Handle sale invoice increment/decrement payment amount once created, edited or deleted.
*/ */
@@ -119,6 +159,24 @@ export default class PaymentReceivesSubscriber {
); );
} }
/**
* Handles revert journal entries once deleted.
*/
@On(events.paymentReceive.onDeleted)
async handleRevertJournalEntriesOnceDeleted({
tenantId,
paymentReceiveId,
}) {
this.logger.info('[payment_receive] trying to revert journal entries.', {
tenantId,
paymentReceiveId,
});
await this.paymentReceivesService.revertPaymentReceiveJournalEntries(
tenantId,
paymentReceiveId
)
}
/** /**
* Handles increment next number of payment receive once be created. * Handles increment next number of payment receive once be created.
*/ */