refactor: GL entries

This commit is contained in:
Ahmed Bouhuolia
2024-12-31 14:57:24 +02:00
parent 1b15261adb
commit a819d6c1ba
54 changed files with 2669 additions and 2298 deletions

View File

@@ -33,5 +33,8 @@ import { GetAccountTransactionsService } from './GetAccountTransactions.service'
GetAccountTypesService, GetAccountTypesService,
GetAccountTransactionsService, GetAccountTransactionsService,
], ],
exports: [
AccountRepository
]
}) })
export class AccountsModule {} export class AccountsModule {}

View File

@@ -1,20 +1,37 @@
import { Model, raw } from 'objection'; import { Model, raw } from 'objection';
import moment from 'moment'; import moment, { unitOfTime } from 'moment';
import { isEmpty, castArray } from 'lodash'; import { isEmpty, castArray } from 'lodash';
import { BaseModel } from '@/models/Model'; import { BaseModel } from '@/models/Model';
import { Account } from './Account.model';
// import { getTransactionTypeLabel } from '@/utils/transactions-types'; // import { getTransactionTypeLabel } from '@/utils/transactions-types';
export class AccountTransaction extends BaseModel { export class AccountTransaction extends BaseModel {
referenceType: string; referenceType: string;
referenceId: number; referenceId: number;
accountId: number;
contactId: number;
credit: number; credit: number;
debit: number; debit: number;
exchangeRate: number; exchangeRate: number;
taxRate: number; taxRate: number;
date: string; date: Date | string;
transactionType: string; transactionType: string;
currencyCode: string; currencyCode: string;
referenceTypeFormatted: string; referenceTypeFormatted: string;
transactionNumber!: string;
referenceNumber!: string;
note!: string;
index!: number;
indexGroup!: number;
taxRateId!: number;
branchId!: number;
userId!: number;
itemId!: number;
projectId!: number;
account: Account;
/** /**
* Table name * Table name
@@ -61,153 +78,184 @@ export class AccountTransaction extends BaseModel {
// return getTransactionTypeLabel(this.referenceType, this.transactionType); // return getTransactionTypeLabel(this.referenceType, this.transactionType);
// } // }
// /** /**
// * Model modifiers. * Model modifiers.
// */ */
// static get modifiers() { static get modifiers() {
// return { return {
// /** /**
// * Filters accounts by the given ids. * Filters accounts by the given ids.
// * @param {Query} query * @param {Query} query
// * @param {number[]} accountsIds * @param {number[]} accountsIds
// */ */
// filterAccounts(query, accountsIds) { filterAccounts(query, accountsIds) {
// if (Array.isArray(accountsIds) && accountsIds.length > 0) { if (Array.isArray(accountsIds) && accountsIds.length > 0) {
// query.whereIn('account_id', accountsIds); query.whereIn('account_id', accountsIds);
// } }
// }, },
// filterTransactionTypes(query, types) {
// if (Array.isArray(types) && types.length > 0) {
// query.whereIn('reference_type', types);
// } else if (typeof types === 'string') {
// query.where('reference_type', types);
// }
// },
// filterDateRange(query, startDate, endDate, type = 'day') {
// const dateFormat = 'YYYY-MM-DD';
// const fromDate = moment(startDate).startOf(type).format(dateFormat);
// const toDate = moment(endDate).endOf(type).format(dateFormat);
// if (startDate) { /**
// query.where('date', '>=', fromDate); * Filters the transaction types.
// } * @param {Query} query
// if (endDate) { * @param {string[]} types
// query.where('date', '<=', toDate); */
// } filterTransactionTypes(query, types) {
// }, if (Array.isArray(types) && types.length > 0) {
// filterAmountRange(query, fromAmount, toAmount) { query.whereIn('reference_type', types);
// if (fromAmount) { } else if (typeof types === 'string') {
// query.andWhere((q) => { query.where('reference_type', types);
// q.where('credit', '>=', fromAmount); }
// q.orWhere('debit', '>=', fromAmount); },
// });
// }
// if (toAmount) {
// query.andWhere((q) => {
// q.where('credit', '<=', toAmount);
// q.orWhere('debit', '<=', toAmount);
// });
// }
// },
// sumationCreditDebit(query) {
// query.select(['accountId']);
// query.sum('credit as credit'); /**
// query.sum('debit as debit'); * Filters the date range.
// query.groupBy('account_id'); * @param {Query} query
// }, * @param {moment.MomentInput} startDate
// filterContactType(query, contactType) { * @param {moment.MomentInput} endDate
// query.where('contact_type', contactType); * @param {unitOfTime.StartOf} type
// }, */
// filterContactIds(query, contactIds) { filterDateRange(
// query.whereIn('contact_id', contactIds); query,
// }, startDate: moment.MomentInput,
// openingBalance(query, fromDate) { endDate: moment.MomentInput,
// query.modify('filterDateRange', null, fromDate); type: unitOfTime.StartOf = 'day',
// query.modify('sumationCreditDebit'); ) {
// }, const dateFormat = 'YYYY-MM-DD';
// closingBalance(query, toDate) { const fromDate = moment(startDate).startOf(type).format(dateFormat);
// query.modify('filterDateRange', null, toDate); const toDate = moment(endDate).endOf(type).format(dateFormat);
// query.modify('sumationCreditDebit');
// },
// contactsOpeningBalance(
// query,
// openingDate,
// receivableAccounts,
// customersIds
// ) {
// // Filter by date.
// query.modify('filterDateRange', null, openingDate);
// // Filter by customers. if (startDate) {
// query.whereNot('contactId', null); query.where('date', '>=', fromDate);
// query.whereIn('accountId', castArray(receivableAccounts)); }
if (endDate) {
query.where('date', '<=', toDate);
}
},
// if (!isEmpty(customersIds)) { /**
// query.whereIn('contactId', castArray(customersIds)); * Filters the amount range.
// } * @param {Query} query
// // Group by the contact transactions. * @param {number} fromAmount
// query.groupBy('contactId'); * @param {number} toAmount
// query.sum('credit as credit'); */
// query.sum('debit as debit'); filterAmountRange(query, fromAmount, toAmount) {
// query.select('contactId'); if (fromAmount) {
// }, query.andWhere((q) => {
// creditDebitSummation(query) { q.where('credit', '>=', fromAmount);
// query.sum('credit as credit'); q.orWhere('debit', '>=', fromAmount);
// query.sum('debit as debit'); });
// }, }
// groupByDateFormat(query, groupType = 'month') { if (toAmount) {
// const groupBy = { query.andWhere((q) => {
// day: '%Y-%m-%d', q.where('credit', '<=', toAmount);
// month: '%Y-%m', q.orWhere('debit', '<=', toAmount);
// year: '%Y', });
// }; }
// const dateFormat = groupBy[groupType]; },
sumationCreditDebit(query) {
query.select(['accountId']);
// query.select(raw(`DATE_FORMAT(DATE, '${dateFormat}')`).as('date')); query.sum('credit as credit');
// query.groupByRaw(`DATE_FORMAT(DATE, '${dateFormat}')`); query.sum('debit as debit');
// }, query.groupBy('account_id');
},
filterContactType(query, contactType) {
query.where('contact_type', contactType);
},
filterContactIds(query, contactIds) {
query.whereIn('contact_id', contactIds);
},
openingBalance(query, fromDate) {
query.modify('filterDateRange', null, fromDate);
query.modify('sumationCreditDebit');
},
closingBalance(query, toDate) {
query.modify('filterDateRange', null, toDate);
query.modify('sumationCreditDebit');
},
contactsOpeningBalance(
query,
openingDate,
receivableAccounts,
customersIds,
) {
// Filter by date.
query.modify('filterDateRange', null, openingDate);
// filterByBranches(query, branchesIds) { // Filter by customers.
// const formattedBranchesIds = castArray(branchesIds); query.whereNot('contactId', null);
query.whereIn('accountId', castArray(receivableAccounts));
// query.whereIn('branchId', formattedBranchesIds); if (!isEmpty(customersIds)) {
// }, query.whereIn('contactId', castArray(customersIds));
}
// Group by the contact transactions.
query.groupBy('contactId');
query.sum('credit as credit');
query.sum('debit as debit');
query.select('contactId');
},
creditDebitSummation(query) {
query.sum('credit as credit');
query.sum('debit as debit');
},
groupByDateFormat(query, groupType = 'month') {
const groupBy = {
day: '%Y-%m-%d',
month: '%Y-%m',
year: '%Y',
};
const dateFormat = groupBy[groupType];
// filterByProjects(query, projectsIds) { query.select(raw(`DATE_FORMAT(DATE, '${dateFormat}')`).as('date'));
// const formattedProjectsIds = castArray(projectsIds); query.groupByRaw(`DATE_FORMAT(DATE, '${dateFormat}')`);
},
// query.whereIn('projectId', formattedProjectsIds); filterByBranches(query, branchesIds) {
// }, const formattedBranchesIds = castArray(branchesIds);
// };
// }
// /** query.whereIn('branchId', formattedBranchesIds);
// * Relationship mapping. },
// */
// static get relationMappings() {
// const Account = require('models/Account');
// const Contact = require('models/Contact');
// return { filterByProjects(query, projectsIds) {
// account: { const formattedProjectsIds = castArray(projectsIds);
// relation: Model.BelongsToOneRelation,
// modelClass: Account.default, query.whereIn('projectId', formattedProjectsIds);
// join: { },
// from: 'accounts_transactions.accountId',
// to: 'accounts.id', filterByReference(query, referenceId: number, referenceType: string) {
// }, query.where('reference_id', referenceId);
// }, query.where('reference_type', referenceType);
// contact: { },
// relation: Model.BelongsToOneRelation, };
// modelClass: Contact.default, }
// join: {
// from: 'accounts_transactions.contactId', /**
// to: 'contacts.id', * Relationship mapping.
// }, */
// }, static get relationMappings() {
// }; const { Account } = require('./Account.model');
// } const { Contact } = require('../../Contacts/models/Contact');
return {
account: {
relation: Model.BelongsToOneRelation,
modelClass: Account,
join: {
from: 'accounts_transactions.accountId',
to: 'accounts.id',
},
},
contact: {
relation: Model.BelongsToOneRelation,
modelClass: Contact,
join: {
from: 'accounts_transactions.contactId',
to: 'contacts.id',
},
},
};
}
/** /**
* Prevents mutate base currency since the model is not empty. * Prevents mutate base currency since the model is not empty.

View File

@@ -3,19 +3,26 @@ import { Inject, Injectable, Scope } from '@nestjs/common';
import { TenantRepository } from '@/common/repository/TenantRepository'; import { TenantRepository } from '@/common/repository/TenantRepository';
import { TENANCY_DB_CONNECTION } from '@/modules/Tenancy/TenancyDB/TenancyDB.constants'; import { TENANCY_DB_CONNECTION } from '@/modules/Tenancy/TenancyDB/TenancyDB.constants';
import { Account } from '../models/Account.model'; import { Account } from '../models/Account.model';
// import { TenantMetadata } from '@/modules/System/models/TenantMetadataModel'; import { I18nService } from 'nestjs-i18n';
// import { IAccount } from '../Accounts.types'; import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
// import { import {
// PrepardExpenses, PrepardExpenses,
// StripeClearingAccount, StripeClearingAccount,
// TaxPayableAccount, TaxPayableAccount,
// UnearnedRevenueAccount, UnearnedRevenueAccount,
// } from '../Accounts.constants'; } from '../Accounts.constants';
@Injectable({ scope: Scope.REQUEST }) @Injectable({ scope: Scope.REQUEST })
export class AccountRepository extends TenantRepository { export class AccountRepository extends TenantRepository {
@Inject(TENANCY_DB_CONNECTION) constructor(
private readonly tenantDBKnex: Knex; private readonly i18n: I18nService,
private readonly tenancyContext: TenancyContext,
@Inject(TENANCY_DB_CONNECTION)
private readonly tenantDBKnex: Knex,
) {
super();
}
/** /**
* Gets the repository's model. * Gets the repository's model.
@@ -107,185 +114,274 @@ export class AccountRepository extends TenantRepository {
return results; return results;
} }
// /** /**
// * *
// * @param {string} currencyCode * @param {string} currencyCode
// * @param extraAttrs * @param extraAttrs
// * @param trx * @param trx
// * @returns * @returns
// */ */
// findOrCreateAccountReceivable = async ( findOrCreateAccountReceivable = async (
// currencyCode: string = '', currencyCode: string = '',
// extraAttrs = {}, extraAttrs = {},
// trx?: Knex.Transaction, trx?: Knex.Transaction,
// ) => { ) => {
// let result = await this.model let result = await this.model
// .query(trx) .query(trx)
// .onBuild((query) => { .onBuild((query) => {
// if (currencyCode) { if (currencyCode) {
// query.where('currencyCode', currencyCode); query.where('currencyCode', currencyCode);
// } }
// query.where('accountType', 'accounts-receivable'); query.where('accountType', 'accounts-receivable');
// }) })
// .first(); .first();
// if (!result) { if (!result) {
// result = await this.model.query(trx).insertAndFetch({ result = await this.model.query(trx).insertAndFetch({
// name: this.i18n.__('account.accounts_receivable.currency', { name: this.i18n.t('account.accounts_receivable.currency', {
// currency: currencyCode, args: { currency: currencyCode },
// }), }),
// accountType: 'accounts-receivable', accountType: 'accounts-receivable',
// currencyCode, currencyCode,
// active: 1, active: 1,
// ...extraAttrs, ...extraAttrs,
// }); });
// } }
// return result; return result;
// }; };
// /** /**
// * Find or create tax payable account. * Find or create tax payable account.
// * @param {Record<string, string>}extraAttrs * @param {Record<string, string>}extraAttrs
// * @param {Knex.Transaction} trx * @param {Knex.Transaction} trx
// * @returns * @returns
// */ */
// async findOrCreateTaxPayable( async findOrCreateTaxPayable(
// extraAttrs: Record<string, string> = {}, extraAttrs: Record<string, string> = {},
// trx?: Knex.Transaction, trx?: Knex.Transaction,
// ) { ) {
// let result = await this.model let result = await this.model
// .query(trx) .query(trx)
// .findOne({ slug: TaxPayableAccount.slug, ...extraAttrs }); .findOne({ slug: TaxPayableAccount.slug, ...extraAttrs });
// if (!result) { if (!result) {
// result = await this.model.query(trx).insertAndFetch({ result = await this.model.query(trx).insertAndFetch({
// ...TaxPayableAccount, ...TaxPayableAccount,
// ...extraAttrs, ...extraAttrs,
// }); });
// } }
// return result; return result;
// } }
// findOrCreateAccountsPayable = async ( findOrCreateAccountsPayable = async (
// currencyCode: string = '', currencyCode: string = '',
// extraAttrs = {}, extraAttrs = {},
// trx?: Knex.Transaction, trx?: Knex.Transaction,
// ) => { ) => {
// let result = await this.model let result = await this.model
// .query(trx) .query(trx)
// .onBuild((query) => { .onBuild((query) => {
// if (currencyCode) { if (currencyCode) {
// query.where('currencyCode', currencyCode); query.where('currencyCode', currencyCode);
// } }
// query.where('accountType', 'accounts-payable'); query.where('accountType', 'accounts-payable');
// }) })
// .first(); .first();
// if (!result) { if (!result) {
// result = await this.model.query(trx).insertAndFetch({ result = await this.model.query(trx).insertAndFetch({
// name: this.i18n.__('account.accounts_payable.currency', { name: this.i18n.t('account.accounts_payable.currency', {
// currency: currencyCode, args: { currency: currencyCode },
// }), }),
// accountType: 'accounts-payable', accountType: 'accounts-payable',
// currencyCode, currencyCode,
// active: 1, active: 1,
// ...extraAttrs, ...extraAttrs,
// }); });
// } }
// return result; return result;
// }; };
// /** /**
// * Finds or creates the unearned revenue. * Finds or creates the unearned revenue.
// * @param {Record<string, string>} extraAttrs * @param {Record<string, string>} extraAttrs
// * @param {Knex.Transaction} trx * @param {Knex.Transaction} trx
// * @returns * @returns
// */ */
// public async findOrCreateUnearnedRevenue( public async findOrCreateUnearnedRevenue(
// extraAttrs: Record<string, string> = {}, extraAttrs: Record<string, string> = {},
// trx?: Knex.Transaction, trx?: Knex.Transaction,
// ) { ) {
// // Retrieves the given tenant metadata. const tenantMeta = await this.tenancyContext.getTenantMetadata();
// const tenantMeta = await TenantMetadata.query().findOne({ const _extraAttrs = {
// tenantId: this.tenantId, currencyCode: tenantMeta.baseCurrency,
// }); ...extraAttrs,
// const _extraAttrs = { };
// currencyCode: tenantMeta.baseCurrency, let result = await this.model
// ...extraAttrs, .query(trx)
// }; .findOne({ slug: UnearnedRevenueAccount.slug, ..._extraAttrs });
// let result = await this.model
// .query(trx)
// .findOne({ slug: UnearnedRevenueAccount.slug, ..._extraAttrs });
// if (!result) { if (!result) {
// result = await this.model.query(trx).insertAndFetch({ result = await this.model.query(trx).insertAndFetch({
// ...UnearnedRevenueAccount, ...UnearnedRevenueAccount,
// ..._extraAttrs, ..._extraAttrs,
// }); });
// } }
// return result; return result;
// } }
// /** /**
// * Finds or creates the prepard expenses account. * Finds or creates the prepard expenses account.
// * @param {Record<string, string>} extraAttrs * @param {Record<string, string>} extraAttrs
// * @param {Knex.Transaction} trx * @param {Knex.Transaction} trx
// * @returns * @returns
// */ */
// public async findOrCreatePrepardExpenses( public async findOrCreatePrepardExpenses(
// extraAttrs: Record<string, string> = {}, extraAttrs: Record<string, string> = {},
// trx?: Knex.Transaction, trx?: Knex.Transaction,
// ) { ) {
// // Retrieves the given tenant metadata. const tenantMeta = await this.tenancyContext.getTenantMetadata();
// const tenantMeta = await TenantMetadata.query().findOne({ const _extraAttrs = {
// tenantId: this.tenantId, currencyCode: tenantMeta.baseCurrency,
// }); ...extraAttrs,
// const _extraAttrs = { };
// currencyCode: tenantMeta.baseCurrency,
// ...extraAttrs,
// };
// let result = await this.model let result = await this.model
// .query(trx) .query(trx)
// .findOne({ slug: PrepardExpenses.slug, ..._extraAttrs }); .findOne({ slug: PrepardExpenses.slug, ..._extraAttrs });
// if (!result) { if (!result) {
// result = await this.model.query(trx).insertAndFetch({ result = await this.model.query(trx).insertAndFetch({
// ...PrepardExpenses, ...PrepardExpenses,
// ..._extraAttrs, ..._extraAttrs,
// }); });
// } }
// return result; return result;
// } }
// /** /**
// * Finds or creates the stripe clearing account. * Finds or creates the stripe clearing account.
// * @param {Record<string, string>} extraAttrs * @param {Record<string, string>} extraAttrs
// * @param {Knex.Transaction} trx * @param {Knex.Transaction} trx
// * @returns * @returns
// */ */
// public async findOrCreateStripeClearing( public async findOrCreateStripeClearing(
// extraAttrs: Record<string, string> = {}, extraAttrs: Record<string, string> = {},
// trx?: Knex.Transaction, trx?: Knex.Transaction,
// ) { ) {
// // Retrieves the given tenant metadata. const tenantMeta = await this.tenancyContext.getTenantMetadata();
// const tenantMeta = await TenantMetadata.query().findOne({ const _extraAttrs = {
// tenantId: this.tenantId, currencyCode: tenantMeta.baseCurrency,
// }); ...extraAttrs,
// const _extraAttrs = { };
// currencyCode: tenantMeta.baseCurrency, let result = await this.model
// ...extraAttrs, .query(trx)
// }; .findOne({ slug: StripeClearingAccount.slug, ..._extraAttrs });
// let result = await this.model
// .query(trx)
// .findOne({ slug: StripeClearingAccount.slug, ..._extraAttrs });
// if (!result) { if (!result) {
// result = await this.model.query(trx).insertAndFetch({ result = await this.model.query(trx).insertAndFetch({
// ...StripeClearingAccount, ...StripeClearingAccount,
// ..._extraAttrs, ..._extraAttrs,
// }); });
// } }
// return result; return result;
// } }
/**
* Finds or creates the discount expense account.
* @param {Record<string, string>} extraAttrs
* @param {Knex.Transaction} trx
* @returns
*/
public async findOrCreateDiscountAccount(
extraAttrs: Record<string, string> = {},
trx?: Knex.Transaction,
) {
const tenantMeta = await this.tenancyContext.getTenantMetadata();
const _extraAttrs = {
currencyCode: tenantMeta.baseCurrency,
...extraAttrs,
};
let result = await this.model
.query(trx)
.findOne({ slug: DiscountExpenseAccount.slug, ..._extraAttrs });
if (!result) {
result = await this.model.query(trx).insertAndFetch({
...DiscountExpenseAccount,
..._extraAttrs,
});
}
return result;
}
public async findOrCreatePurchaseDiscountAccount(
extraAttrs: Record<string, string> = {},
trx?: Knex.Transaction,
) {
const tenantMeta = await this.tenancyContext.getTenantMetadata();
const _extraAttrs = {
currencyCode: tenantMeta.baseCurrency,
...extraAttrs,
};
let result = await this.model
.query(trx)
.findOne({ slug: PurchaseDiscountAccount.slug, ..._extraAttrs });
if (!result) {
result = await this.model.query(trx).insertAndFetch({
...PurchaseDiscountAccount,
..._extraAttrs,
});
}
return result;
}
public async findOrCreateOtherChargesAccount(
extraAttrs: Record<string, string> = {},
trx?: Knex.Transaction,
) {
const tenantMeta = await this.tenancyContext.getTenantMetadata();
const _extraAttrs = {
currencyCode: tenantMeta.baseCurrency,
...extraAttrs,
};
let result = await this.model
.query(trx)
.findOne({ slug: OtherChargesAccount.slug, ..._extraAttrs });
if (!result) {
result = await this.model.query(trx).insertAndFetch({
...OtherChargesAccount,
..._extraAttrs,
});
}
return result;
}
public async findOrCreateOtherExpensesAccount(
extraAttrs: Record<string, string> = {},
trx?: Knex.Transaction,
) {
const tenantMeta = await this.tenancyContext.getTenantMetadata();
const _extraAttrs = {
currencyCode: tenantMeta.baseCurrency,
...extraAttrs,
};
let result = await this.model
.query(trx)
.findOne({ slug: OtherExpensesAccount.slug, ..._extraAttrs });
if (!result) {
result = await this.model.query(trx).insertAndFetch({
...OtherExpensesAccount,
..._extraAttrs,
});
}
return result;
}
} }

View File

@@ -53,6 +53,7 @@ import { VendorCreditsRefundModule } from '../VendorCreditsRefund/VendorCreditsR
import { CreditNoteRefundsModule } from '../CreditNoteRefunds/CreditNoteRefunds.module'; import { CreditNoteRefundsModule } from '../CreditNoteRefunds/CreditNoteRefunds.module';
import { BillPaymentsModule } from '../BillPayments/BillPayments.module'; import { BillPaymentsModule } from '../BillPayments/BillPayments.module';
import { PaymentsReceivedModule } from '../PaymentReceived/PaymentsReceived.module'; import { PaymentsReceivedModule } from '../PaymentReceived/PaymentsReceived.module';
import { LedgerModule } from '../Ledger/Ledger.module';
@Module({ @Module({
imports: [ imports: [
@@ -130,6 +131,7 @@ import { PaymentsReceivedModule } from '../PaymentReceived/PaymentsReceived.modu
CreditNoteRefundsModule, CreditNoteRefundsModule,
BillPaymentsModule, BillPaymentsModule,
PaymentsReceivedModule, PaymentsReceivedModule,
LedgerModule,
], ],
controllers: [AppController], controllers: [AppController],
providers: [ providers: [

View File

@@ -17,6 +17,7 @@ import { ItemEntriesTaxTransactions } from '../TaxRates/ItemEntriesTaxTransactio
import { TenancyContext } from '../Tenancy/TenancyContext.service'; import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { BillsController } from './Bills.controller'; import { BillsController } from './Bills.controller';
import { BillLandedCostsModule } from '../BillLandedCosts/BillLandedCosts.module'; import { BillLandedCostsModule } from '../BillLandedCosts/BillLandedCosts.module';
import { BillGLEntriesSubscriber } from './subscribers/BillGLEntriesSubscriber';
@Module({ @Module({
imports: [BillLandedCostsModule], imports: [BillLandedCostsModule],
@@ -36,7 +37,8 @@ import { BillLandedCostsModule } from '../BillLandedCosts/BillLandedCosts.module
DeleteBill, DeleteBill,
BillDTOTransformer, BillDTOTransformer,
BillsValidators, BillsValidators,
ItemsEntriesService ItemsEntriesService,
BillGLEntriesSubscriber
], ],
controllers: [BillsController], controllers: [BillsController],
}) })

View File

@@ -1,288 +0,0 @@
// import moment from 'moment';
// import { sumBy } from 'lodash';
// import { Knex } from 'knex';
// import { Inject, Service } from 'typedi';
// import * as R from 'ramda';
// import { AccountNormal, IBill, IItemEntry, ILedgerEntry } from '@/interfaces';
// import HasTenancyService from '@/services/Tenancy/TenancyService';
// import Ledger from '@/services/Accounting/Ledger';
// import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
// import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
// @Service()
// export class BillGLEntries {
// @Inject()
// private tenancy: HasTenancyService;
// @Inject()
// private ledgerStorage: LedgerStorageService;
// @Inject()
// private itemsEntriesService: ItemsEntriesService;
// /**
// * Creates bill GL entries.
// * @param {number} tenantId -
// * @param {number} billId -
// * @param {Knex.Transaction} trx -
// */
// public writeBillGLEntries = async (
// tenantId: number,
// billId: number,
// trx?: Knex.Transaction
// ) => {
// const { accountRepository } = this.tenancy.repositories(tenantId);
// const { Bill } = this.tenancy.models(tenantId);
// // Retrieves bill with associated entries and landed costs.
// const bill = await Bill.query(trx)
// .findById(billId)
// .withGraphFetched('entries.item')
// .withGraphFetched('entries.allocatedCostEntries')
// .withGraphFetched('locatedLandedCosts.allocateEntries');
// // Finds or create a A/P account based on the given currency.
// const APAccount = await accountRepository.findOrCreateAccountsPayable(
// bill.currencyCode,
// {},
// trx
// );
// // Find or create tax payable account.
// const taxPayableAccount = await accountRepository.findOrCreateTaxPayable(
// {},
// trx
// );
// const billLedger = this.getBillLedger(
// bill,
// APAccount.id,
// taxPayableAccount.id
// );
// // Commit the GL enties on the storage.
// await this.ledgerStorage.commit(tenantId, billLedger, trx);
// };
// /**
// * Reverts the given bill GL entries.
// * @param {number} tenantId
// * @param {number} billId
// * @param {Knex.Transaction} trx
// */
// public revertBillGLEntries = async (
// tenantId: number,
// billId: number,
// trx?: Knex.Transaction
// ) => {
// await this.ledgerStorage.deleteByReference(tenantId, billId, 'Bill', trx);
// };
// /**
// * Rewrites the given bill GL entries.
// * @param {number} tenantId
// * @param {number} billId
// * @param {Knex.Transaction} trx
// */
// public rewriteBillGLEntries = async (
// tenantId: number,
// billId: number,
// trx?: Knex.Transaction
// ) => {
// // Reverts the bill GL entries.
// await this.revertBillGLEntries(tenantId, billId, trx);
// // Writes the bill GL entries.
// await this.writeBillGLEntries(tenantId, billId, trx);
// };
// /**
// * Retrieves the bill common entry.
// * @param {IBill} bill
// * @returns {ILedgerEntry}
// */
// private getBillCommonEntry = (bill: IBill) => {
// return {
// debit: 0,
// credit: 0,
// currencyCode: bill.currencyCode,
// exchangeRate: bill.exchangeRate || 1,
// transactionId: bill.id,
// transactionType: 'Bill',
// date: moment(bill.billDate).format('YYYY-MM-DD'),
// userId: bill.userId,
// referenceNumber: bill.referenceNo,
// transactionNumber: bill.billNumber,
// branchId: bill.branchId,
// projectId: bill.projectId,
// createdAt: bill.createdAt,
// };
// };
// /**
// * Retrieves the bill item inventory/cost entry.
// * @param {IBill} bill -
// * @param {IItemEntry} entry -
// * @param {number} index -
// */
// private getBillItemEntry = R.curry(
// (bill: IBill, entry: IItemEntry, index: number): ILedgerEntry => {
// const commonJournalMeta = this.getBillCommonEntry(bill);
// const localAmount = bill.exchangeRate * entry.amountExludingTax;
// const landedCostAmount = sumBy(entry.allocatedCostEntries, 'cost');
// return {
// ...commonJournalMeta,
// debit: localAmount + landedCostAmount,
// accountId:
// ['inventory'].indexOf(entry.item.type) !== -1
// ? entry.item.inventoryAccountId
// : entry.costAccountId,
// index: index + 1,
// indexGroup: 10,
// itemId: entry.itemId,
// itemQuantity: entry.quantity,
// accountNormal: AccountNormal.DEBIT,
// };
// }
// );
// /**
// * Retrieves the bill landed cost entry.
// * @param {IBill} bill -
// * @param {} landedCost -
// * @param {number} index -
// */
// private getBillLandedCostEntry = R.curry(
// (bill: IBill, landedCost, index: number): ILedgerEntry => {
// const commonJournalMeta = this.getBillCommonEntry(bill);
// return {
// ...commonJournalMeta,
// credit: landedCost.amount,
// accountId: landedCost.costAccountId,
// accountNormal: AccountNormal.DEBIT,
// index: 1,
// indexGroup: 20,
// };
// }
// );
// /**
// * Retrieves the bill payable entry.
// * @param {number} payableAccountId
// * @param {IBill} bill
// * @returns {ILedgerEntry}
// */
// private getBillPayableEntry = (
// payableAccountId: number,
// bill: IBill
// ): ILedgerEntry => {
// const commonJournalMeta = this.getBillCommonEntry(bill);
// return {
// ...commonJournalMeta,
// credit: bill.totalLocal,
// accountId: payableAccountId,
// contactId: bill.vendorId,
// accountNormal: AccountNormal.CREDIT,
// index: 1,
// indexGroup: 5,
// };
// };
// /**
// * Retrieves the bill tax GL entry.
// * @param {IBill} bill -
// * @param {number} taxPayableAccountId -
// * @param {IItemEntry} entry -
// * @param {number} index -
// * @returns {ILedgerEntry}
// */
// private getBillTaxEntry = R.curry(
// (
// bill: IBill,
// taxPayableAccountId: number,
// entry: IItemEntry,
// index: number
// ): ILedgerEntry => {
// const commonJournalMeta = this.getBillCommonEntry(bill);
// return {
// ...commonJournalMeta,
// debit: entry.taxAmount,
// index,
// indexGroup: 30,
// accountId: taxPayableAccountId,
// accountNormal: AccountNormal.CREDIT,
// taxRateId: entry.taxRateId,
// taxRate: entry.taxRate,
// };
// }
// );
// /**
// * Retrieves the bill tax GL entries.
// * @param {IBill} bill
// * @param {number} taxPayableAccountId
// * @returns {ILedgerEntry[]}
// */
// private getBillTaxEntries = (bill: IBill, taxPayableAccountId: number) => {
// // Retrieves the non-zero tax entries.
// const nonZeroTaxEntries = this.itemsEntriesService.getNonZeroEntries(
// bill.entries
// );
// const transformTaxEntry = this.getBillTaxEntry(bill, taxPayableAccountId);
// return nonZeroTaxEntries.map(transformTaxEntry);
// };
// /**
// * Retrieves the given bill GL entries.
// * @param {IBill} bill
// * @param {number} payableAccountId
// * @returns {ILedgerEntry[]}
// */
// private getBillGLEntries = (
// bill: IBill,
// payableAccountId: number,
// taxPayableAccountId: number
// ): ILedgerEntry[] => {
// const payableEntry = this.getBillPayableEntry(payableAccountId, bill);
// const itemEntryTransformer = this.getBillItemEntry(bill);
// const landedCostTransformer = this.getBillLandedCostEntry(bill);
// const itemsEntries = bill.entries.map(itemEntryTransformer);
// const landedCostEntries = bill.locatedLandedCosts.map(
// landedCostTransformer
// );
// const taxEntries = this.getBillTaxEntries(bill, taxPayableAccountId);
// // Allocate cost entries journal entries.
// return [payableEntry, ...itemsEntries, ...landedCostEntries, ...taxEntries];
// };
// /**
// * Retrieves the given bill ledger.
// * @param {IBill} bill
// * @param {number} payableAccountId
// * @returns {Ledger}
// */
// private getBillLedger = (
// bill: IBill,
// payableAccountId: number,
// taxPayableAccountId: number
// ) => {
// const entries = this.getBillGLEntries(
// bill,
// payableAccountId,
// taxPayableAccountId
// );
// return new Ledger(entries);
// };
// }

View File

@@ -1,75 +0,0 @@
// import { Inject, Service } from 'typedi';
// import events from '@/subscribers/events';
// import {
// IBillCreatedPayload,
// IBillEditedPayload,
// IBIllEventDeletedPayload,
// IBillOpenedPayload,
// } from '@/interfaces';
// import { BillGLEntries } from './BillGLEntries';
// @Service()
// export class BillGLEntriesSubscriber {
// @Inject()
// private billGLEntries: BillGLEntries;
// /**
// * Attaches events with handles.
// */
// public attach(bus) {
// bus.subscribe(
// events.bill.onCreated,
// this.handlerWriteJournalEntriesOnCreate
// );
// bus.subscribe(
// events.bill.onOpened,
// this.handlerWriteJournalEntriesOnCreate
// );
// bus.subscribe(
// events.bill.onEdited,
// this.handleOverwriteJournalEntriesOnEdit
// );
// bus.subscribe(events.bill.onDeleted, this.handlerDeleteJournalEntries);
// }
// /**
// * Handles writing journal entries once bill created.
// * @param {IBillCreatedPayload} payload -
// */
// private handlerWriteJournalEntriesOnCreate = async ({
// tenantId,
// bill,
// trx,
// }: IBillCreatedPayload | IBillOpenedPayload) => {
// if (!bill.openedAt) return null;
// await this.billGLEntries.writeBillGLEntries(tenantId, bill.id, trx);
// };
// /**
// * Handles the overwriting journal entries once bill edited.
// * @param {IBillEditedPayload} payload -
// */
// private handleOverwriteJournalEntriesOnEdit = async ({
// tenantId,
// billId,
// bill,
// trx,
// }: IBillEditedPayload) => {
// if (!bill.openedAt) return null;
// await this.billGLEntries.rewriteBillGLEntries(tenantId, billId, trx);
// };
// /**
// * Handles revert journal entries on bill deleted.
// * @param {IBIllEventDeletedPayload} payload -
// */
// private handlerDeleteJournalEntries = async ({
// tenantId,
// billId,
// trx,
// }: IBIllEventDeletedPayload) => {
// await this.billGLEntries.revertBillGLEntries(tenantId, billId, trx);
// };
// }

View File

@@ -0,0 +1,243 @@
import { sumBy } from 'lodash';
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
import { ItemEntry } from '@/modules/Items/models/ItemEntry';
import { Bill } from '../models/Bill';
import { AccountNormal } from '@/modules/Accounts/Accounts.types';
import { Ledger } from '@/modules/Ledger/Ledger';
import { BillLandedCost } from '@/modules/BillLandedCosts/models/BillLandedCost';
export class BillGL {
private bill: Bill;
private payableAccountId: number;
private taxPayableAccountId: number;
private purchaseDiscountAccountId: number;
private otherExpensesAccountId: number;
constructor(bill: Bill) {
this.bill = bill;
}
setPayableAccountId(payableAccountId: number) {
this.payableAccountId = payableAccountId;
return this;
}
setTaxPayableAccountId(taxPayableAccountId: number) {
this.taxPayableAccountId = taxPayableAccountId;
return this;
}
setPurchaseDiscountAccountId(purchaseDiscountAccountId: number) {
this.purchaseDiscountAccountId = purchaseDiscountAccountId;
return this;
}
setOtherExpensesAccountId(otherExpensesAccountId: number) {
this.otherExpensesAccountId = otherExpensesAccountId;
return this;
}
/**
* Retrieves the bill common entry.
* @returns {ILedgerEntry}
*/
private get billCommonEntry() {
return {
debit: 0,
credit: 0,
currencyCode: this.bill.currencyCode,
exchangeRate: this.bill.exchangeRate || 1,
transactionId: this.bill.id,
transactionType: 'Bill',
date: moment(this.bill.billDate).format('YYYY-MM-DD'),
userId: this.bill.userId,
referenceNumber: this.bill.referenceNo,
transactionNumber: this.bill.billNumber,
branchId: this.bill.branchId,
projectId: this.bill.projectId,
createdAt: this.bill.createdAt,
};
}
/**
* Retrieves the bill item inventory/cost entry.
* @param {ItemEntry} entry -
* @param {number} index -
*/
private getBillItemEntry(entry: ItemEntry, index: number): ILedgerEntry {
const commonJournalMeta = this.billCommonEntry;
const totalLocal = this.bill.exchangeRate * entry.totalExcludingTax;
const landedCostAmount = sumBy(entry.allocatedCostEntries, 'cost');
return {
...commonJournalMeta,
debit: totalLocal + landedCostAmount,
accountId:
['inventory'].indexOf(entry.item.type) !== -1
? entry.item.inventoryAccountId
: entry.costAccountId,
index: index + 1,
indexGroup: 10,
itemId: entry.itemId,
itemQuantity: entry.quantity,
accountNormal: AccountNormal.DEBIT,
};
}
/**
* Retrieves the bill landed cost entry.
* @param {BillLandedCost} landedCost - Landed cost
* @param {number} index - Index
*/
private getBillLandedCostEntry(
landedCost: BillLandedCost,
index: number,
): ILedgerEntry {
const commonJournalMeta = this.billCommonEntry;
return {
...commonJournalMeta,
credit: landedCost.amount,
accountId: landedCost.costAccountId,
accountNormal: AccountNormal.DEBIT,
index: 1,
indexGroup: 20,
};
}
/**
* Retrieves the bill payable entry.
* @returns {ILedgerEntry}
*/
private get billPayableEntry(): ILedgerEntry {
const commonJournalMeta = this.billCommonEntry;
return {
...commonJournalMeta,
credit: this.bill.totalLocal,
accountId: this.payableAccountId,
contactId: this.bill.vendorId,
accountNormal: AccountNormal.CREDIT,
index: 1,
indexGroup: 5,
};
}
/**
* Retrieves the bill tax GL entry.
* @param {IBill} bill -
* @param {number} taxPayableAccountId -
* @param {IItemEntry} entry -
* @param {number} index -
* @returns {ILedgerEntry}
*/
private getBillTaxEntry(entry: ItemEntry, index: number): ILedgerEntry {
const commonJournalMeta = this.billCommonEntry;
return {
...commonJournalMeta,
debit: entry.taxAmount,
index,
indexGroup: 30,
accountId: this.taxPayableAccountId,
accountNormal: AccountNormal.CREDIT,
taxRateId: entry.taxRateId,
taxRate: entry.taxRate,
};
}
/**
* Retrieves the bill tax GL entries.
* @param {IBill} bill
* @param {number} taxPayableAccountId
* @returns {ILedgerEntry[]}
*/
// private getBillTaxEntries = () => {
// // Retrieves the non-zero tax entries.
// const nonZeroTaxEntries = this.itemsEntriesService.getNonZeroEntries(
// this.bill.entries,
// );
// const transformTaxEntry = this.getBillTaxEntry(
// this.bill,
// this.taxPayableAccountId,
// );
// return nonZeroTaxEntries.map(transformTaxEntry);
// };
/**
* Retrieves the purchase discount GL entry.
* @returns {ILedgerEntry}
*/
private get purchaseDiscountEntry(): ILedgerEntry {
const commonEntry = this.billCommonEntry;
return {
...commonEntry,
credit: this.bill.discountAmountLocal,
accountId: this.purchaseDiscountAccountId,
accountNormal: AccountNormal.DEBIT,
index: 1,
indexGroup: 40,
};
}
/**
* Retrieves the purchase other charges GL entry.
* @returns {ILedgerEntry}
*/
private get adjustmentEntry(): ILedgerEntry {
const commonEntry = this.billCommonEntry;
const adjustmentAmount = Math.abs(this.bill.adjustmentLocal);
return {
...commonEntry,
debit: this.bill.adjustmentLocal > 0 ? adjustmentAmount : 0,
credit: this.bill.adjustmentLocal < 0 ? adjustmentAmount : 0,
accountId: this.otherExpensesAccountId,
accountNormal: AccountNormal.DEBIT,
index: 1,
indexGroup: 40,
};
}
/**
* Retrieves the given bill GL entries.
* @returns {ILedgerEntry[]}
*/
private getBillGLEntries = (): ILedgerEntry[] => {
const payableEntry = this.billPayableEntry;
const itemsEntries = this.bill.entries.map((entry, index) =>
this.getBillItemEntry(entry, index),
);
const landedCostEntries = this.bill.locatedLandedCosts.map(
(landedCost, index) => this.getBillLandedCostEntry(landedCost, index),
);
// Allocate cost entries journal entries.
return [
payableEntry,
...itemsEntries,
...landedCostEntries,
this.purchaseDiscountEntry,
this.adjustmentEntry,
];
};
/**
* Retrieves the given bill ledger.
* @returns {Ledger}
*/
public getBillLedger = () => {
const entries = this.getBillGLEntries();
return new Ledger(entries);
};
}

View File

@@ -0,0 +1,97 @@
import { Knex } from 'knex';
import { LedgerStorageService } from '@/modules/Ledger/LedgerStorage.service';
import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository';
import { Bill } from '../models/Bill';
import { Injectable } from '@nestjs/common';
import { BillGL } from './BillsGL';
@Injectable()
export class BillGLEntries {
/**
* @param {LedgerStorageService} ledgerStorage - Ledger storage service.
* @param {AccountRepository} accountRepository - Account repository.
* @param {typeof Bill} billModel - Bill model.
*/
constructor(
private readonly ledgerStorage: LedgerStorageService,
private readonly accountRepository: AccountRepository,
private readonly billModel: typeof Bill,
) {}
/**
* Creates bill GL entries.
* @param {number} billId - Bill id.
* @param {Knex.Transaction} trx - Knex transaction.
*/
public writeBillGLEntries = async (
billId: number,
trx?: Knex.Transaction,
) => {
// Retrieves bill with associated entries and landed costs.
const bill = await this.billModel
.query(trx)
.findById(billId)
.withGraphFetched('entries.item')
.withGraphFetched('entries.allocatedCostEntries')
.withGraphFetched('locatedLandedCosts.allocateEntries');
// Finds or create a A/P account based on the given currency.
const APAccount = await this.accountRepository.findOrCreateAccountsPayable(
bill.currencyCode,
{},
trx,
);
// Find or create tax payable account.
const taxPayableAccount =
await this.accountRepository.findOrCreateTaxPayable({}, trx);
// Find or create other expenses account.
const otherExpensesAccount =
await this.accountRepository.findOrCreateOtherExpensesAccount({}, trx);
// Find or create purchase discount account.
const purchaseDiscountAccount =
await this.accountRepository.findOrCreatePurchaseDiscountAccount({}, trx);
// Retrieves the bill ledger.
const billLedger = new BillGL(bill)
.setPayableAccountId(APAccount.id)
.setTaxPayableAccountId(taxPayableAccount.id)
.setPurchaseDiscountAccountId(purchaseDiscountAccount.id)
.setOtherExpensesAccountId(otherExpensesAccount.id)
.getBillLedger();
// Commit the GL enties on the storage.
await this.ledgerStorage.commit(billLedger, trx);
};
/**
* Reverts the given bill GL entries.
* @param {number} tenantId
* @param {number} billId
* @param {Knex.Transaction} trx
*/
public revertBillGLEntries = async (
billId: number,
trx?: Knex.Transaction,
) => {
await this.ledgerStorage.deleteByReference(billId, 'Bill', trx);
};
/**
* Rewrites the given bill GL entries.
* @param {number} tenantId
* @param {number} billId
* @param {Knex.Transaction} trx
*/
public rewriteBillGLEntries = async (
billId: number,
trx?: Knex.Transaction,
) => {
// Reverts the bill GL entries.
await this.revertBillGLEntries(billId, trx);
// Writes the bill GL entries.
await this.writeBillGLEntries(billId, trx);
};
}

View File

@@ -9,6 +9,7 @@ import moment from 'moment';
// import ModelSearchable from './ModelSearchable'; // import ModelSearchable from './ModelSearchable';
import { BaseModel } from '@/models/Model'; import { BaseModel } from '@/models/Model';
import { ItemEntry } from '@/modules/Items/models/ItemEntry'; import { ItemEntry } from '@/modules/Items/models/ItemEntry';
import { BillLandedCost } from '@/modules/BillLandedCosts/models/BillLandedCost';
export class Bill extends BaseModel{ export class Bill extends BaseModel{
public amount: number; public amount: number;
@@ -34,12 +35,13 @@ export class Bill extends BaseModel{
public branchId: number; public branchId: number;
public warehouseId: number; public warehouseId: number;
public projectId: number;
public createdAt: Date; public createdAt: Date;
public updatedAt: Date | null; public updatedAt: Date | null;
public entries?: ItemEntry[]; public entries?: ItemEntry[];
public locatedLandedCosts?: BillLandedCost[];
/** /**
* Timestamps columns. * Timestamps columns.
*/ */

View File

@@ -0,0 +1,59 @@
import { Injectable } from '@nestjs/common';
import {
IBillCreatedPayload,
IBillEditedPayload,
IBIllEventDeletedPayload,
IBillOpenedPayload,
} from '../Bills.types';
import { BillGLEntries } from '../commands/BillsGLEntries';
import { events } from '@/common/events/events';
import { OnEvent } from '@nestjs/event-emitter';
@Injectable()
export class BillGLEntriesSubscriber {
/**
* @param {BillGLEntries} billGLEntries - Bill GL entries command.
*/
constructor(private billGLEntries: BillGLEntries) {}
/**
* Handles writing journal entries once bill created.
* @param {IBillCreatedPayload} payload -
*/
@OnEvent(events.bill.onCreated)
@OnEvent(events.bill.onOpened)
public async handlerWriteJournalEntriesOnCreate({
bill,
trx,
}: IBillCreatedPayload | IBillOpenedPayload) {
if (!bill.openedAt) return null;
await this.billGLEntries.writeBillGLEntries(bill.id, trx);
};
/**
* Handles the overwriting journal entries once bill edited.
* @param {IBillEditedPayload} payload -
*/
@OnEvent(events.bill.onEdited)
public async handleOverwriteJournalEntriesOnEdit({
bill,
trx,
}: IBillEditedPayload) {
if (!bill.openedAt) return null;
await this.billGLEntries.rewriteBillGLEntries(bill.id, trx);
};
/**
* Handles revert journal entries on bill deleted.
* @param {IBIllEventDeletedPayload} payload -
*/
@OnEvent(events.bill.onDeleted)
public async handlerDeleteJournalEntries({
oldBill,
trx,
}: IBIllEventDeletedPayload) {
await this.billGLEntries.revertBillGLEntries(oldBill.id, trx);
};
}

View File

@@ -18,6 +18,8 @@ import { TemplateInjectableModule } from '../TemplateInjectable/TemplateInjectab
import { GetCreditNote } from './queries/GetCreditNote.service'; import { GetCreditNote } from './queries/GetCreditNote.service';
import { CreditNoteBrandingTemplate } from './queries/CreditNoteBrandingTemplate.service'; import { CreditNoteBrandingTemplate } from './queries/CreditNoteBrandingTemplate.service';
import { AutoIncrementOrdersModule } from '../AutoIncrementOrders/AutoIncrementOrders.module'; import { AutoIncrementOrdersModule } from '../AutoIncrementOrders/AutoIncrementOrders.module';
import CreditNoteGLEntries from './commands/CreditNoteGLEntries';
import CreditNoteGLEntriesSubscriber from './subscribers/CreditNoteGLEntriesSubscriber';
@Module({ @Module({
imports: [ imports: [
@@ -40,7 +42,9 @@ import { AutoIncrementOrdersModule } from '../AutoIncrementOrders/AutoIncrementO
CreditNoteAutoIncrementService, CreditNoteAutoIncrementService,
GetCreditNoteState, GetCreditNoteState,
CreditNoteApplication, CreditNoteApplication,
CreditNoteBrandingTemplate CreditNoteBrandingTemplate,
CreditNoteGLEntries,
CreditNoteGLEntriesSubscriber
], ],
exports: [ exports: [
CreateCreditNoteService, CreateCreditNoteService,

View File

@@ -1,297 +1,293 @@
// import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
// import { Knex } from 'knex'; import { Knex } from 'knex';
// import * as R from 'ramda'; import * as R from 'ramda';
// import { import {
// AccountNormal, AccountNormal,
// IItemEntry, IItemEntry,
// ILedgerEntry, ILedgerEntry,
// ICreditNote, ICreditNote,
// ILedger, ILedger,
// ICreditNoteGLCommonEntry, ICreditNoteGLCommonEntry,
// } from '@/interfaces'; } from '@/interfaces';
// import HasTenancyService from '@/services/Tenancy/TenancyService'; import HasTenancyService from '@/services/Tenancy/TenancyService';
// import Ledger from '@/services/Accounting/Ledger'; import Ledger from '@/services/Accounting/Ledger';
// import LedgerStorageService from '@/services/Accounting/LedgerStorageService'; import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
// import { SaleReceipt } from '@/models'; import { SaleReceipt } from '@/models';
// @Service() @Service()
// export default class CreditNoteGLEntries { export default class CreditNoteGLEntries {
// @Inject() @Inject()
// private tenancy: HasTenancyService; private tenancy: HasTenancyService;
// @Inject() @Inject()
// private ledgerStorage: LedgerStorageService; private ledgerStorage: LedgerStorageService;
// /** /**
// * Retrieves the credit note GL. * Retrieves the credit note GL.
// * @param {ICreditNote} creditNote * @param {ICreditNote} creditNote
// * @param {number} receivableAccount * @param {number} receivableAccount
// * @returns {Ledger} * @returns {Ledger}
// */ */
// private getCreditNoteGLedger = ( private getCreditNoteGLedger = (
// creditNote: ICreditNote, creditNote: ICreditNote,
// receivableAccount: number, receivableAccount: number,
// discountAccount: number, discountAccount: number,
// adjustmentAccount: number adjustmentAccount: number
// ): Ledger => { ): Ledger => {
// const ledgerEntries = this.getCreditNoteGLEntries( const ledgerEntries = this.getCreditNoteGLEntries(
// creditNote, creditNote,
// receivableAccount, receivableAccount,
// discountAccount, discountAccount,
// adjustmentAccount adjustmentAccount
// ); );
// return new Ledger(ledgerEntries); return new Ledger(ledgerEntries);
// }; };
// /** /**
// * Saves credit note GL entries. * Saves credit note GL entries.
// * @param {number} tenantId - * @param {number} tenantId -
// * @param {ICreditNote} creditNote - Credit note model. * @param {ICreditNote} creditNote - Credit note model.
// * @param {number} payableAccount - Payable account id. * @param {number} payableAccount - Payable account id.
// * @param {Knex.Transaction} trx * @param {Knex.Transaction} trx
// */ */
// public saveCreditNoteGLEntries = async ( public saveCreditNoteGLEntries = async (
// tenantId: number, tenantId: number,
// creditNote: ICreditNote, creditNote: ICreditNote,
// payableAccount: number, payableAccount: number,
// discountAccount: number, discountAccount: number,
// adjustmentAccount: number, adjustmentAccount: number,
// trx?: Knex.Transaction trx?: Knex.Transaction
// ): Promise<void> => { ): Promise<void> => {
// const ledger = this.getCreditNoteGLedger( const ledger = this.getCreditNoteGLedger(
// creditNote, creditNote,
// payableAccount, payableAccount,
// discountAccount, discountAccount,
// adjustmentAccount adjustmentAccount
// ); );
// await this.ledgerStorage.commit(tenantId, ledger, trx); await this.ledgerStorage.commit(tenantId, ledger, trx);
// }; };
// /** /**
// * Reverts the credit note associated GL entries. * Reverts the credit note associated GL entries.
// * @param {number} tenantId * @param {number} tenantId
// * @param {number} vendorCreditId * @param {number} vendorCreditId
// * @param {Knex.Transaction} trx * @param {Knex.Transaction} trx
// */ */
// public revertVendorCreditGLEntries = async ( public revertVendorCreditGLEntries = async (
// tenantId: number, creditNoteId: number,
// creditNoteId: number, trx?: Knex.Transaction
// trx?: Knex.Transaction ): Promise<void> => {
// ): Promise<void> => { await this.ledgerStorage.deleteByReference(
// await this.ledgerStorage.deleteByReference( creditNoteId,
// tenantId, 'CreditNote',
// creditNoteId, trx
// 'CreditNote', );
// trx };
// );
// };
// /** /**
// * Writes vendor credit associated GL entries. * Writes vendor credit associated GL entries.
// * @param {number} tenantId - Tenant id. * @param {number} tenantId - Tenant id.
// * @param {number} creditNoteId - Credit note id. * @param {number} creditNoteId - Credit note id.
// * @param {Knex.Transaction} trx - Knex transactions. * @param {Knex.Transaction} trx - Knex transactions.
// */ */
// public createVendorCreditGLEntries = async ( public createVendorCreditGLEntries = async (
// tenantId: number, creditNoteId: number,
// creditNoteId: number, trx?: Knex.Transaction
// trx?: Knex.Transaction ): Promise<void> => {
// ): Promise<void> => { const { CreditNote } = this.tenancy.models(tenantId);
// const { CreditNote } = this.tenancy.models(tenantId); const { accountRepository } = this.tenancy.repositories(tenantId);
// const { accountRepository } = this.tenancy.repositories(tenantId);
// // Retrieve the credit note with associated entries and items. // Retrieve the credit note with associated entries and items.
// const creditNoteWithItems = await CreditNote.query(trx) const creditNoteWithItems = await CreditNote.query(trx)
// .findById(creditNoteId) .findById(creditNoteId)
// .withGraphFetched('entries.item'); .withGraphFetched('entries.item');
// // Retreive the the `accounts receivable` account based on the given currency. // Retreive the the `accounts receivable` account based on the given currency.
// const ARAccount = await accountRepository.findOrCreateAccountReceivable( const ARAccount = await accountRepository.findOrCreateAccountReceivable(
// creditNoteWithItems.currencyCode creditNoteWithItems.currencyCode
// ); );
// const discountAccount = await accountRepository.findOrCreateDiscountAccount( const discountAccount = await accountRepository.findOrCreateDiscountAccount(
// {} {}
// ); );
// const adjustmentAccount = const adjustmentAccount =
// await accountRepository.findOrCreateOtherChargesAccount({}); await accountRepository.findOrCreateOtherChargesAccount({});
// // Saves the credit note GL entries. // Saves the credit note GL entries.
// await this.saveCreditNoteGLEntries( await this.saveCreditNoteGLEntries(
// tenantId, tenantId,
// creditNoteWithItems, creditNoteWithItems,
// ARAccount.id, ARAccount.id,
// discountAccount.id, discountAccount.id,
// adjustmentAccount.id, adjustmentAccount.id,
// trx trx
// ); );
// }; };
// /** /**
// * Edits vendor credit associated GL entries. * Edits vendor credit associated GL entries.
// * @param {number} tenantId * @param {number} tenantId
// * @param {number} creditNoteId * @param {number} creditNoteId
// * @param {Knex.Transaction} trx * @param {Knex.Transaction} trx
// */ */
// public editVendorCreditGLEntries = async ( public editVendorCreditGLEntries = async (
// tenantId: number, creditNoteId: number,
// creditNoteId: number, trx?: Knex.Transaction
// trx?: Knex.Transaction ): Promise<void> => {
// ): Promise<void> => { // Reverts vendor credit GL entries.
// // Reverts vendor credit GL entries. await this.revertVendorCreditGLEntries(creditNoteId, trx);
// await this.revertVendorCreditGLEntries(tenantId, creditNoteId, trx);
// // Creates vendor credit Gl entries. // Creates vendor credit Gl entries.
// await this.createVendorCreditGLEntries(tenantId, creditNoteId, trx); await this.createVendorCreditGLEntries(creditNoteId, trx);
// }; };
// /** /**
// * Retrieve the credit note common entry. * Retrieve the credit note common entry.
// * @param {ICreditNote} creditNote - * @param {ICreditNote} creditNote -
// * @returns {ICreditNoteGLCommonEntry} * @returns {ICreditNoteGLCommonEntry}
// */ */
// private getCreditNoteCommonEntry = ( private getCreditNoteCommonEntry = (
// creditNote: ICreditNote creditNote: ICreditNote
// ): ICreditNoteGLCommonEntry => { ): ICreditNoteGLCommonEntry => {
// return { return {
// date: creditNote.creditNoteDate, date: creditNote.creditNoteDate,
// userId: creditNote.userId, userId: creditNote.userId,
// currencyCode: creditNote.currencyCode, currencyCode: creditNote.currencyCode,
// exchangeRate: creditNote.exchangeRate, exchangeRate: creditNote.exchangeRate,
// transactionType: 'CreditNote', transactionType: 'CreditNote',
// transactionId: creditNote.id, transactionId: creditNote.id,
// transactionNumber: creditNote.creditNoteNumber, transactionNumber: creditNote.creditNoteNumber,
// referenceNumber: creditNote.referenceNo, referenceNumber: creditNote.referenceNo,
// createdAt: creditNote.createdAt, createdAt: creditNote.createdAt,
// indexGroup: 10, indexGroup: 10,
// credit: 0, credit: 0,
// debit: 0, debit: 0,
// branchId: creditNote.branchId, branchId: creditNote.branchId,
// }; };
// }; };
// /** /**
// * Retrieves the creidt note A/R entry. * Retrieves the creidt note A/R entry.
// * @param {ICreditNote} creditNote - * @param {ICreditNote} creditNote -
// * @param {number} ARAccountId - * @param {number} ARAccountId -
// * @returns {ILedgerEntry} * @returns {ILedgerEntry}
// */ */
// private getCreditNoteAREntry = ( private getCreditNoteAREntry = (
// creditNote: ICreditNote, creditNote: ICreditNote,
// ARAccountId: number ARAccountId: number
// ): ILedgerEntry => { ): ILedgerEntry => {
// const commonEntry = this.getCreditNoteCommonEntry(creditNote); const commonEntry = this.getCreditNoteCommonEntry(creditNote);
// return { return {
// ...commonEntry, ...commonEntry,
// credit: creditNote.totalLocal, credit: creditNote.totalLocal,
// accountId: ARAccountId, accountId: ARAccountId,
// contactId: creditNote.customerId, contactId: creditNote.customerId,
// index: 1, index: 1,
// accountNormal: AccountNormal.DEBIT, accountNormal: AccountNormal.DEBIT,
// }; };
// }; };
// /** /**
// * Retrieve the credit note item entry. * Retrieve the credit note item entry.
// * @param {ICreditNote} creditNote * @param {ICreditNote} creditNote
// * @param {IItemEntry} entry * @param {IItemEntry} entry
// * @param {number} index * @param {number} index
// * @returns {ILedgerEntry} * @returns {ILedgerEntry}
// */ */
// private getCreditNoteItemEntry = R.curry( private getCreditNoteItemEntry = R.curry(
// ( (
// creditNote: ICreditNote, creditNote: ICreditNote,
// entry: IItemEntry, entry: IItemEntry,
// index: number index: number
// ): ILedgerEntry => { ): ILedgerEntry => {
// const commonEntry = this.getCreditNoteCommonEntry(creditNote); const commonEntry = this.getCreditNoteCommonEntry(creditNote);
// const totalLocal = entry.totalExcludingTax * creditNote.exchangeRate; const totalLocal = entry.totalExcludingTax * creditNote.exchangeRate;
// return { return {
// ...commonEntry, ...commonEntry,
// debit: totalLocal, debit: totalLocal,
// accountId: entry.sellAccountId || entry.item.sellAccountId, accountId: entry.sellAccountId || entry.item.sellAccountId,
// note: entry.description, note: entry.description,
// index: index + 2, index: index + 2,
// itemId: entry.itemId, itemId: entry.itemId,
// itemQuantity: entry.quantity, itemQuantity: entry.quantity,
// accountNormal: AccountNormal.CREDIT, accountNormal: AccountNormal.CREDIT,
// }; };
// } }
// ); );
// /** /**
// * Retrieves the credit note discount entry. * Retrieves the credit note discount entry.
// * @param {ICreditNote} creditNote * @param {ICreditNote} creditNote
// * @param {number} discountAccountId * @param {number} discountAccountId
// * @returns {ILedgerEntry} * @returns {ILedgerEntry}
// */ */
// private getDiscountEntry = ( private getDiscountEntry = (
// creditNote: ICreditNote, creditNote: ICreditNote,
// discountAccountId: number discountAccountId: number
// ): ILedgerEntry => { ): ILedgerEntry => {
// const commonEntry = this.getCreditNoteCommonEntry(creditNote); const commonEntry = this.getCreditNoteCommonEntry(creditNote);
// return { return {
// ...commonEntry, ...commonEntry,
// credit: creditNote.discountAmountLocal, credit: creditNote.discountAmountLocal,
// accountId: discountAccountId, accountId: discountAccountId,
// index: 1, index: 1,
// accountNormal: AccountNormal.CREDIT, accountNormal: AccountNormal.CREDIT,
// }; };
// }; };
// /** /**
// * Retrieves the credit note adjustment entry. * Retrieves the credit note adjustment entry.
// * @param {ICreditNote} creditNote * @param {ICreditNote} creditNote
// * @param {number} adjustmentAccountId * @param {number} adjustmentAccountId
// * @returns {ILedgerEntry} * @returns {ILedgerEntry}
// */ */
// private getAdjustmentEntry = ( private getAdjustmentEntry = (
// creditNote: ICreditNote, creditNote: ICreditNote,
// adjustmentAccountId: number adjustmentAccountId: number
// ): ILedgerEntry => { ): ILedgerEntry => {
// const commonEntry = this.getCreditNoteCommonEntry(creditNote); const commonEntry = this.getCreditNoteCommonEntry(creditNote);
// const adjustmentAmount = Math.abs(creditNote.adjustmentLocal); const adjustmentAmount = Math.abs(creditNote.adjustmentLocal);
// return { return {
// ...commonEntry, ...commonEntry,
// credit: creditNote.adjustmentLocal < 0 ? adjustmentAmount : 0, credit: creditNote.adjustmentLocal < 0 ? adjustmentAmount : 0,
// debit: creditNote.adjustmentLocal > 0 ? adjustmentAmount : 0, debit: creditNote.adjustmentLocal > 0 ? adjustmentAmount : 0,
// accountId: adjustmentAccountId, accountId: adjustmentAccountId,
// accountNormal: AccountNormal.CREDIT, accountNormal: AccountNormal.CREDIT,
// index: 1, index: 1,
// }; };
// }; };
// /** /**
// * Retrieve the credit note GL entries. * Retrieve the credit note GL entries.
// * @param {ICreditNote} creditNote - Credit note. * @param {ICreditNote} creditNote - Credit note.
// * @param {IAccount} receivableAccount - Receviable account. * @param {IAccount} receivableAccount - Receviable account.
// * @returns {ILedgerEntry[]} - Ledger entries. * @returns {ILedgerEntry[]} - Ledger entries.
// */ */
// public getCreditNoteGLEntries = ( public getCreditNoteGLEntries = (
// creditNote: ICreditNote, creditNote: ICreditNote,
// ARAccountId: number, ARAccountId: number,
// discountAccountId: number, discountAccountId: number,
// adjustmentAccountId: number adjustmentAccountId: number
// ): ILedgerEntry[] => { ): ILedgerEntry[] => {
// const AREntry = this.getCreditNoteAREntry(creditNote, ARAccountId); const AREntry = this.getCreditNoteAREntry(creditNote, ARAccountId);
// const getItemEntry = this.getCreditNoteItemEntry(creditNote); const getItemEntry = this.getCreditNoteItemEntry(creditNote);
// const itemsEntries = creditNote.entries.map(getItemEntry); const itemsEntries = creditNote.entries.map(getItemEntry);
// const discountEntry = this.getDiscountEntry(creditNote, discountAccountId); const discountEntry = this.getDiscountEntry(creditNote, discountAccountId);
// const adjustmentEntry = this.getAdjustmentEntry( const adjustmentEntry = this.getAdjustmentEntry(
// creditNote, creditNote,
// adjustmentAccountId adjustmentAccountId
// ); );
// return [AREntry, discountEntry, adjustmentEntry, ...itemsEntries]; return [AREntry, discountEntry, adjustmentEntry, ...itemsEntries];
// }; };
// } }

View File

@@ -1,113 +1,78 @@
// import { Service, Inject } from 'typedi'; import {
// import events from '@/subscribers/events'; ICreditNoteCreatedPayload,
// import { ICreditNoteDeletedPayload,
// ICreditNoteCreatedPayload, ICreditNoteEditedPayload,
// ICreditNoteDeletedPayload, ICreditNoteOpenedPayload,
// ICreditNoteEditedPayload, } from '../types/CreditNotes.types';
// ICreditNoteOpenedPayload, import CreditNoteGLEntries from '../commands/CreditNoteGLEntries';
// } from '@/interfaces'; import { OnEvent } from '@nestjs/event-emitter';
// import CreditNoteGLEntries from '../commands/CreditNoteGLEntries'; import { Injectable } from '@nestjs/common';
import { events } from '@/common/events/events';
// @Service() @Injectable()
// export default class CreditNoteGLEntriesSubscriber { export default class CreditNoteGLEntriesSubscriber {
// @Inject() constructor(private readonly creditNoteGLEntries: CreditNoteGLEntries) {}
// private creditNoteGLEntries: CreditNoteGLEntries;
// /** /**
// * Attaches events with handlers. * Writes the GL entries once the credit note transaction created or open.
// * @param bus * @param {ICreditNoteCreatedPayload|ICreditNoteOpenedPayload} payload -
// */ */
// public attach(bus) { @OnEvent(events.creditNote.onCreated)
// bus.subscribe( public async writeGlEntriesOnceCreditNoteCreated({
// events.creditNote.onCreated, creditNote,
// this.writeGlEntriesOnceCreditNoteCreated trx,
// ); }: ICreditNoteCreatedPayload | ICreditNoteOpenedPayload) {
// bus.subscribe( // Can't continue if the credit note is not published yet.
// events.creditNote.onOpened, if (!creditNote.isPublished) return;
// this.writeGLEntriesOnceCreditNoteOpened
// );
// bus.subscribe(
// events.creditNote.onEdited,
// this.editVendorCreditGLEntriesOnceEdited
// );
// bus.subscribe(
// events.creditNote.onDeleted,
// this.revertGLEntriesOnceCreditNoteDeleted
// );
// }
// /** await this.creditNoteGLEntries.createVendorCreditGLEntries(
// * Writes the GL entries once the credit note transaction created or open. creditNote.id,
// * @private trx,
// * @param {ICreditNoteCreatedPayload|ICreditNoteOpenedPayload} payload - );
// */ }
// private writeGlEntriesOnceCreditNoteCreated = async ({
// tenantId,
// creditNote,
// creditNoteId,
// trx,
// }: ICreditNoteCreatedPayload | ICreditNoteOpenedPayload) => {
// // Can't continue if the credit note is not published yet.
// if (!creditNote.isPublished) return;
// await this.creditNoteGLEntries.createVendorCreditGLEntries( /**
// tenantId, * Writes the GL entries once the vendor credit transaction opened.
// creditNoteId, * @param {ICreditNoteOpenedPayload} payload
// trx */
// ); @OnEvent(events.creditNote.onOpened)
// }; public async writeGLEntriesOnceCreditNoteOpened({
creditNote,
trx,
}: ICreditNoteOpenedPayload) {
await this.creditNoteGLEntries.createVendorCreditGLEntries(
creditNote.id,
trx,
);
}
// /** /**
// * Writes the GL entries once the vendor credit transaction opened. * Reverts GL entries once credit note deleted.
// * @param {ICreditNoteOpenedPayload} payload */
// */ @OnEvent(events.creditNote.onDeleted)
// private writeGLEntriesOnceCreditNoteOpened = async ({ public async revertGLEntriesOnceCreditNoteDeleted({
// tenantId, oldCreditNote,
// creditNoteId, creditNoteId,
// trx, trx,
// }: ICreditNoteOpenedPayload) => { }: ICreditNoteDeletedPayload) {
// await this.creditNoteGLEntries.createVendorCreditGLEntries( // Can't continue if the credit note is not published yet.
// tenantId, if (!oldCreditNote.isPublished) return;
// creditNoteId,
// trx
// );
// };
// /** await this.creditNoteGLEntries.revertVendorCreditGLEntries(creditNoteId);
// * Reverts GL entries once credit note deleted. }
// */
// private revertGLEntriesOnceCreditNoteDeleted = async ({
// tenantId,
// oldCreditNote,
// creditNoteId,
// trx,
// }: ICreditNoteDeletedPayload) => {
// // Can't continue if the credit note is not published yet.
// if (!oldCreditNote.isPublished) return;
// await this.creditNoteGLEntries.revertVendorCreditGLEntries( /**
// tenantId, * Edits vendor credit associated GL entries once the transaction edited.
// creditNoteId * @param {ICreditNoteEditedPayload} payload -
// ); */
// }; @OnEvent(events.creditNote.onEdited)
public async editVendorCreditGLEntriesOnceEdited({
creditNote,
trx,
}: ICreditNoteEditedPayload) {
// Can't continue if the credit note is not published yet.
if (!creditNote.isPublished) return;
// /** await this.creditNoteGLEntries.editVendorCreditGLEntries(creditNote.id, trx);
// * Edits vendor credit associated GL entries once the transaction edited. }
// * @param {ICreditNoteEditedPayload} payload - }
// */
// private editVendorCreditGLEntriesOnceEdited = async ({
// tenantId,
// creditNote,
// creditNoteId,
// trx,
// }: ICreditNoteEditedPayload) => {
// // Can't continue if the credit note is not published yet.
// if (!creditNote.isPublished) return;
// await this.creditNoteGLEntries.editVendorCreditGLEntries(
// tenantId,
// creditNoteId,
// trx
// );
// };
// }

View File

@@ -12,8 +12,10 @@ import {
IExpenseCreateDTO, IExpenseCreateDTO,
IExpenseEditDTO, IExpenseEditDTO,
} from './interfaces/Expenses.interface'; } from './interfaces/Expenses.interface';
import { PublicRoute } from '../Auth/Jwt.guard';
@Controller('expenses') @Controller('expenses')
@PublicRoute()
export class ExpensesController { export class ExpensesController {
constructor(private readonly expensesApplication: ExpensesApplication) {} constructor(private readonly expensesApplication: ExpensesApplication) {}

View File

@@ -12,9 +12,12 @@ import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service'; import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
import { ExpensesWriteGLSubscriber } from './subscribers/ExpenseGLEntries.subscriber'; import { ExpensesWriteGLSubscriber } from './subscribers/ExpenseGLEntries.subscriber';
import { ExpenseGLEntriesStorageService } from './subscribers/ExpenseGLEntriesStorage.sevice'; import { ExpenseGLEntriesStorageService } from './subscribers/ExpenseGLEntriesStorage.sevice';
import { ExpenseGLEntriesService } from './subscribers/ExpenseGLEntries.service';
import { LedgerModule } from '../Ledger/Ledger.module';
import { BranchesModule } from '../Branches/Branches.module';
@Module({ @Module({
imports: [], imports: [LedgerModule, BranchesModule],
controllers: [ExpensesController], controllers: [ExpensesController],
providers: [ providers: [
CreateExpense, CreateExpense,
@@ -29,6 +32,7 @@ import { ExpenseGLEntriesStorageService } from './subscribers/ExpenseGLEntriesSt
TransformerInjectable, TransformerInjectable,
ExpensesWriteGLSubscriber, ExpensesWriteGLSubscriber,
ExpenseGLEntriesStorageService, ExpenseGLEntriesStorageService,
ExpenseGLEntriesService
], ],
}) })
export class ExpensesModule {} export class ExpensesModule {}

View File

@@ -2,10 +2,6 @@ import { Knex } from 'knex';
import { Expense } from './models/Expense.model'; import { Expense } from './models/Expense.model';
import { SystemUser } from '../System/models/SystemUser'; import { SystemUser } from '../System/models/SystemUser';
import { IFilterRole } from '../DynamicListing/DynamicFilter/DynamicFilter.types'; import { IFilterRole } from '../DynamicListing/DynamicFilter/DynamicFilter.types';
// import { ISystemUser } from './User';
// import { IFilterRole } from './DynamicFilter';
// import { IAccount } from './Account';
// import { AttachmentLinkDTO } from './Attachments';
export interface IPaginationMeta { export interface IPaginationMeta {
total: number; total: number;
@@ -23,55 +19,6 @@ export interface IExpensesFilter {
filterQuery?: (query: any) => void; filterQuery?: (query: any) => void;
} }
// export interface IExpense {
// id: number;
// totalAmount: number;
// localAmount?: number;
// currencyCode: string;
// exchangeRate: number;
// description?: string;
// paymentAccountId: number;
// peyeeId?: number;
// referenceNo?: string;
// publishedAt: Date | null;
// userId: number;
// paymentDate: Date;
// payeeId: number;
// landedCostAmount: number;
// allocatedCostAmount: number;
// unallocatedCostAmount: number;
// categories?: IExpenseCategory[];
// isPublished: boolean;
// localLandedCostAmount?: number;
// localAllocatedCostAmount?: number;
// localUnallocatedCostAmount?: number;
// billableAmount: number;
// invoicedAmount: number;
// branchId?: number;
// createdAt?: Date;
// }
// export interface IExpenseCategory {
// id?: number;
// expenseAccountId: number;
// index: number;
// description: string;
// expenseId: number;
// amount: number;
// projectId?: number;
// allocatedCostAmount: number;
// unallocatedCostAmount: number;
// landedCost: boolean;
// expenseAccount?: IAccount;
// }
export interface IExpenseCommonDTO { export interface IExpenseCommonDTO {
currencyCode: string; currencyCode: string;
exchangeRate?: number; exchangeRate?: number;
@@ -103,68 +50,25 @@ export interface IExpenseCategoryDTO {
projectId?: number; projectId?: number;
} }
// export interface IExpensesService {
// newExpense(
// tenantid: number,
// expenseDTO: IExpenseDTO,
// authorizedUser: ISystemUser
// ): Promise<IExpense>;
// editExpense(
// tenantid: number,
// expenseId: number,
// expenseDTO: IExpenseDTO,
// authorizedUser: ISystemUser
// ): void;
// publishExpense(
// tenantId: number,
// expenseId: number,
// authorizedUser: ISystemUser
// ): Promise<void>;
// deleteExpense(
// tenantId: number,
// expenseId: number,
// authorizedUser: ISystemUser
// ): Promise<void>;
// getExpensesList(
// tenantId: number,
// expensesFilter: IExpensesFilter
// ): Promise<{
// expenses: IExpense[];
// pagination: IPaginationMeta;
// filterMeta: IFilterMeta;
// }>;
// getExpense(tenantId: number, expenseId: number): Promise<IExpense>;
// }
export interface IExpenseCreatingPayload { export interface IExpenseCreatingPayload {
trx: Knex.Transaction; trx: Knex.Transaction;
// tenantId: number;
expenseDTO: IExpenseCreateDTO; expenseDTO: IExpenseCreateDTO;
} }
export interface IExpenseEventEditingPayload { export interface IExpenseEventEditingPayload {
// tenantId: number;
oldExpense: Expense; oldExpense: Expense;
expenseDTO: IExpenseEditDTO; expenseDTO: IExpenseEditDTO;
trx: Knex.Transaction; trx: Knex.Transaction;
} }
export interface IExpenseCreatedPayload { export interface IExpenseCreatedPayload {
// tenantId: number;
expenseId: number; expenseId: number;
// authorizedUser: ISystemUser;
expense: Expense; expense: Expense;
expenseDTO: IExpenseCreateDTO; expenseDTO: IExpenseCreateDTO;
trx?: Knex.Transaction; trx?: Knex.Transaction;
} }
export interface IExpenseEventEditPayload { export interface IExpenseEventEditPayload {
// tenantId: number;
expenseId: number; expenseId: number;
expense: Expense; expense: Expense;
expenseDTO: IExpenseEditDTO; expenseDTO: IExpenseEditDTO;
@@ -174,7 +78,6 @@ export interface IExpenseEventEditPayload {
} }
export interface IExpenseEventDeletePayload { export interface IExpenseEventDeletePayload {
// tenantId: number;
expenseId: number; expenseId: number;
authorizedUser: SystemUser; authorizedUser: SystemUser;
oldExpense: Expense; oldExpense: Expense;
@@ -183,11 +86,9 @@ export interface IExpenseEventDeletePayload {
export interface IExpenseDeletingPayload { export interface IExpenseDeletingPayload {
trx: Knex.Transaction; trx: Knex.Transaction;
// tenantId: number;
oldExpense: Expense; oldExpense: Expense;
} }
export interface IExpenseEventPublishedPayload { export interface IExpenseEventPublishedPayload {
// tenantId: number;
expenseId: number; expenseId: number;
oldExpense: Expense; oldExpense: Expense;
expense: Expense; expense: Expense;
@@ -198,7 +99,6 @@ export interface IExpenseEventPublishedPayload {
export interface IExpensePublishingPayload { export interface IExpensePublishingPayload {
trx: Knex.Transaction; trx: Knex.Transaction;
oldExpense: Expense; oldExpense: Expense;
// tenantId: number;
} }
export enum ExpenseAction { export enum ExpenseAction {
Create = 'Create', Create = 'Create',

View File

@@ -1,21 +1,24 @@
import { Injectable } from '@nestjs/common';
import { omit, sumBy } from 'lodash'; import { omit, sumBy } from 'lodash';
import moment from 'moment'; import * as moment from 'moment';
import * as R from 'ramda'; import * as R from 'ramda';
import { import {
IExpenseCreateDTO, IExpenseCreateDTO,
IExpenseCommonDTO,
IExpenseEditDTO, IExpenseEditDTO,
} from '../interfaces/Expenses.interface'; } from '../interfaces/Expenses.interface';
// import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform'; import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations/BranchTransactionDTOTransform';
import { Injectable } from '@nestjs/common';
import { Expense } from '../models/Expense.model'; import { Expense } from '../models/Expense.model';
import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index'; import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
@Injectable() @Injectable()
export class ExpenseDTOTransformer { export class ExpenseDTOTransformer {
/**
* @param {BranchTransactionDTOTransformer} branchDTOTransform - Branch transaction DTO transformer.
* @param {TenancyContext} tenancyContext - Tenancy context.
*/
constructor( constructor(
// private readonly branchDTOTransform: BranchTransactionDTOTransform; private readonly branchDTOTransform: BranchTransactionDTOTransformer,
private readonly tenancyContext: TenancyContext, private readonly tenancyContext: TenancyContext,
) {} ) {}
@@ -50,7 +53,6 @@ export class ExpenseDTOTransformer {
*/ */
private expenseDTOToModel( private expenseDTOToModel(
expenseDTO: IExpenseCreateDTO | IExpenseEditDTO, expenseDTO: IExpenseCreateDTO | IExpenseEditDTO,
// user?: ISystemUser
): Expense { ): Expense {
const landedCostAmount = this.getExpenseLandedCostAmount(expenseDTO); const landedCostAmount = this.getExpenseLandedCostAmount(expenseDTO);
const totalAmount = this.getExpenseCategoriesTotal(expenseDTO.categories); const totalAmount = this.getExpenseCategoriesTotal(expenseDTO.categories);
@@ -72,10 +74,9 @@ export class ExpenseDTOTransformer {
} }
: {}), : {}),
}; };
return initialDTO; return R.compose(this.branchDTOTransform.transformDTO<Expense>)(
// return R.compose(this.branchDTOTransform.transformDTO<IExpense>(tenantId))( initialDTO,
// initialDTO ) as Expense;
// );
} }
/** /**

View File

@@ -203,21 +203,28 @@ export class Expense extends BaseModel {
* Relationship mapping. * Relationship mapping.
*/ */
static get relationMappings() { static get relationMappings() {
// const Account = require('models/Account'); const { Account } = require('../../Accounts/models/Account.model');
const { ExpenseCategory } = require('./ExpenseCategory.model'); const { ExpenseCategory } = require('./ExpenseCategory.model');
// const Document = require('models/Document'); const { Document } = require('../../ChromiumlyTenancy/models/Document');
// const Branch = require('models/Branch'); const { Branch } = require('../../Branches/models/Branch.model');
// const { MatchedBankTransaction } = require('models/MatchedBankTransaction'); // const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
return { return {
// paymentAccount: { /**
// relation: Model.BelongsToOneRelation, * Expense transaction may belongs to a payment account.
// modelClass: Account.default, */
// join: { paymentAccount: {
// from: 'expenses_transactions.paymentAccountId', relation: Model.BelongsToOneRelation,
// to: 'accounts.id', modelClass: Account,
// }, join: {
// }, from: 'expenses_transactions.paymentAccountId',
to: 'accounts.id',
},
},
/**
* Expense transaction may has many expense categories.
*/
categories: { categories: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: ExpenseCategory, modelClass: ExpenseCategory,
@@ -233,33 +240,33 @@ export class Expense extends BaseModel {
/** /**
* Expense transction may belongs to a branch. * Expense transction may belongs to a branch.
*/ */
// branch: { branch: {
// relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
// modelClass: Branch.default, modelClass: Branch,
// join: { join: {
// from: 'expenses_transactions.branchId', from: 'expenses_transactions.branchId',
// to: 'branches.id', to: 'branches.id',
// }, },
// }, },
// /** /**
// * Expense transaction may has many attached attachments. * Expense transaction may has many attached attachments.
// */ */
// attachments: { attachments: {
// relation: Model.ManyToManyRelation, relation: Model.ManyToManyRelation,
// modelClass: Document.default, modelClass: Document,
// join: { join: {
// from: 'expenses_transactions.id', from: 'expenses_transactions.id',
// through: { through: {
// from: 'document_links.modelId', from: 'document_links.modelId',
// to: 'document_links.documentId', to: 'document_links.documentId',
// }, },
// to: 'documents.id', to: 'documents.id',
// }, },
// filter(query) { filter(query) {
// query.where('model_ref', 'Expense'); query.where('model_ref', 'Expense');
// }, },
// }, },
// /** // /**
// * Expense may belongs to matched bank transaction. // * Expense may belongs to matched bank transaction.

View File

@@ -77,7 +77,7 @@ export class ExpenseGL {
index: index + 2, index: index + 2,
projectId: category.projectId, projectId: category.projectId,
}; };
} },
); );
/** /**
@@ -88,8 +88,9 @@ export class ExpenseGL {
const getCategoryEntry = this.getExpenseGLCategoryEntry(); const getCategoryEntry = this.getExpenseGLCategoryEntry();
const paymentEntry = this.getExpenseGLPaymentEntry(); const paymentEntry = this.getExpenseGLPaymentEntry();
const categoryEntries = this.expense.categories.map(getCategoryEntry); const categoryEntries = this.expense.categories.map((category, index) =>
getCategoryEntry(category, index),
);
return [paymentEntry, ...categoryEntries]; return [paymentEntry, ...categoryEntries];
}; };

View File

@@ -2,6 +2,7 @@ import { Model } from 'objection';
// import TenantModel from 'models/TenantModel'; // import TenantModel from 'models/TenantModel';
// import { getExlusiveTaxAmount, getInclusiveTaxAmount } from '@/utils/taxRate'; // import { getExlusiveTaxAmount, getInclusiveTaxAmount } from '@/utils/taxRate';
import { BaseModel } from '@/models/Model'; import { BaseModel } from '@/models/Model';
import { Item } from './Item';
export class ItemEntry extends BaseModel { export class ItemEntry extends BaseModel {
public taxRate: number; public taxRate: number;
@@ -12,10 +13,14 @@ export class ItemEntry extends BaseModel {
public itemId: number; public itemId: number;
public costAccountId: number; public costAccountId: number;
public taxRateId: number; public taxRateId: number;
public sellAccountId: number;
public description: string;
public landedCost!: boolean; public landedCost!: boolean;
public allocatedCostAmount!: number; public allocatedCostAmount!: number;
public item!: Item;
/** /**
* Table name. * Table name.
* @returns {string} * @returns {string}

View File

@@ -3,18 +3,25 @@ import { LedgerStorageService } from './LedgerStorage.service';
import { LedgerEntriesStorageService } from './LedgerEntriesStorage.service'; import { LedgerEntriesStorageService } from './LedgerEntriesStorage.service';
import { LedgerRevertService } from './LedgerStorageRevert.service'; import { LedgerRevertService } from './LedgerStorageRevert.service';
import { LedgerContactsBalanceStorage } from './LedgerContactStorage.service'; import { LedgerContactsBalanceStorage } from './LedgerContactStorage.service';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { LedegrAccountsStorage } from './LedgetAccountStorage.service';
import { AccountsModule } from '../Accounts/Accounts.module';
@Module({ @Module({
imports: [AccountsModule],
providers: [ providers: [
LedgerStorageService, LedgerStorageService,
LedgerEntriesStorageService, LedgerEntriesStorageService,
LedgerRevertService, LedgerRevertService,
LedgerContactsBalanceStorage, LedgerContactsBalanceStorage,
LedegrAccountsStorage,
TenancyContext
], ],
exports: [ exports: [
LedgerStorageService, LedgerStorageService,
LedgerEntriesStorageService, LedgerEntriesStorageService,
LedgerRevertService, LedgerRevertService,
LedegrAccountsStorage
], ],
}) })
export class LedgerModule {} export class LedgerModule {}

View File

@@ -1,6 +1,9 @@
import moment from 'moment'; import moment from 'moment';
import { defaultTo, sumBy, uniqBy } from 'lodash'; import { defaultTo, sumBy, uniqBy } from 'lodash';
import { IAccountTransaction, ILedger, ILedgerEntry } from '@/interfaces'; import { ILedger } from './types/Ledger.types';
import { ILedgerEntry } from './types/Ledger.types';
import { AccountTransaction } from '../Accounts/models/AccountTransaction.model';
import { IAccountTransaction } from '@/interfaces/Account';
export class Ledger implements ILedger { export class Ledger implements ILedger {
readonly entries: ILedgerEntry[]; readonly entries: ILedgerEntry[];
@@ -225,7 +228,7 @@ export class Ledger implements ILedger {
* @param {IAccountTransaction[]} entries * @param {IAccountTransaction[]} entries
* @returns {ILedgerEntry[]} * @returns {ILedgerEntry[]}
*/ */
static mappingTransactions(entries: IAccountTransaction[]): ILedgerEntry[] { static mappingTransactions(entries: AccountTransaction[]): ILedgerEntry[] {
return entries.map(this.mapTransaction); return entries.map(this.mapTransaction);
} }
@@ -234,7 +237,7 @@ export class Ledger implements ILedger {
* @param {IAccountTransaction} entry * @param {IAccountTransaction} entry
* @returns {ILedgerEntry} * @returns {ILedgerEntry}
*/ */
static mapTransaction(entry: IAccountTransaction): ILedgerEntry { static mapTransaction(entry: AccountTransaction): ILedgerEntry {
return { return {
credit: defaultTo(entry.credit, 0), credit: defaultTo(entry.credit, 0),
debit: defaultTo(entry.debit, 0), debit: defaultTo(entry.debit, 0),
@@ -274,7 +277,7 @@ export class Ledger implements ILedger {
* @param {IAccountTransaction[]} transactions * @param {IAccountTransaction[]} transactions
* @returns {ILedger} * @returns {ILedger}
*/ */
static fromTransactions(transactions: IAccountTransaction[]): Ledger { static fromTransactions(transactions: AccountTransaction[]): Ledger {
const entries = Ledger.mappingTransactions(transactions); const entries = Ledger.mappingTransactions(transactions);
return new Ledger(entries); return new Ledger(entries);
} }

View File

@@ -24,7 +24,7 @@ export class LedgerContactsBalanceStorage {
) {} ) {}
/** /**
* * Saves the contacts balance.
* @param {ILedger} ledger * @param {ILedger} ledger
* @param {Knex.Transaction} trx * @param {Knex.Transaction} trx
* @returns {Promise<void>} * @returns {Promise<void>}
@@ -48,7 +48,7 @@ export class LedgerContactsBalanceStorage {
}; };
/** /**
* * Saves the contact balance.
* @param {ISaleContactsBalanceQueuePayload} task * @param {ISaleContactsBalanceQueuePayload} task
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
@@ -67,7 +67,6 @@ export class LedgerContactsBalanceStorage {
* @returns {Promise<(entry: ILedgerEntry) => boolean>} * @returns {Promise<(entry: ILedgerEntry) => boolean>}
*/ */
private filterARAPLedgerEntris = async ( private filterARAPLedgerEntris = async (
tenantId: number,
trx?: Knex.Transaction, trx?: Knex.Transaction,
): Promise<(entry: ILedgerEntry) => boolean> => { ): Promise<(entry: ILedgerEntry) => boolean> => {
const ARAPAccounts = await this.accountModel const ARAPAccounts = await this.accountModel
@@ -91,14 +90,11 @@ export class LedgerContactsBalanceStorage {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
private saveContactBalance = async ( private saveContactBalance = async (
tenantId: number,
ledger: ILedger, ledger: ILedger,
contactId: number, contactId: number,
trx?: Knex.Transaction, trx?: Knex.Transaction,
): Promise<void> => { ): Promise<void> => {
const contact = await this.contactModel.query(trx).findById(contactId); const contact = await this.contactModel.query(trx).findById(contactId);
// Retrieves the given tenant metadata.
const tenant = await this.tenancyContext.getTenant(true); const tenant = await this.tenancyContext.getTenant(true);
// Detarmines whether the contact has foreign currency. // Detarmines whether the contact has foreign currency.
@@ -106,10 +102,7 @@ export class LedgerContactsBalanceStorage {
contact.currencyCode !== tenant?.metadata.baseCurrency; contact.currencyCode !== tenant?.metadata.baseCurrency;
// Filters the ledger base on the given contact id. // Filters the ledger base on the given contact id.
const filterARAPLedgerEntris = await this.filterARAPLedgerEntris( const filterARAPLedgerEntris = await this.filterARAPLedgerEntris(trx);
tenantId,
trx,
);
const contactLedger = ledger const contactLedger = ledger
// Filter entries only that have contact id. // Filter entries only that have contact id.
.whereContactId(contactId) .whereContactId(contactId)
@@ -122,27 +115,25 @@ export class LedgerContactsBalanceStorage {
.getForeignClosingBalance() .getForeignClosingBalance()
: contactLedger.getClosingBalance(); : contactLedger.getClosingBalance();
await this.changeContactBalance(tenantId, contactId, closingBalance, trx); await this.changeContactBalance(contactId, closingBalance, trx);
}; };
/** /**
* * Changes the contact receiable/payable balance.
* @param {number} tenantId * @param {number} contactId - The contact ID.
* @param {number} contactId * @param {number} change - The change amount.
* @param {number} change * @returns {Promise<void>}
* @returns
*/ */
private changeContactBalance = ( private changeContactBalance = (
tenantId: number,
contactId: number, contactId: number,
change: number, change: number,
trx?: Knex.Transaction, trx?: Knex.Transaction,
) => { ) => {
return this.contactModel.changeAmount( // return this.contactModel.changeAmount(
{ id: contactId }, // { id: contactId },
'balance', // 'balance',
change, // change,
trx, // trx,
); // );
}; };
} }

View File

@@ -1,17 +1,26 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { ILedger } from './types/Ledger.types'; import { ILedger } from './types/Ledger.types';
import { LedgerContactsBalanceStorage } from './LedgerContactStorage.service'; import { LedgerContactsBalanceStorage } from './LedgerContactStorage.service';
import { LedegrAccountsStorage } from './LedgetAccountStorage.service'; import { LedegrAccountsStorage } from './LedgetAccountStorage.service';
import { LedgerEntriesStorageService } from './LedgerEntriesStorage.service'; import { LedgerEntriesStorageService } from './LedgerEntriesStorage.service';
import { AccountTransaction } from '../Accounts/models/AccountTransaction.model';
import { Ledger } from './Ledger'; import { Ledger } from './Ledger';
import { Injectable } from '@nestjs/common';
@Injectable() @Injectable()
export class LedgerStorageService { export class LedgerStorageService {
/**
* @param {LedgerContactsBalanceStorage} ledgerContactsBalance - Ledger contacts balance storage.
* @param {LedegrAccountsStorage} ledgerAccountsBalance - Ledger accounts balance storage.
* @param {LedgerEntriesStorageService} ledgerEntriesService - Ledger entries storage service.
*/
constructor( constructor(
private ledgerContactsBalance: LedgerContactsBalanceStorage, private ledgerContactsBalance: LedgerContactsBalanceStorage,
private ledgerAccountsBalance: LedegrAccountsStorage, private ledgerAccountsBalance: LedegrAccountsStorage,
private ledgerEntriesService: LedgerEntriesStorageService, private ledgerEntriesService: LedgerEntriesStorageService,
@Inject(AccountTransaction.name)
private accountTransactionModel: typeof AccountTransaction,
) {} ) {}
/** /**
@@ -43,10 +52,7 @@ export class LedgerStorageService {
* @param {Knex.Transaction} trx * @param {Knex.Transaction} trx
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public delete = async ( public delete = async (ledger: ILedger, trx?: Knex.Transaction) => {
ledger: ILedger,
trx?: Knex.Transaction,
) => {
const tasks = [ const tasks = [
// Deletes the ledger entries. // Deletes the ledger entries.
this.ledgerEntriesService.deleteEntries(ledger, trx), this.ledgerEntriesService.deleteEntries(ledger, trx),
@@ -61,9 +67,10 @@ export class LedgerStorageService {
}; };
/** /**
* @param {number | number[]} referenceId * Deletes the ledger entries by the given reference.
* @param {string | string[]} referenceType * @param {number | number[]} referenceId - The reference ID.
* @param {Knex.Transaction} trx * @param {string | string[]} referenceType - The reference type.
* @param {Knex.Transaction} trx - The knex transaction.
*/ */
public deleteByReference = async ( public deleteByReference = async (
referenceId: number | number[], referenceId: number | number[],
@@ -71,15 +78,15 @@ export class LedgerStorageService {
trx?: Knex.Transaction, trx?: Knex.Transaction,
) => { ) => {
// Retrieves the transactions of the given reference. // Retrieves the transactions of the given reference.
const transactions = const transactions = await this.accountTransactionModel
await transactionsRepository.getTransactionsByReference( .query(trx)
referenceId, .modify('filterByReference', referenceId, referenceType)
referenceType, .withGraphFetched('account');
);
// Creates a new ledger from transaction and reverse the entries. // Creates a new ledger from transaction and reverse the entries.
const reversedLedger = Ledger.fromTransactions(transactions).reverse(); const reversedLedger = Ledger.fromTransactions(transactions).reverse();
// Deletes and reverts the balances. // Deletes and reverts the balances.
await this.delete(tenantId, reversedLedger, trx); await this.delete(reversedLedger, trx);
}; };
} }

View File

@@ -8,6 +8,7 @@ import {
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Account } from '../Accounts/models/Account.model'; import { Account } from '../Accounts/models/Account.model';
import { AccountRepository } from '../Accounts/repositories/Account.repository'; import { AccountRepository } from '../Accounts/repositories/Account.repository';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
@Injectable() @Injectable()
export class LedegrAccountsStorage { export class LedegrAccountsStorage {
@@ -16,10 +17,12 @@ export class LedegrAccountsStorage {
* @param {AccountRepository} accountRepository - * @param {AccountRepository} accountRepository -
*/ */
constructor( constructor(
private tenancyContext: TenancyContext,
@Inject(Account.name) @Inject(Account.name)
private accountModel: typeof Account, private accountModel: typeof Account,
@Inject(AccountRepository.name) @Inject(AccountRepository)
private accountRepository: AccountRepository, private accountRepository: AccountRepository,
) {} ) {}
@@ -43,7 +46,7 @@ export class LedegrAccountsStorage {
}; };
/** /**
* * Finds the dependant accounts ids.
* @param {number[]} accountsIds * @param {number[]} accountsIds
* @returns {number[]} * @returns {number[]}
*/ */
@@ -60,9 +63,9 @@ export class LedegrAccountsStorage {
/** /**
* Atomic mutation for accounts balances. * Atomic mutation for accounts balances.
* @param {number} tenantId * @param {number} tenantId
* @param {ILedger} ledger * @param {ILedger} ledger
* @param {Knex.Transaction} trx - * @param {Knex.Transaction} trx -
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public saveAccountsBalance = async ( public saveAccountsBalance = async (
@@ -95,9 +98,9 @@ export class LedegrAccountsStorage {
private saveAccountBalanceTask = async ( private saveAccountBalanceTask = async (
task: ISaveAccountsBalanceQueuePayload, task: ISaveAccountsBalanceQueuePayload,
): Promise<void> => { ): Promise<void> => {
const { tenantId, ledger, accountId, trx } = task; const { ledger, accountId, trx } = task;
await this.saveAccountBalanceFromLedger(tenantId, ledger, accountId, trx); await this.saveAccountBalanceFromLedger(ledger, accountId, trx);
}; };
/** /**
@@ -109,7 +112,6 @@ export class LedegrAccountsStorage {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
private saveAccountBalanceFromLedger = async ( private saveAccountBalanceFromLedger = async (
tenantId: number,
ledger: ILedger, ledger: ILedger,
accountId: number, accountId: number,
trx?: Knex.Transaction, trx?: Knex.Transaction,
@@ -120,10 +122,11 @@ export class LedegrAccountsStorage {
const accountLedger = ledger.whereAccountId(accountId); const accountLedger = ledger.whereAccountId(accountId);
// Retrieves the given tenant metadata. // Retrieves the given tenant metadata.
const tenantMeta = await TenantMetadata.query().findOne({ tenantId }); const tenant = await this.tenancyContext.getTenant(true);
// Detarmines whether the account has foreign currency. // Detarmines whether the account has foreign currency.
const isAccountForeign = account.currencyCode !== tenantMeta.baseCurrency; const isAccountForeign =
account.currencyCode !== tenant.metadata?.baseCurrency;
// Calculates the closing foreign balance by the given currency if account was has // Calculates the closing foreign balance by the given currency if account was has
// foreign currency otherwise get closing balance. // foreign currency otherwise get closing balance.
@@ -133,7 +136,7 @@ export class LedegrAccountsStorage {
.getForeignClosingBalance() .getForeignClosingBalance()
: accountLedger.getClosingBalance(); : accountLedger.getClosingBalance();
await this.saveAccountBalance(tenantId, accountId, closingBalance, trx); await this.saveAccountBalance(accountId, closingBalance, trx);
}; };
/** /**
@@ -156,11 +159,11 @@ export class LedegrAccountsStorage {
.whereNull('amount') .whereNull('amount')
.patch({ amount: 0 }); .patch({ amount: 0 });
await this.accountModel.changeAmount( // await this.accountModel.changeAmount(
{ id: accountId }, // { id: accountId },
'amount', // 'amount',
change, // change,
trx, // trx,
); // );
}; };
} }

View File

@@ -1,8 +1,9 @@
import { IAccountTransaction, ILedgerEntry } from '@/interfaces'; import { AccountTransaction } from "../Accounts/models/AccountTransaction.model";
import { ILedgerEntry } from "./types/Ledger.types";
export const transformLedgerEntryToTransaction = ( export const transformLedgerEntryToTransaction = (
entry: ILedgerEntry entry: ILedgerEntry
): IAccountTransaction => { ): Partial<AccountTransaction> => {
return { return {
date: entry.date, date: entry.date,
@@ -33,7 +34,7 @@ export const transformLedgerEntryToTransaction = (
itemId: entry.itemId, itemId: entry.itemId,
projectId: entry.projectId, projectId: entry.projectId,
costable: entry.costable, // costable: entry.costable,
taxRateId: entry.taxRateId, taxRateId: entry.taxRateId,
taxRate: entry.taxRate, taxRate: entry.taxRate,

View File

@@ -12,9 +12,12 @@ import { BranchesModule } from '../Branches/Branches.module';
import { ManualJournalsController } from './ManualJournals.controller'; import { ManualJournalsController } from './ManualJournals.controller';
import { ManualJournalsApplication } from './ManualJournalsApplication.service'; import { ManualJournalsApplication } from './ManualJournalsApplication.service';
import { GetManualJournal } from './queries/GetManualJournal.service'; import { GetManualJournal } from './queries/GetManualJournal.service';
import { ManualJournalWriteGLSubscriber } from './commands/ManualJournalGLEntriesSubscriber';
import { ManualJournalGLEntries } from './commands/ManualJournalGLEntries';
import { LedgerModule } from '../Ledger/Ledger.module';
@Module({ @Module({
imports: [BranchesModule], imports: [BranchesModule, LedgerModule],
controllers: [ManualJournalsController], controllers: [ManualJournalsController],
providers: [ providers: [
TenancyContext, TenancyContext,
@@ -28,7 +31,9 @@ import { GetManualJournal } from './queries/GetManualJournal.service';
ManualJournalBranchesDTOTransformer, ManualJournalBranchesDTOTransformer,
AutoIncrementOrdersService, AutoIncrementOrdersService,
ManualJournalsApplication, ManualJournalsApplication,
GetManualJournal GetManualJournal,
ManualJournalGLEntries,
ManualJournalWriteGLSubscriber
], ],
}) })
export class ManualJournalsModule {} export class ManualJournalsModule {}

View File

@@ -0,0 +1,80 @@
import { Ledger } from '@/modules/Ledger/Ledger';
import { ManualJournal } from '../models/ManualJournal';
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
import { ManualJournalEntry } from '../models/ManualJournalEntry';
export class ManualJournalGL {
manualJournal: ManualJournal;
constructor(manualJournal: ManualJournal) {
this.manualJournal = manualJournal;
}
/**
* Retrieves the ledger of the given manual journal.
* @param {ManualJournal} manualJournal - The manual journal.
* @returns {Ledger}
*/
public getManualJournalGLedger = () => {
const entries = this.getManualJournalGLEntries();
return new Ledger(entries);
};
/**
* Retrieves the common entry details of the manual journal
* @param {IManualJournal} manualJournal - The manual journal.
* @returns {Partial<ILedgerEntry>}
*/
public get manualJournalCommonEntry() {
return {
transactionNumber: this.manualJournal.journalNumber,
referenceNumber: this.manualJournal.reference,
createdAt: this.manualJournal.createdAt,
date: this.manualJournal.date,
currencyCode: this.manualJournal.currencyCode,
exchangeRate: this.manualJournal.exchangeRate,
transactionType: 'Journal',
transactionId: this.manualJournal.id,
userId: this.manualJournal.userId,
};
}
/**
* Retrieves the ledger entry of the given manual journal and
* its associated entry.
* @param {IManualJournal} manualJournal - The manual journal.
* @param {IManualJournalEntry} entry - The manual journal entry.
* @returns {ILedgerEntry}
*/
public getManualJournalEntry(entry: ManualJournalEntry): ILedgerEntry {
const commonEntry = this.manualJournalCommonEntry;
return {
...commonEntry,
debit: entry.debit,
credit: entry.credit,
accountId: entry.accountId,
contactId: entry.contactId,
note: entry.note,
index: entry.index,
accountNormal: entry.account.accountNormal,
branchId: entry.branchId,
projectId: entry.projectId,
};
}
/**
* Retrieves the ledger entries of the given manual journal.
* @param {IManualJournal} manualJournal - The manual journal.
* @returns {ILedgerEntry[]}
*/
public getManualJournalGLEntries = (): ILedgerEntry[] => {
return this.manualJournal.entries.map(this.getManualJournalEntry).flat();
};
}

View File

@@ -1,161 +1,72 @@
// import { Service, Inject } from 'typedi'; import { Knex } from 'knex';
// import * as R from 'ramda'; import { Inject, Injectable } from '@nestjs/common';
// import { import { LedgerStorageService } from '@/modules/Ledger/LedgerStorage.service';
// IManualJournal, import { ManualJournal } from '../models/ManualJournal';
// IManualJournalEntry, import { ManualJournalGL } from './ManualJournalGL';
// ILedgerEntry,
// } from '@/interfaces';
// import { Knex } from 'knex';
// import Ledger from '@/services/Accounting/Ledger';
// import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
// import HasTenancyService from '@/services/Tenancy/TenancyService';
// @Service() @Injectable()
// export class ManualJournalGLEntries { export class ManualJournalGLEntries {
// @Inject() /**
// private ledgerStorage: LedgerStorageService; * @param {typeof ManualJournal} manualJournalModel - The manual journal model.
* @param {LedgerStorageService} ledgerStorage - The ledger storage service.
*/
constructor(
@Inject(ManualJournal.name)
private readonly manualJournalModel: typeof ManualJournal,
private readonly ledgerStorage: LedgerStorageService,
) {}
// @Inject() /**
// private tenancy: HasTenancyService; * Create manual journal GL entries.
* @param {number} manualJournalId - The manual journal ID.
* @param {Knex.Transaction} trx - The knex transaction.
*/
public createManualJournalGLEntries = async (
manualJournalId: number,
trx?: Knex.Transaction,
) => {
// Retrieves the given manual journal with associated entries.
const manualJournal = await this.manualJournalModel
.query(trx)
.findById(manualJournalId)
.withGraphFetched('entries.account');
// /** // Retrieves the ledger entries of the given manual journal.
// * Create manual journal GL entries. const ledger = new ManualJournalGL(manualJournal).getManualJournalGLedger();
// * @param {number} tenantId
// * @param {number} manualJournalId
// * @param {Knex.Transaction} trx
// */
// public createManualJournalGLEntries = async (
// tenantId: number,
// manualJournalId: number,
// trx?: Knex.Transaction
// ) => {
// const { ManualJournal } = this.tenancy.models(tenantId);
// // Retrieves the given manual journal with associated entries. // Commits the given ledger on the storage.
// const manualJournal = await ManualJournal.query(trx) await this.ledgerStorage.commit(ledger, trx);
// .findById(manualJournalId) };
// .withGraphFetched('entries.account');
// // Retrieves the ledger entries of the given manual journal. /**
// const ledger = this.getManualJournalGLedger(manualJournal); * Edits manual journal GL entries.
* @param {number} manualJournalId - The manual journal ID.
* @param {Knex.Transaction} trx - The knex transaction.
*/
public editManualJournalGLEntries = async (
manualJournalId: number,
trx?: Knex.Transaction,
) => {
// Reverts the manual journal GL entries.
await this.revertManualJournalGLEntries(manualJournalId, trx);
// // Commits the given ledger on the storage. // Write the manual journal GL entries.
// await this.ledgerStorage.commit(tenantId, ledger, trx); await this.createManualJournalGLEntries(manualJournalId, trx);
// }; };
// /** /**
// * Edits manual journal GL entries. * Deletes the manual journal GL entries.
// * @param {number} tenantId * @param {number} manualJournalId - The manual journal ID.
// * @param {number} manualJournalId * @param {Knex.Transaction} trx - The knex transaction.
// * @param {Knex.Transaction} trx */
// */ public revertManualJournalGLEntries = async (
// public editManualJournalGLEntries = async ( manualJournalId: number,
// tenantId: number, trx?: Knex.Transaction,
// manualJournalId: number, ): Promise<void> => {
// trx?: Knex.Transaction return this.ledgerStorage.deleteByReference(
// ) => { manualJournalId,
// // Reverts the manual journal GL entries. 'Journal',
// await this.revertManualJournalGLEntries(tenantId, manualJournalId, trx); trx,
);
// // Write the manual journal GL entries. };
// await this.createManualJournalGLEntries(tenantId, manualJournalId, trx); }
// };
// /**
// * Deletes the manual journal GL entries.
// * @param {number} tenantId
// * @param {number} manualJournalId
// * @param {Knex.Transaction} trx
// */
// public revertManualJournalGLEntries = async (
// tenantId: number,
// manualJournalId: number,
// trx?: Knex.Transaction
// ): Promise<void> => {
// return this.ledgerStorage.deleteByReference(
// tenantId,
// manualJournalId,
// 'Journal',
// trx
// );
// };
// /**
// * Retrieves the ledger of the given manual journal.
// * @param {IManualJournal} manualJournal
// * @returns {Ledger}
// */
// private getManualJournalGLedger = (manualJournal: IManualJournal) => {
// const entries = this.getManualJournalGLEntries(manualJournal);
// return new Ledger(entries);
// };
// /**
// * Retrieves the common entry details of the manual journal
// * @param {IManualJournal} manualJournal
// * @returns {Partial<ILedgerEntry>}
// */
// private getManualJournalCommonEntry = (
// manualJournal: IManualJournal
// ): Partial<ILedgerEntry> => {
// return {
// transactionNumber: manualJournal.journalNumber,
// referenceNumber: manualJournal.reference,
// createdAt: manualJournal.createdAt,
// date: manualJournal.date,
// currencyCode: manualJournal.currencyCode,
// exchangeRate: manualJournal.exchangeRate,
// transactionType: 'Journal',
// transactionId: manualJournal.id,
// userId: manualJournal.userId,
// };
// };
// /**
// * Retrieves the ledger entry of the given manual journal and
// * its associated entry.
// * @param {IManualJournal} manualJournal -
// * @param {IManualJournalEntry} entry -
// * @returns {ILedgerEntry}
// */
// private getManualJournalEntry = R.curry(
// (
// manualJournal: IManualJournal,
// entry: IManualJournalEntry
// ): ILedgerEntry => {
// const commonEntry = this.getManualJournalCommonEntry(manualJournal);
// return {
// ...commonEntry,
// debit: entry.debit,
// credit: entry.credit,
// accountId: entry.accountId,
// contactId: entry.contactId,
// note: entry.note,
// index: entry.index,
// accountNormal: entry.account.accountNormal,
// branchId: entry.branchId,
// projectId: entry.projectId,
// };
// }
// );
// /**
// * Retrieves the ledger of the given manual journal.
// * @param {IManualJournal} manualJournal
// * @returns {ILedgerEntry[]}
// */
// private getManualJournalGLEntries = (
// manualJournal: IManualJournal
// ): ILedgerEntry[] => {
// const transformEntry = this.getManualJournalEntry(manualJournal);
// return manualJournal.entries.map(transformEntry).flat();
// };
// }

View File

@@ -1,131 +1,102 @@
// import { Inject } from 'typedi'; import { OnEvent } from '@nestjs/event-emitter';
// import { EventSubscriber } from 'event-dispatch'; import { Injectable } from '@nestjs/common';
// import { import {
// IManualJournalEventCreatedPayload, IManualJournalEventCreatedPayload,
// IManualJournalEventEditedPayload, IManualJournalEventEditedPayload,
// IManualJournalEventPublishedPayload, IManualJournalEventPublishedPayload,
// IManualJournalEventDeletedPayload, IManualJournalEventDeletedPayload,
// } from '@/interfaces'; } from '../types/ManualJournals.types';
// import events from '@/subscribers/events'; import { ManualJournalGLEntries } from './ManualJournalGLEntries';
// import { ManualJournalGLEntries } from './ManualJournalGLEntries'; import { AutoIncrementManualJournal } from './AutoIncrementManualJournal.service';
// import { AutoIncrementManualJournal } from './AutoIncrementManualJournal.service'; import { events } from '@/common/events/events';
// @EventSubscriber() @Injectable()
// export class ManualJournalWriteGLSubscriber { export class ManualJournalWriteGLSubscriber {
// @Inject() /**
// private manualJournalGLEntries: ManualJournalGLEntries; * @param {ManualJournalGLEntries} manualJournalGLEntries - The manual journal GL entries service.
* @param {AutoIncrementManualJournal} manualJournalAutoIncrement - The manual journal auto increment service.
*/
constructor(
private manualJournalGLEntries: ManualJournalGLEntries,
private manualJournalAutoIncrement: AutoIncrementManualJournal,
) {}
// @Inject() /**
// private manualJournalAutoIncrement: AutoIncrementManualJournal; * Handle manual journal created event.
* @param {IManualJournalEventCreatedPayload} payload -
* @returns {Promise<void>}
*/
@OnEvent(events.manualJournals.onCreated)
public async handleWriteJournalEntriesOnCreated({
manualJournal,
trx,
}: IManualJournalEventCreatedPayload) {
// Ingore writing manual journal journal entries in case was not published.
if (!manualJournal.publishedAt) return;
// /** await this.manualJournalGLEntries.createManualJournalGLEntries(
// * Attaches events with handlers. manualJournal.id,
// * @param bus trx,
// */ );
// public attach(bus) { }
// bus.subscribe(
// events.manualJournals.onCreated,
// this.handleWriteJournalEntriesOnCreated
// );
// bus.subscribe(
// events.manualJournals.onCreated,
// this.handleJournalNumberIncrement
// );
// bus.subscribe(
// events.manualJournals.onEdited,
// this.handleRewriteJournalEntriesOnEdited
// );
// bus.subscribe(
// events.manualJournals.onPublished,
// this.handleWriteJournalEntriesOnPublished
// );
// bus.subscribe(
// events.manualJournals.onDeleted,
// this.handleRevertJournalEntries
// );
// }
// /** /**
// * Handle manual journal created event. * Handles the manual journal next number increment once the journal be created.
// * @param {IManualJournalEventCreatedPayload} payload - * @param {IManualJournalEventCreatedPayload} payload -
// * @returns {Promise<void>} * @return {Promise<void>}
// */ */
// private handleWriteJournalEntriesOnCreated = async ({ @OnEvent(events.manualJournals.onCreated)
// tenantId, public async handleJournalNumberIncrement({}: IManualJournalEventCreatedPayload) {
// manualJournal, await this.manualJournalAutoIncrement.incrementNextJournalNumber();
// trx, }
// }: IManualJournalEventCreatedPayload) => {
// // Ingore writing manual journal journal entries in case was not published.
// if (!manualJournal.publishedAt) return;
// await this.manualJournalGLEntries.createManualJournalGLEntries( /**
// tenantId, * Handle manual journal edited event.
// manualJournal.id, * @param {IManualJournalEventEditedPayload}
// trx * @return {Promise<void>}
// ); */
// }; @OnEvent(events.manualJournals.onEdited)
public async handleRewriteJournalEntriesOnEdited({
manualJournal,
oldManualJournal,
trx,
}: IManualJournalEventEditedPayload) {
if (manualJournal.publishedAt) {
await this.manualJournalGLEntries.editManualJournalGLEntries(
manualJournal.id,
trx,
);
}
}
// /** /**
// * Handles the manual journal next number increment once the journal be created. * Handles writing journal entries once the manula journal publish.
// * @param {IManualJournalEventCreatedPayload} payload - * @param {IManualJournalEventPublishedPayload} payload -
// * @return {Promise<void>} * @return {Promise<void>}
// */ */
// private handleJournalNumberIncrement = async ({ @OnEvent(events.manualJournals.onPublished)
// tenantId, public async handleWriteJournalEntriesOnPublished({
// }: IManualJournalEventCreatedPayload) => { manualJournal,
// await this.manualJournalAutoIncrement.incrementNextJournalNumber(tenantId); trx,
// }; }: IManualJournalEventPublishedPayload) {
await this.manualJournalGLEntries.createManualJournalGLEntries(
manualJournal.id,
trx,
);
}
// /** /**
// * Handle manual journal edited event. * Handle manual journal deleted event.
// * @param {IManualJournalEventEditedPayload} * @param {IManualJournalEventDeletedPayload} payload -
// * @return {Promise<void>} */
// */ @OnEvent(events.manualJournals.onDeleted)
// private handleRewriteJournalEntriesOnEdited = async ({ public async handleRevertJournalEntries({
// tenantId, manualJournalId,
// manualJournal, trx,
// oldManualJournal, }: IManualJournalEventDeletedPayload) {
// trx, await this.manualJournalGLEntries.revertManualJournalGLEntries(
// }: IManualJournalEventEditedPayload) => { manualJournalId,
// if (manualJournal.publishedAt) { trx,
// await this.manualJournalGLEntries.editManualJournalGLEntries( );
// tenantId, }
// manualJournal.id, }
// trx
// );
// }
// };
// /**
// * Handles writing journal entries once the manula journal publish.
// * @param {IManualJournalEventPublishedPayload} payload -
// * @return {Promise<void>}
// */
// private handleWriteJournalEntriesOnPublished = async ({
// tenantId,
// manualJournal,
// trx,
// }: IManualJournalEventPublishedPayload) => {
// await this.manualJournalGLEntries.createManualJournalGLEntries(
// tenantId,
// manualJournal.id,
// trx
// );
// };
// /**
// * Handle manual journal deleted event.
// * @param {IManualJournalEventDeletedPayload} payload -
// */
// private handleRevertJournalEntries = async ({
// tenantId,
// manualJournalId,
// trx,
// }: IManualJournalEventDeletedPayload) => {
// await this.manualJournalGLEntries.revertManualJournalGLEntries(
// tenantId,
// manualJournalId,
// trx
// );
// };
// }

View File

@@ -1,5 +1,8 @@
import { Model } from 'objection'; import { Model } from 'objection';
import { BaseModel } from '@/models/Model'; import { BaseModel } from '@/models/Model';
import { Account } from '@/modules/Accounts/models/Account.model';
import { Contact } from '@/modules/Contacts/models/Contact';
import { Branch } from '@/modules/Branches/models/Branch.model';
export class ManualJournalEntry extends BaseModel { export class ManualJournalEntry extends BaseModel {
index: number; index: number;
@@ -12,6 +15,10 @@ export class ManualJournalEntry extends BaseModel {
branchId!: number; branchId!: number;
projectId?: number; projectId?: number;
contact?: Contact;
account?: Account;
branch?: Branch;
/** /**
* Table name. * Table name.
*/ */

View File

@@ -0,0 +1,181 @@
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
import { PaymentReceived } from '../models/PaymentReceived';
import { sumBy } from 'lodash';
import { AccountNormal } from '@/interfaces/Account';
import { Ledger } from '@/modules/Ledger/Ledger';
export class PaymentReceivedGL {
private readonly paymentReceived: PaymentReceived;
private ARAccountId: number;
private exchangeGainOrLossAccountId: number;
private baseCurrencyCode: string;
/**
* Constructor method.
* @param {PaymentReceived} paymentReceived - Payment received.
*/
constructor(paymentReceived: PaymentReceived) {
this.paymentReceived = paymentReceived;
}
setARAccountId(ARAccountId: number) {
this.ARAccountId = ARAccountId;
return this;
}
setExchangeGainOrLossAccountId(exchangeGainOrLossAccountId: number) {
this.exchangeGainOrLossAccountId = exchangeGainOrLossAccountId;
return this;
}
setBaseCurrencyCode(baseCurrencyCode: string) {
this.baseCurrencyCode = baseCurrencyCode;
return this;
}
/**
* Calculates the payment total exchange gain/loss.
* @param {IBillPayment} paymentReceive - Payment receive with entries.
* @returns {number}
*/
private paymentExGainOrLoss = (): number => {
return sumBy(this.paymentReceived.entries, (entry) => {
const paymentLocalAmount =
entry.paymentAmount * this.paymentReceived.exchangeRate;
const invoicePayment = entry.paymentAmount * entry.invoice.exchangeRate;
return paymentLocalAmount - invoicePayment;
});
};
/**
* Retrieves the common entry of payment receive.
*/
private get paymentReceiveCommonEntry() {
return {
debit: 0,
credit: 0,
currencyCode: this.paymentReceived.currencyCode,
exchangeRate: this.paymentReceived.exchangeRate,
transactionId: this.paymentReceived.id,
transactionType: 'PaymentReceive',
transactionNumber: this.paymentReceived.paymentReceiveNo,
referenceNumber: this.paymentReceived.referenceNo,
date: this.paymentReceived.paymentDate,
userId: this.paymentReceived.userId,
createdAt: this.paymentReceived.createdAt,
branchId: this.paymentReceived.branchId,
};
}
/**
* Retrieves the payment exchange gain/loss entry.
* @param {IPaymentReceived} paymentReceive -
* @param {number} ARAccountId -
* @param {number} exchangeGainOrLossAccountId -
* @param {string} baseCurrencyCode -
* @returns {ILedgerEntry[]}
*/
private get paymentExchangeGainLossEntry(): ILedgerEntry[] {
const commonJournal = this.paymentReceiveCommonEntry;
const gainOrLoss = this.paymentExGainOrLoss();
const absGainOrLoss = Math.abs(gainOrLoss);
return gainOrLoss
? [
{
...commonJournal,
currencyCode: this.baseCurrencyCode,
exchangeRate: 1,
debit: gainOrLoss > 0 ? absGainOrLoss : 0,
credit: gainOrLoss < 0 ? absGainOrLoss : 0,
accountId: this.ARAccountId,
contactId: this.paymentReceived.customerId,
index: 3,
accountNormal: AccountNormal.CREDIT,
},
{
...commonJournal,
currencyCode: this.baseCurrencyCode,
exchangeRate: 1,
credit: gainOrLoss > 0 ? absGainOrLoss : 0,
debit: gainOrLoss < 0 ? absGainOrLoss : 0,
accountId: this.exchangeGainOrLossAccountId,
index: 3,
accountNormal: AccountNormal.DEBIT,
},
]
: [];
}
/**
* Retrieves the payment deposit GL entry.
* @param {IPaymentReceived} paymentReceive
* @returns {ILedgerEntry}
*/
private get paymentDepositGLEntry(): ILedgerEntry {
const commonJournal = this.paymentReceiveCommonEntry;
return {
...commonJournal,
debit: this.paymentReceived.localAmount,
accountId: this.paymentReceived.depositAccountId,
index: 2,
accountNormal: AccountNormal.DEBIT,
};
}
/**
* Retrieves the payment receivable entry.
* @param {IPaymentReceived} paymentReceive
* @param {number} ARAccountId
* @returns {ILedgerEntry}
*/
private get paymentReceivableEntry(): ILedgerEntry {
const commonJournal = this.paymentReceiveCommonEntry;
return {
...commonJournal,
credit: this.paymentReceived.localAmount,
contactId: this.paymentReceived.customerId,
accountId: this.ARAccountId,
index: 1,
accountNormal: AccountNormal.DEBIT,
};
}
/**
* Records payment receive journal transactions.
*
* Invoice payment journals.
* --------
* - Account receivable -> Debit
* - Payment account [current asset] -> Credit
* @returns {Promise<ILedgerEntry>}
*/
public GLEntries(): ILedgerEntry[] {
// Retrieve the payment deposit entry.
const paymentDepositEntry = this.paymentDepositGLEntry;
// Retrieves the A/R entry.
const receivableEntry = this.paymentReceivableEntry;
// Exchange gain/loss entries.
const gainLossEntries = this.paymentExchangeGainLossEntry;
return [paymentDepositEntry, receivableEntry, ...gainLossEntries];
}
/**
* Retrieves the payment receive ledger.
* @returns {Ledger}
*/
public getLedger = (): Ledger => {
return new Ledger(this.GLEntries());
};
}

View File

@@ -1,299 +1,110 @@
// import { Service, Inject } from 'typedi'; import { Knex } from 'knex';
// import { sumBy } from 'lodash'; import { PaymentReceivedGL } from './PaymentReceivedGL';
// import { Knex } from 'knex'; import { PaymentReceived } from '../models/PaymentReceived';
// import Ledger from '@/services/Accounting/Ledger'; import { LedgerStorageService } from '@/modules/Ledger/LedgerStorage.service';
// import TenancyService from '@/services/Tenancy/TenancyService'; import { Ledger } from '@/modules/Ledger/Ledger';
// import { import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository';
// IPaymentReceived, import { Injectable } from '@nestjs/common';
// ILedgerEntry, import { Inject } from '@nestjs/common';
// AccountNormal, import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
// IPaymentReceiveGLCommonEntry,
// } from '@/interfaces';
// import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
// import { TenantMetadata } from '@/system/models';
// @Service() @Injectable()
// export class PaymentReceivedGLEntries { export class PaymentReceivedGLEntries {
// @Inject() constructor(
// private tenancy: TenancyService; private readonly ledgerStorage: LedgerStorageService,
private readonly accountRepository: AccountRepository,
private readonly tenancyContext: TenancyContext,
// @Inject() @Inject(PaymentReceived.name)
// private ledgerStorage: LedgerStorageService; private readonly paymentReceivedModel: typeof PaymentReceived,
) {}
// /** /**
// * Writes payment GL entries to the storage. * Writes payment GL entries to the storage.
// * @param {number} tenantId * @param {number} paymentReceiveId - Payment received id.
// * @param {number} paymentReceiveId * @param {Knex.Transaction} trx - Knex transaction.
// * @param {Knex.Transaction} trx * @returns {Promise<void>}
// * @returns {Promise<void>} */
// */ public writePaymentGLEntries = async (
// public writePaymentGLEntries = async ( paymentReceiveId: number,
// tenantId: number, trx?: Knex.Transaction
// paymentReceiveId: number, ): Promise<void> => {
// trx?: Knex.Transaction // Retrieves the given tenant metadata.
// ): Promise<void> => { const tenantMeta = await this.tenancyContext.getTenantMetadata();
// const { PaymentReceive } = this.tenancy.models(tenantId);
// // Retrieves the given tenant metadata. // Retrieves the payment receive with associated entries.
// const tenantMeta = await TenantMetadata.query().findOne({ tenantId }); const paymentReceive = await this.paymentReceivedModel
.query(trx)
.findById(paymentReceiveId)
.withGraphFetched('entries.invoice');
// // Retrieves the payment receive with associated entries. // Retrives the payment receive ledger.
// const paymentReceive = await PaymentReceive.query(trx) const ledger = await this.getPaymentReceiveGLedger(
// .findById(paymentReceiveId) paymentReceive,
// .withGraphFetched('entries.invoice'); tenantMeta.baseCurrency,
);
// Commit the ledger entries to the storage.
await this.ledgerStorage.commit(ledger, trx);
};
// // Retrives the payment receive ledger. /**
// const ledger = await this.getPaymentReceiveGLedger( * Reverts the given payment receive GL entries.
// tenantId, * @param {number} paymentReceiveId - Payment received id.
// paymentReceive, * @param {Knex.Transaction} trx - Knex transaction.
// tenantMeta.baseCurrency, */
// trx public revertPaymentGLEntries = async (
// ); paymentReceiveId: number,
// // Commit the ledger entries to the storage. trx?: Knex.Transaction
// await this.ledgerStorage.commit(tenantId, ledger, trx); ) => {
// }; await this.ledgerStorage.deleteByReference(
paymentReceiveId,
'PaymentReceive',
trx
);
};
// /** /**
// * Reverts the given payment receive GL entries. * Rewrites the given payment receive GL entries.
// * @param {number} tenantId * @param {number} paymentReceiveId - Payment received id.
// * @param {number} paymentReceiveId * @param {Knex.Transaction} trx - Knex transaction.
// * @param {Knex.Transaction} trx */
// */ public rewritePaymentGLEntries = async (
// public revertPaymentGLEntries = async ( paymentReceiveId: number,
// tenantId: number, trx?: Knex.Transaction
// paymentReceiveId: number, ) => {
// trx?: Knex.Transaction // Reverts the payment GL entries.
// ) => { await this.revertPaymentGLEntries(paymentReceiveId, trx);
// await this.ledgerStorage.deleteByReference(
// tenantId,
// paymentReceiveId,
// 'PaymentReceive',
// trx
// );
// };
// /** // Writes the payment GL entries.
// * Rewrites the given payment receive GL entries. await this.writePaymentGLEntries(paymentReceiveId, trx);
// * @param {number} tenantId };
// * @param {number} paymentReceiveId
// * @param {Knex.Transaction} trx
// */
// public rewritePaymentGLEntries = async (
// tenantId: number,
// paymentReceiveId: number,
// trx?: Knex.Transaction
// ) => {
// // Reverts the payment GL entries.
// await this.revertPaymentGLEntries(tenantId, paymentReceiveId, trx);
// // Writes the payment GL entries. /**
// await this.writePaymentGLEntries(tenantId, paymentReceiveId, trx); * Retrieves the payment receive general ledger.
// }; * @param {IPaymentReceived} paymentReceive - Payment received.
* @param {string} baseCurrencyCode - Base currency code.
* @param {Knex.Transaction} trx - Knex transaction.
* @returns {Ledger}
*/
public getPaymentReceiveGLedger = async (
paymentReceive: PaymentReceived,
baseCurrencyCode: string,
): Promise<Ledger> => {
// Retrieve the A/R account of the given currency.
const receivableAccount =
await this.accountRepository.findOrCreateAccountReceivable(
paymentReceive.currencyCode
);
// Exchange gain/loss account.
const exGainLossAccount = await this.accountRepository.findBySlug(
'exchange-grain-loss'
);
const paymentReceivedGL = new PaymentReceivedGL(paymentReceive)
.setARAccountId(receivableAccount.id)
.setExchangeGainOrLossAccountId(exGainLossAccount.id)
.setBaseCurrencyCode(baseCurrencyCode);
// /** return paymentReceivedGL.getLedger();
// * Retrieves the payment receive general ledger. };
// * @param {number} tenantId -
// * @param {IPaymentReceived} paymentReceive -
// * @param {string} baseCurrencyCode -
// * @param {Knex.Transaction} trx -
// * @returns {Ledger}
// */
// public getPaymentReceiveGLedger = async (
// tenantId: number,
// paymentReceive: IPaymentReceived,
// baseCurrencyCode: string,
// trx?: Knex.Transaction
// ): Promise<Ledger> => {
// const { Account } = this.tenancy.models(tenantId);
// const { accountRepository } = this.tenancy.repositories(tenantId);
// // Retrieve the A/R account of the given currency. }
// const receivableAccount =
// await accountRepository.findOrCreateAccountReceivable(
// paymentReceive.currencyCode
// );
// // Exchange gain/loss account.
// const exGainLossAccount = await Account.query(trx).modify(
// 'findBySlug',
// 'exchange-grain-loss'
// );
// const ledgerEntries = this.getPaymentReceiveGLEntries(
// paymentReceive,
// receivableAccount.id,
// exGainLossAccount.id,
// baseCurrencyCode
// );
// return new Ledger(ledgerEntries);
// };
// /**
// * Calculates the payment total exchange gain/loss.
// * @param {IBillPayment} paymentReceive - Payment receive with entries.
// * @returns {number}
// */
// private getPaymentExGainOrLoss = (
// paymentReceive: IPaymentReceived
// ): number => {
// return sumBy(paymentReceive.entries, (entry) => {
// const paymentLocalAmount =
// entry.paymentAmount * paymentReceive.exchangeRate;
// const invoicePayment = entry.paymentAmount * entry.invoice.exchangeRate;
// return paymentLocalAmount - invoicePayment;
// });
// };
// /**
// * Retrieves the common entry of payment receive.
// * @param {IPaymentReceived} paymentReceive
// * @returns {}
// */
// private getPaymentReceiveCommonEntry = (
// paymentReceive: IPaymentReceived
// ): IPaymentReceiveGLCommonEntry => {
// return {
// debit: 0,
// credit: 0,
// currencyCode: paymentReceive.currencyCode,
// exchangeRate: paymentReceive.exchangeRate,
// transactionId: paymentReceive.id,
// transactionType: 'PaymentReceive',
// transactionNumber: paymentReceive.paymentReceiveNo,
// referenceNumber: paymentReceive.referenceNo,
// date: paymentReceive.paymentDate,
// userId: paymentReceive.userId,
// createdAt: paymentReceive.createdAt,
// branchId: paymentReceive.branchId,
// };
// };
// /**
// * Retrieves the payment exchange gain/loss entry.
// * @param {IPaymentReceived} paymentReceive -
// * @param {number} ARAccountId -
// * @param {number} exchangeGainOrLossAccountId -
// * @param {string} baseCurrencyCode -
// * @returns {ILedgerEntry[]}
// */
// private getPaymentExchangeGainLossEntry = (
// paymentReceive: IPaymentReceived,
// ARAccountId: number,
// exchangeGainOrLossAccountId: number,
// baseCurrencyCode: string
// ): ILedgerEntry[] => {
// const commonJournal = this.getPaymentReceiveCommonEntry(paymentReceive);
// const gainOrLoss = this.getPaymentExGainOrLoss(paymentReceive);
// const absGainOrLoss = Math.abs(gainOrLoss);
// return gainOrLoss
// ? [
// {
// ...commonJournal,
// currencyCode: baseCurrencyCode,
// exchangeRate: 1,
// debit: gainOrLoss > 0 ? absGainOrLoss : 0,
// credit: gainOrLoss < 0 ? absGainOrLoss : 0,
// accountId: ARAccountId,
// contactId: paymentReceive.customerId,
// index: 3,
// accountNormal: AccountNormal.CREDIT,
// },
// {
// ...commonJournal,
// currencyCode: baseCurrencyCode,
// exchangeRate: 1,
// credit: gainOrLoss > 0 ? absGainOrLoss : 0,
// debit: gainOrLoss < 0 ? absGainOrLoss : 0,
// accountId: exchangeGainOrLossAccountId,
// index: 3,
// accountNormal: AccountNormal.DEBIT,
// },
// ]
// : [];
// };
// /**
// * Retrieves the payment deposit GL entry.
// * @param {IPaymentReceived} paymentReceive
// * @returns {ILedgerEntry}
// */
// private getPaymentDepositGLEntry = (
// paymentReceive: IPaymentReceived
// ): ILedgerEntry => {
// const commonJournal = this.getPaymentReceiveCommonEntry(paymentReceive);
// return {
// ...commonJournal,
// debit: paymentReceive.localAmount,
// accountId: paymentReceive.depositAccountId,
// index: 2,
// accountNormal: AccountNormal.DEBIT,
// };
// };
// /**
// * Retrieves the payment receivable entry.
// * @param {IPaymentReceived} paymentReceive
// * @param {number} ARAccountId
// * @returns {ILedgerEntry}
// */
// private getPaymentReceivableEntry = (
// paymentReceive: IPaymentReceived,
// ARAccountId: number
// ): ILedgerEntry => {
// const commonJournal = this.getPaymentReceiveCommonEntry(paymentReceive);
// return {
// ...commonJournal,
// credit: paymentReceive.localAmount,
// contactId: paymentReceive.customerId,
// accountId: ARAccountId,
// index: 1,
// accountNormal: AccountNormal.DEBIT,
// };
// };
// /**
// * Records payment receive journal transactions.
// *
// * Invoice payment journals.
// * --------
// * - Account receivable -> Debit
// * - Payment account [current asset] -> Credit
// *
// * @param {number} tenantId
// * @param {IPaymentReceived} paymentRecieve - Payment receive model.
// * @param {number} ARAccountId - A/R account id.
// * @param {number} exGainOrLossAccountId - Exchange gain/loss account id.
// * @param {string} baseCurrency - Base currency code.
// * @returns {Promise<ILedgerEntry>}
// */
// public getPaymentReceiveGLEntries = (
// paymentReceive: IPaymentReceived,
// ARAccountId: number,
// exGainOrLossAccountId: number,
// baseCurrency: string
// ): ILedgerEntry[] => {
// // Retrieve the payment deposit entry.
// const paymentDepositEntry = this.getPaymentDepositGLEntry(paymentReceive);
// // Retrieves the A/R entry.
// const receivableEntry = this.getPaymentReceivableEntry(
// paymentReceive,
// ARAccountId
// );
// // Exchange gain/loss entries.
// const gainLossEntries = this.getPaymentExchangeGainLossEntry(
// paymentReceive,
// ARAccountId,
// exGainOrLossAccountId,
// baseCurrency
// );
// return [paymentDepositEntry, receivableEntry, ...gainLossEntries];
// };
// }

View File

@@ -1,276 +0,0 @@
// import * as R from 'ramda';
// import { Knex } from 'knex';
// import {
// ISaleInvoice,
// IItemEntry,
// ILedgerEntry,
// AccountNormal,
// ILedger,
// } from '@/interfaces';
// import { Service, Inject } from 'typedi';
// import Ledger from '@/services/Accounting/Ledger';
// import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
// import HasTenancyService from '@/services/Tenancy/TenancyService';
// import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
// @Service()
// export class SaleInvoiceGLEntries {
// @Inject()
// private tenancy: HasTenancyService;
// @Inject()
// private ledegrRepository: LedgerStorageService;
// @Inject()
// private itemsEntriesService: ItemsEntriesService;
// /**
// * Writes a sale invoice GL entries.
// * @param {number} tenantId - Tenant id.
// * @param {number} saleInvoiceId - Sale invoice id.
// * @param {Knex.Transaction} trx
// */
// public writeInvoiceGLEntries = async (
// tenantId: number,
// saleInvoiceId: number,
// trx?: Knex.Transaction
// ) => {
// const { SaleInvoice } = this.tenancy.models(tenantId);
// const { accountRepository } = this.tenancy.repositories(tenantId);
// const saleInvoice = await SaleInvoice.query(trx)
// .findById(saleInvoiceId)
// .withGraphFetched('entries.item');
// // Find or create the A/R account.
// const ARAccount = await accountRepository.findOrCreateAccountReceivable(
// saleInvoice.currencyCode, {}, trx
// );
// // Find or create tax payable account.
// const taxPayableAccount = await accountRepository.findOrCreateTaxPayable(
// {},
// trx
// );
// // Retrieves the ledger of the invoice.
// const ledger = this.getInvoiceGLedger(
// saleInvoice,
// ARAccount.id,
// taxPayableAccount.id
// );
// // Commits the ledger entries to the storage as UOW.
// await this.ledegrRepository.commit(tenantId, ledger, trx);
// };
// /**
// * Rewrites the given invoice GL entries.
// * @param {number} tenantId
// * @param {number} saleInvoiceId
// * @param {Knex.Transaction} trx
// */
// public rewritesInvoiceGLEntries = async (
// tenantId: number,
// saleInvoiceId: number,
// trx?: Knex.Transaction
// ) => {
// // Reverts the invoice GL entries.
// await this.revertInvoiceGLEntries(tenantId, saleInvoiceId, trx);
// // Writes the invoice GL entries.
// await this.writeInvoiceGLEntries(tenantId, saleInvoiceId, trx);
// };
// /**
// * Reverts the given invoice GL entries.
// * @param {number} tenantId
// * @param {number} saleInvoiceId
// * @param {Knex.Transaction} trx
// */
// public revertInvoiceGLEntries = async (
// tenantId: number,
// saleInvoiceId: number,
// trx?: Knex.Transaction
// ) => {
// await this.ledegrRepository.deleteByReference(
// tenantId,
// saleInvoiceId,
// 'SaleInvoice',
// trx
// );
// };
// /**
// * Retrieves the given invoice ledger.
// * @param {ISaleInvoice} saleInvoice
// * @param {number} ARAccountId
// * @returns {ILedger}
// */
// public getInvoiceGLedger = (
// saleInvoice: ISaleInvoice,
// ARAccountId: number,
// taxPayableAccountId: number
// ): ILedger => {
// const entries = this.getInvoiceGLEntries(
// saleInvoice,
// ARAccountId,
// taxPayableAccountId
// );
// return new Ledger(entries);
// };
// /**
// * Retrieves the invoice GL common entry.
// * @param {ISaleInvoice} saleInvoice
// * @returns {Partial<ILedgerEntry>}
// */
// private getInvoiceGLCommonEntry = (
// saleInvoice: ISaleInvoice
// ): Partial<ILedgerEntry> => ({
// credit: 0,
// debit: 0,
// currencyCode: saleInvoice.currencyCode,
// exchangeRate: saleInvoice.exchangeRate,
// transactionType: 'SaleInvoice',
// transactionId: saleInvoice.id,
// date: saleInvoice.invoiceDate,
// userId: saleInvoice.userId,
// transactionNumber: saleInvoice.invoiceNo,
// referenceNumber: saleInvoice.referenceNo,
// createdAt: saleInvoice.createdAt,
// indexGroup: 10,
// branchId: saleInvoice.branchId,
// });
// /**
// * Retrieve receivable entry of the given invoice.
// * @param {ISaleInvoice} saleInvoice
// * @param {number} ARAccountId
// * @returns {ILedgerEntry}
// */
// private getInvoiceReceivableEntry = (
// saleInvoice: ISaleInvoice,
// ARAccountId: number
// ): ILedgerEntry => {
// const commonEntry = this.getInvoiceGLCommonEntry(saleInvoice);
// return {
// ...commonEntry,
// debit: saleInvoice.totalLocal,
// accountId: ARAccountId,
// contactId: saleInvoice.customerId,
// accountNormal: AccountNormal.DEBIT,
// index: 1,
// } as ILedgerEntry;
// };
// /**
// * Retrieve item income entry of the given invoice.
// * @param {ISaleInvoice} saleInvoice -
// * @param {IItemEntry} entry -
// * @param {number} index -
// * @returns {ILedgerEntry}
// */
// private getInvoiceItemEntry = R.curry(
// (
// saleInvoice: ISaleInvoice,
// entry: IItemEntry,
// index: number
// ): ILedgerEntry => {
// const commonEntry = this.getInvoiceGLCommonEntry(saleInvoice);
// const localAmount = entry.amountExludingTax * saleInvoice.exchangeRate;
// return {
// ...commonEntry,
// credit: localAmount,
// accountId: entry.sellAccountId,
// note: entry.description,
// index: index + 2,
// itemId: entry.itemId,
// itemQuantity: entry.quantity,
// accountNormal: AccountNormal.CREDIT,
// projectId: entry.projectId || saleInvoice.projectId,
// taxRateId: entry.taxRateId,
// taxRate: entry.taxRate,
// };
// }
// );
// /**
// * Retreives the GL entry of tax payable.
// * @param {ISaleInvoice} saleInvoice -
// * @param {number} taxPayableAccountId -
// * @returns {ILedgerEntry}
// */
// private getInvoiceTaxEntry = R.curry(
// (
// saleInvoice: ISaleInvoice,
// taxPayableAccountId: number,
// entry: IItemEntry,
// index: number
// ): ILedgerEntry => {
// const commonEntry = this.getInvoiceGLCommonEntry(saleInvoice);
// return {
// ...commonEntry,
// credit: entry.taxAmount,
// accountId: taxPayableAccountId,
// index: index + 1,
// indexGroup: 30,
// accountNormal: AccountNormal.CREDIT,
// taxRateId: entry.taxRateId,
// taxRate: entry.taxRate,
// };
// }
// );
// /**
// * Retrieves the invoice tax GL entries.
// * @param {ISaleInvoice} saleInvoice
// * @param {number} taxPayableAccountId
// * @returns {ILedgerEntry[]}
// */
// private getInvoiceTaxEntries = (
// saleInvoice: ISaleInvoice,
// taxPayableAccountId: number
// ): ILedgerEntry[] => {
// // Retrieves the non-zero tax entries.
// const nonZeroTaxEntries = this.itemsEntriesService.getNonZeroEntries(
// saleInvoice.entries
// );
// const transformTaxEntry = this.getInvoiceTaxEntry(
// saleInvoice,
// taxPayableAccountId
// );
// // Transforms the non-zero tax entries to GL entries.
// return nonZeroTaxEntries.map(transformTaxEntry);
// };
// /**
// * Retrieves the invoice GL entries.
// * @param {ISaleInvoice} saleInvoice
// * @param {number} ARAccountId
// * @returns {ILedgerEntry[]}
// */
// public getInvoiceGLEntries = (
// saleInvoice: ISaleInvoice,
// ARAccountId: number,
// taxPayableAccountId: number
// ): ILedgerEntry[] => {
// const receivableEntry = this.getInvoiceReceivableEntry(
// saleInvoice,
// ARAccountId
// );
// const transformItemEntry = this.getInvoiceItemEntry(saleInvoice);
// const creditEntries = saleInvoice.entries.map(transformItemEntry);
// const taxEntries = this.getInvoiceTaxEntries(
// saleInvoice,
// taxPayableAccountId
// );
// return [receivableEntry, ...creditEntries, ...taxEntries];
// };
// }

View File

@@ -117,6 +117,7 @@ export interface ISaleInvoiceDeletePayload {
// tenantId: number; // tenantId: number;
oldSaleInvoice: SaleInvoice; oldSaleInvoice: SaleInvoice;
saleInvoiceId: number; saleInvoiceId: number;
trx: Knex.Transaction;
} }
export interface ISaleInvoiceDeletingPayload { export interface ISaleInvoiceDeletingPayload {

View File

@@ -31,6 +31,8 @@ import { BranchesModule } from '../Branches/Branches.module';
import { WarehousesModule } from '../Warehouses/Warehouses.module'; import { WarehousesModule } from '../Warehouses/Warehouses.module';
import { TaxRatesModule } from '../TaxRates/TaxRate.module'; import { TaxRatesModule } from '../TaxRates/TaxRate.module';
import { SaleInvoicesController } from './SaleInvoices.controller'; import { SaleInvoicesController } from './SaleInvoices.controller';
import { InvoiceGLEntriesSubscriber } from './subscribers/InvoiceGLEntriesSubscriber';
import { SaleInvoiceGLEntries } from './ledger/InvoiceGLEntries';
@Module({ @Module({
imports: [ imports: [
@@ -68,6 +70,8 @@ import { SaleInvoicesController } from './SaleInvoices.controller';
SaleInvoicePdfTemplate, SaleInvoicePdfTemplate,
WriteoffSaleInvoice, WriteoffSaleInvoice,
GetInvoicePaymentsService, GetInvoicePaymentsService,
SaleInvoiceGLEntries,
InvoiceGLEntriesSubscriber,
], ],
}) })
export class SaleInvoicesModule {} export class SaleInvoicesModule {}

View File

@@ -23,8 +23,8 @@ export class CreateSaleInvoice {
private readonly validators: CommandSaleInvoiceValidators, private readonly validators: CommandSaleInvoiceValidators,
private readonly transformerDTO: CommandSaleInvoiceDTOTransformer, private readonly transformerDTO: CommandSaleInvoiceDTOTransformer,
private readonly eventPublisher: EventEmitter2, private readonly eventPublisher: EventEmitter2,
private readonly uow: UnitOfWork,
private readonly commandEstimateValidators: SaleEstimateValidators, private readonly commandEstimateValidators: SaleEstimateValidators,
private readonly uow: UnitOfWork,
@Inject(SaleInvoice.name) @Inject(SaleInvoice.name)
private readonly saleInvoiceModel: typeof SaleInvoice, private readonly saleInvoiceModel: typeof SaleInvoice,

View File

@@ -0,0 +1,217 @@
import * as R from 'ramda';
import { ILedger } from '@/modules/Ledger/types/Ledger.types';
import { AccountNormal } from '@/modules/Accounts/Accounts.types';
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
import { ItemEntry } from '@/modules/Items/models/ItemEntry';
import { Ledger } from '@/modules/Ledger/Ledger';
import { SaleInvoice } from '../models/SaleInvoice';
export class InvoiceGL {
private saleInvoice: SaleInvoice;
private ARAccountId: number;
private taxPayableAccountId: number;
private discountAccountId: number;
private otherChargesAccountId: number;
/**
* Constructor method.
* @param {SaleInvoice} saleInvoice - Sale invoice.
*/
constructor(saleInvoice: SaleInvoice) {
this.saleInvoice = saleInvoice;
}
/**
* Set the receivable account id.
* @param {number} ARAccountId - Receivable account id.
*/
setARAccountId(ARAccountId: number) {
this.ARAccountId = ARAccountId;
}
/**
* Set the tax payable account id.
* @param {number} taxPayableAccountId - Tax payable account id.
*/
setTaxPayableAccountId(taxPayableAccountId: number) {
this.taxPayableAccountId = taxPayableAccountId;
}
/**
* Set the discount account id.
* @param {number} discountAccountId - Discount account id.
*/
setDiscountAccountId(discountAccountId: number) {
this.discountAccountId = discountAccountId;
}
/**
* Set the other charges account id.
* @param {number} otherChargesAccountId - Other charges account id.
*/
setOtherChargesAccountId(otherChargesAccountId: number) {
this.otherChargesAccountId = otherChargesAccountId;
}
/**
* Retrieves the invoice GL common entry.
*/
private get invoiceGLCommonEntry() {
return {
credit: 0,
debit: 0,
currencyCode: this.saleInvoice.currencyCode,
exchangeRate: this.saleInvoice.exchangeRate,
transactionType: 'SaleInvoice',
transactionId: this.saleInvoice.id,
date: this.saleInvoice.invoiceDate,
userId: this.saleInvoice.userId,
transactionNumber: this.saleInvoice.invoiceNo,
referenceNumber: this.saleInvoice.referenceNo,
createdAt: this.saleInvoice.createdAt,
indexGroup: 10,
branchId: this.saleInvoice.branchId,
};
}
/**
* Retrieve receivable entry of the invoice.
* @returns {ILedgerEntry}
*/
public get invoiceReceivableEntry(): ILedgerEntry {
const commonEntry = this.invoiceGLCommonEntry;
return {
...commonEntry,
debit: this.saleInvoice.totalLocal,
accountId: this.ARAccountId,
contactId: this.saleInvoice.customerId,
accountNormal: AccountNormal.DEBIT,
index: 1,
};
}
/**
* Retrieve item income entry of the invoice.
* @param {ItemEntry} entry - Item entry.
* @param {number} index - Index.
* @returns {ILedgerEntry}
*/
private getInvoiceItemEntry = R.curry(
(entry: ItemEntry, index: number): ILedgerEntry => {
const commonEntry = this.invoiceGLCommonEntry;
const localAmount =
entry.totalExcludingTax * this.saleInvoice.exchangeRate;
return {
...commonEntry,
credit: localAmount,
accountId: entry.sellAccountId,
note: entry.description,
index: index + 2,
itemId: entry.itemId,
itemQuantity: entry.quantity,
accountNormal: AccountNormal.CREDIT,
taxRateId: entry.taxRateId,
taxRate: entry.taxRate,
};
},
);
/**
* Retreives the GL entry of tax payable.
* @param {ItemEntry} entry - Item entry.
* @param {number} index - Index.
* @returns {ILedgerEntry}
*/
private getInvoiceTaxEntry(entry: ItemEntry, index: number): ILedgerEntry {
const commonEntry = this.invoiceGLCommonEntry;
return {
...commonEntry,
credit: entry.taxAmount,
accountId: this.taxPayableAccountId,
index: index + 1,
indexGroup: 30,
accountNormal: AccountNormal.CREDIT,
taxRateId: entry.taxRateId,
taxRate: entry.taxRate,
};
}
/**
* Retrieves the invoice discount GL entry.
* @returns {ILedgerEntry}
*/
private getInvoiceDiscountEntry = (): ILedgerEntry => {
const commonEntry = this.invoiceGLCommonEntry;
return {
...commonEntry,
debit: this.saleInvoice.discountAmountLocal,
accountId: this.discountAccountId,
accountNormal: AccountNormal.CREDIT,
index: 1,
} as ILedgerEntry;
};
/**
* Retrieves the invoice adjustment GL entry.
* @returns {ILedgerEntry}
*/
private getAdjustmentEntry = (): ILedgerEntry => {
const commonEntry = this.invoiceGLCommonEntry;
const adjustmentAmount = Math.abs(this.saleInvoice.adjustmentLocal);
return {
...commonEntry,
debit: this.saleInvoice.adjustmentLocal < 0 ? adjustmentAmount : 0,
credit: this.saleInvoice.adjustmentLocal > 0 ? adjustmentAmount : 0,
accountId: this.otherChargesAccountId,
accountNormal: AccountNormal.CREDIT,
index: 1,
};
};
/**
* Retrieves the invoice GL entries.
* @returns {ILedgerEntry[]}
*/
public getInvoiceGLEntries = (): ILedgerEntry[] => {
const receivableEntry = this.invoiceReceivableEntry;
const creditEntries = this.saleInvoice.entries.map(
this.getInvoiceItemEntry,
);
const taxEntries = this.saleInvoice.entries
.filter((entry) => entry.taxAmount > 0)
.map(this.getInvoiceTaxEntry);
const discountEntry = this.getInvoiceDiscountEntry();
const adjustmentEntry = this.getAdjustmentEntry();
return [
this.invoiceReceivableEntry,
...creditEntries,
...taxEntries,
discountEntry,
adjustmentEntry,
];
};
/**
* Retrieves the invoice ledger.
* @returns {ILedger}
*/
public getInvoiceLedger = (): ILedger => {
const entries = this.getInvoiceGLEntries();
return new Ledger(entries);
};
}

View File

@@ -0,0 +1,95 @@
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { LedgerStorageService } from '../../Ledger/LedgerStorage.service';
import { SaleInvoice } from '../models/SaleInvoice';
import { AccountRepository } from '../../Accounts/repositories/Account.repository';
import { InvoiceGL } from './InvoiceGL';
@Injectable()
export class SaleInvoiceGLEntries {
constructor(
private readonly ledegrRepository: LedgerStorageService,
private readonly accountRepository: AccountRepository,
@Inject(SaleInvoice.name)
private readonly saleInvoiceModel: typeof SaleInvoice,
) {}
/**
* Writes a sale invoice GL entries.
* @param {number} saleInvoiceId - Sale invoice id.
* @param {Knex.Transaction} trx
*/
public writeInvoiceGLEntries = async (
saleInvoiceId: number,
trx?: Knex.Transaction,
) => {
const saleInvoice = await this.saleInvoiceModel
.query(trx)
.findById(saleInvoiceId)
.withGraphFetched('entries.item');
// Find or create the A/R account.
const ARAccount =
await this.accountRepository.findOrCreateAccountReceivable(
saleInvoice.currencyCode,
{},
trx,
);
// Find or create tax payable account.
const taxPayableAccount =
await this.accountRepository.findOrCreateTaxPayable({}, trx);
// Find or create the discount expense account.
const discountAccount =
await this.accountRepository.findOrCreateDiscountAccount({}, trx);
// Find or create the other charges account.
const otherChargesAccount =
await this.accountRepository.findOrCreateOtherChargesAccount({}, trx);
// Retrieves the ledger of the invoice.
const invoiceGL = new InvoiceGL(saleInvoice);
invoiceGL.setARAccountId(ARAccount.id);
invoiceGL.setTaxPayableAccountId(taxPayableAccount.id);
invoiceGL.setDiscountAccountId(discountAccount.id);
invoiceGL.setOtherChargesAccountId(otherChargesAccount.id);
const ledger = invoiceGL.getInvoiceLedger();
// Commits the ledger entries to the storage as UOW.
await this.ledegrRepository.commit(ledger, trx);
};
/**
* Rewrites the given invoice GL entries.
* @param {number} tenantId
* @param {number} saleInvoiceId
* @param {Knex.Transaction} trx
*/
public rewritesInvoiceGLEntries = async (
saleInvoiceId: number,
trx?: Knex.Transaction,
) => {
// Reverts the invoice GL entries.
await this.revertInvoiceGLEntries(saleInvoiceId, trx);
// Writes the invoice GL entries.
await this.writeInvoiceGLEntries(saleInvoiceId, trx);
};
/**
* Reverts the given invoice GL entries.
* @param {number} saleInvoiceId - Sale invoice id.
* @param {Knex.Transaction} trx
*/
public revertInvoiceGLEntries = async (
saleInvoiceId: number,
trx?: Knex.Transaction,
) => {
await this.ledegrRepository.deleteByReference(
saleInvoiceId,
'SaleInvoice',
trx,
);
};
}

View File

@@ -39,6 +39,7 @@ export class SaleInvoice extends BaseModel {
public referenceNo: string; public referenceNo: string;
public pdfTemplateId: number; public pdfTemplateId: number;
public userId: number;
public branchId: number; public branchId: number;
public warehouseId: number; public warehouseId: number;

View File

@@ -0,0 +1,64 @@
import { Injectable } from '@nestjs/common';
import {
ISaleInvoiceCreatedPayload,
ISaleInvoiceDeletePayload,
ISaleInvoiceEditedPayload,
} from '../SaleInvoice.types';
import { OnEvent } from '@nestjs/event-emitter';
import { SaleInvoiceGLEntries } from '../ledger/InvoiceGLEntries';
import { events } from '@/common/events/events';
@Injectable()
export class InvoiceGLEntriesSubscriber {
constructor(public readonly saleInvoiceGLEntries: SaleInvoiceGLEntries) {}
/**
* Records journal entries of the non-inventory invoice.
* @param {ISaleInvoiceCreatedPayload} payload -
* @returns {Promise<void>}
*/
@OnEvent(events.saleInvoice.onCreated)
@OnEvent(events.saleInvoice.onDelivered)
public async handleWriteJournalEntriesOnInvoiceCreated({
saleInvoiceId,
saleInvoice,
trx,
}: ISaleInvoiceCreatedPayload) {
// Can't continue if the sale invoice is not delivered yet.
if (!saleInvoice.deliveredAt) return null;
await this.saleInvoiceGLEntries.writeInvoiceGLEntries(saleInvoiceId, trx);
}
/**
* Records journal entries of the non-inventory invoice.
* @param {ISaleInvoiceEditedPayload} payload -
* @returns {Promise<void>}
*/
@OnEvent(events.saleInvoice.onEdited)
public async handleRewriteJournalEntriesOnceInvoiceEdit({
saleInvoice,
trx,
}: ISaleInvoiceEditedPayload) {
// Can't continue if the sale invoice is not delivered yet.
if (!saleInvoice.deliveredAt) return null;
await this.saleInvoiceGLEntries.rewritesInvoiceGLEntries(
saleInvoice.id,
trx,
);
}
/**
* Handle reverting journal entries once sale invoice delete.
* @param {ISaleInvoiceDeletePayload} payload -
* @returns {Promise<void>}
*/
@OnEvent(events.saleInvoice.onDeleted)
public async handleRevertingInvoiceJournalEntriesOnDelete({
saleInvoiceId,
trx,
}: ISaleInvoiceDeletePayload) {
await this.saleInvoiceGLEntries.revertInvoiceGLEntries(saleInvoiceId, trx);
}
}

View File

@@ -20,6 +20,8 @@ import { SaleReceiptIncrement } from './commands/SaleReceiptIncrement.service';
import { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module'; import { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module';
import { AutoIncrementOrdersModule } from '../AutoIncrementOrders/AutoIncrementOrders.module'; import { AutoIncrementOrdersModule } from '../AutoIncrementOrders/AutoIncrementOrders.module';
import { SaleReceiptsController } from './SaleReceipts.controller'; import { SaleReceiptsController } from './SaleReceipts.controller';
import { SaleReceiptGLEntriesSubscriber } from './subscribers/SaleReceiptGLEntriesSubscriber';
import { SaleReceiptGLEntries } from './ledger/SaleReceiptGLEntries';
@Module({ @Module({
controllers: [SaleReceiptsController], controllers: [SaleReceiptsController],
@@ -46,6 +48,8 @@ import { SaleReceiptsController } from './SaleReceipts.controller';
SaleReceiptDTOTransformer, SaleReceiptDTOTransformer,
SaleReceiptBrandingTemplate, SaleReceiptBrandingTemplate,
SaleReceiptIncrement, SaleReceiptIncrement,
SaleReceiptGLEntries,
SaleReceiptGLEntriesSubscriber
], ],
}) })
export class SaleReceiptsModule {} export class SaleReceiptsModule {}

View File

@@ -1,184 +0,0 @@
// import { Knex } from 'knex';
// import { Service, Inject } from 'typedi';
// import * as R from 'ramda';
// import HasTenancyService from '@/services/Tenancy/TenancyService';
// import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
// import {
// AccountNormal,
// ILedgerEntry,
// ISaleReceipt,
// IItemEntry,
// } from '@/interfaces';
// import Ledger from '@/services/Accounting/Ledger';
// @Service()
// export class SaleReceiptGLEntries {
// @Inject()
// private tenancy: HasTenancyService;
// @Inject()
// private ledgerStorage: LedgerStorageService;
// /**
// * Creates income GL entries.
// * @param {number} tenantId
// * @param {number} saleReceiptId
// * @param {Knex.Transaction} trx
// */
// public writeIncomeGLEntries = async (
// tenantId: number,
// saleReceiptId: number,
// trx?: Knex.Transaction
// ): Promise<void> => {
// const { SaleReceipt } = this.tenancy.models(tenantId);
// const saleReceipt = await SaleReceipt.query(trx)
// .findById(saleReceiptId)
// .withGraphFetched('entries.item');
// // Retrieve the income entries ledger.
// const incomeLedger = this.getIncomeEntriesLedger(saleReceipt);
// // Commits the ledger entries to the storage.
// await this.ledgerStorage.commit(tenantId, incomeLedger, trx);
// };
// /**
// * Reverts the receipt GL entries.
// * @param {number} tenantId
// * @param {number} saleReceiptId
// * @param {Knex.Transaction} trx
// * @returns {Promise<void>}
// */
// public revertReceiptGLEntries = async (
// tenantId: number,
// saleReceiptId: number,
// trx?: Knex.Transaction
// ): Promise<void> => {
// await this.ledgerStorage.deleteByReference(
// tenantId,
// saleReceiptId,
// 'SaleReceipt',
// trx
// );
// };
// /**
// * Rewrites the receipt GL entries.
// * @param {number} tenantId
// * @param {number} saleReceiptId
// * @param {Knex.Transaction} trx
// * @returns {Promise<void>}
// */
// public rewriteReceiptGLEntries = async (
// tenantId: number,
// saleReceiptId: number,
// trx?: Knex.Transaction
// ): Promise<void> => {
// // Reverts the receipt GL entries.
// await this.revertReceiptGLEntries(tenantId, saleReceiptId, trx);
// // Writes the income GL entries.
// await this.writeIncomeGLEntries(tenantId, saleReceiptId, trx);
// };
// /**
// * Retrieves the income GL ledger.
// * @param {ISaleReceipt} saleReceipt
// * @returns {Ledger}
// */
// private getIncomeEntriesLedger = (saleReceipt: ISaleReceipt): Ledger => {
// const entries = this.getIncomeGLEntries(saleReceipt);
// return new Ledger(entries);
// };
// /**
// * Retireves the income GL common entry.
// * @param {ISaleReceipt} saleReceipt -
// */
// private getIncomeGLCommonEntry = (saleReceipt: ISaleReceipt) => {
// return {
// currencyCode: saleReceipt.currencyCode,
// exchangeRate: saleReceipt.exchangeRate,
// transactionType: 'SaleReceipt',
// transactionId: saleReceipt.id,
// date: saleReceipt.receiptDate,
// transactionNumber: saleReceipt.receiptNumber,
// referenceNumber: saleReceipt.referenceNo,
// createdAt: saleReceipt.createdAt,
// credit: 0,
// debit: 0,
// userId: saleReceipt.userId,
// branchId: saleReceipt.branchId,
// };
// };
// /**
// * Retrieve receipt income item GL entry.
// * @param {ISaleReceipt} saleReceipt -
// * @param {IItemEntry} entry -
// * @param {number} index -
// * @returns {ILedgerEntry}
// */
// private getReceiptIncomeItemEntry = R.curry(
// (
// saleReceipt: ISaleReceipt,
// entry: IItemEntry,
// index: number
// ): ILedgerEntry => {
// const commonEntry = this.getIncomeGLCommonEntry(saleReceipt);
// const itemIncome = entry.amount * saleReceipt.exchangeRate;
// return {
// ...commonEntry,
// credit: itemIncome,
// accountId: entry.item.sellAccountId,
// note: entry.description,
// index: index + 2,
// itemId: entry.itemId,
// itemQuantity: entry.quantity,
// accountNormal: AccountNormal.CREDIT,
// };
// }
// );
// /**
// * Retrieves the receipt deposit GL deposit entry.
// * @param {ISaleReceipt} saleReceipt
// * @returns {ILedgerEntry}
// */
// private getReceiptDepositEntry = (
// saleReceipt: ISaleReceipt
// ): ILedgerEntry => {
// const commonEntry = this.getIncomeGLCommonEntry(saleReceipt);
// return {
// ...commonEntry,
// debit: saleReceipt.localAmount,
// accountId: saleReceipt.depositAccountId,
// index: 1,
// accountNormal: AccountNormal.DEBIT,
// };
// };
// /**
// * Retrieves the income GL entries.
// * @param {ISaleReceipt} saleReceipt -
// * @returns {ILedgerEntry[]}
// */
// private getIncomeGLEntries = (saleReceipt: ISaleReceipt): ILedgerEntry[] => {
// const getItemEntry = this.getReceiptIncomeItemEntry(saleReceipt);
// const creditEntries = saleReceipt.entries.map(getItemEntry);
// const depositEntry = this.getReceiptDepositEntry(saleReceipt);
// return [depositEntry, ...creditEntries];
// };
// }

View File

@@ -0,0 +1,167 @@
import * as R from 'ramda';
import { ILedger } from '@/modules/Ledger/types/Ledger.types';
import { AccountNormal } from '@/modules/Accounts/Accounts.types';
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
import { Ledger } from '@/modules/Ledger/Ledger';
import { SaleReceipt } from '../models/SaleReceipt';
import { ItemEntry } from '@/modules/Items/models/ItemEntry';
export class SaleReceiptGL {
private saleReceipt: SaleReceipt;
private discountAccountId: number;
private otherChargesAccountId: number;
/**
* Constructor method.
* @param {SaleReceipt} saleReceipt - Sale receipt.
*/
constructor(saleReceipt: SaleReceipt) {
this.saleReceipt = saleReceipt;
}
/**
* Sets the discount account id.
* @param {number} discountAccountId - Discount account id.
*/
setDiscountAccountId(discountAccountId: number) {
this.discountAccountId = discountAccountId;
return this;
}
/**
* Sets the other charges account id.
* @param {number} otherChargesAccountId - Other charges account id.
*/
setOtherChargesAccountId(otherChargesAccountId: number) {
this.otherChargesAccountId = otherChargesAccountId;
return this;
}
/**
* Retrieves the income GL common entry.
*/
private getIncomeGLCommonEntry = () => {
return {
currencyCode: this.saleReceipt.currencyCode,
exchangeRate: this.saleReceipt.exchangeRate,
transactionType: 'SaleReceipt',
transactionId: this.saleReceipt.id,
date: this.saleReceipt.receiptDate,
transactionNumber: this.saleReceipt.receiptNumber,
referenceNumber: this.saleReceipt.referenceNo,
createdAt: this.saleReceipt.createdAt,
credit: 0,
debit: 0,
userId: this.saleReceipt.userId,
branchId: this.saleReceipt.branchId,
};
};
/**
* Retrieve receipt income item G/L entry.
* @param {ItemEntry} entry - Item entry.
* @param {number} index - Index.
* @returns {ILedgerEntry}
*/
private getReceiptIncomeItemEntry = R.curry(
(entry: ItemEntry, index: number): ILedgerEntry => {
const commonEntry = this.getIncomeGLCommonEntry();
const totalLocal =
entry.totalExcludingTax * this.saleReceipt.exchangeRate;
return {
...commonEntry,
credit: totalLocal,
accountId: entry.item.sellAccountId,
note: entry.description,
index: index + 2,
itemId: entry.itemId,
itemQuantity: entry.quantity,
accountNormal: AccountNormal.CREDIT,
};
},
);
/**
* Retrieves the receipt deposit GL deposit entry.
* @returns {ILedgerEntry}
*/
private getReceiptDepositEntry = (): ILedgerEntry => {
const commonEntry = this.getIncomeGLCommonEntry();
return {
...commonEntry,
debit: this.saleReceipt.totalLocal,
accountId: this.saleReceipt.depositAccountId,
index: 1,
accountNormal: AccountNormal.DEBIT,
};
};
/**
* Retrieves the discount GL entry.
* @returns {ILedgerEntry}
*/
private getDiscountEntry = (): ILedgerEntry => {
const commonEntry = this.getIncomeGLCommonEntry();
return {
...commonEntry,
debit: this.saleReceipt.discountAmountLocal,
accountId: this.discountAccountId,
index: 1,
accountNormal: AccountNormal.CREDIT,
};
};
/**
* Retrieves the adjustment GL entry.
* @returns {ILedgerEntry}
*/
private getAdjustmentEntry = (): ILedgerEntry => {
const commonEntry = this.getIncomeGLCommonEntry();
const adjustmentAmount = Math.abs(this.saleReceipt.adjustmentLocal);
return {
...commonEntry,
debit: this.saleReceipt.adjustmentLocal < 0 ? adjustmentAmount : 0,
credit: this.saleReceipt.adjustmentLocal > 0 ? adjustmentAmount : 0,
accountId: this.otherChargesAccountId,
accountNormal: AccountNormal.CREDIT,
index: 1,
};
};
/**
* Retrieves the income GL entries.
* @returns {ILedgerEntry[]}
*/
public getIncomeGLEntries = (): ILedgerEntry[] => {
const getItemEntry = this.getReceiptIncomeItemEntry;
const creditEntries = this.saleReceipt.entries.map((e, index) =>
getItemEntry(e, index),
);
const depositEntry = this.getReceiptDepositEntry();
const discountEntry = this.getDiscountEntry();
const adjustmentEntry = this.getAdjustmentEntry();
return [depositEntry, ...creditEntries, discountEntry, adjustmentEntry];
};
/**
* Retrieves the income GL ledger.
* @returns {ILedger}
*/
public getIncomeLedger = (): ILedger => {
const entries = this.getIncomeGLEntries();
return new Ledger(entries);
};
}

View File

@@ -0,0 +1,83 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { LedgerStorageService } from '@/modules/Ledger/LedgerStorage.service';
import { SaleReceipt } from '../models/SaleReceipt';
import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository';
import { SaleReceiptGL } from './SaleReceiptGL';
@Injectable()
export class SaleReceiptGLEntries {
constructor(
private readonly ledgerStorage: LedgerStorageService,
private readonly accountRepository: AccountRepository,
@Inject(SaleReceipt.name)
private readonly saleReceiptModel: typeof SaleReceipt,
) {}
/**
* Creates income GL entries.
* @param {number} saleReceiptId
* @param {Knex.Transaction} trx
*/
public writeIncomeGLEntries = async (
saleReceiptId: number,
trx?: Knex.Transaction
): Promise<void> => {
const saleReceipt = await this.saleReceiptModel.query(trx)
.findById(saleReceiptId)
.withGraphFetched('entries.item');
// Find or create the discount expense account.
const discountAccount = await this.accountRepository.findOrCreateDiscountAccount(
{},
trx
);
// Find or create the other charges account.
const otherChargesAccount =
await this.accountRepository.findOrCreateOtherChargesAccount({}, trx);
// Retrieves the income ledger.
const incomeLedger = new SaleReceiptGL(saleReceipt)
.setDiscountAccountId(discountAccount.id)
.setOtherChargesAccountId(otherChargesAccount.id)
.getIncomeLedger();
// Commits the ledger entries to the storage.
await this.ledgerStorage.commit(incomeLedger, trx);
};
/**
* Reverts the receipt GL entries.
* @param {number} saleReceiptId - Sale receipt id.
* @param {Knex.Transaction} trx
* @returns {Promise<void>}
*/
public revertReceiptGLEntries = async (
saleReceiptId: number,
trx?: Knex.Transaction
): Promise<void> => {
await this.ledgerStorage.deleteByReference(
saleReceiptId,
'SaleReceipt',
trx
);
};
/**
* Rewrites the receipt GL entries.
* @param {number} saleReceiptId
* @param {Knex.Transaction} trx
* @returns {Promise<void>}
*/
public rewriteReceiptGLEntries = async (
saleReceiptId: number,
trx?: Knex.Transaction
): Promise<void> => {
// Reverts the receipt GL entries.
await this.revertReceiptGLEntries(saleReceiptId, trx);
// Writes the income GL entries.
await this.writeIncomeGLEntries(saleReceiptId, trx);
};
}

View File

@@ -6,6 +6,11 @@ import { Model, mixin } from 'objection';
// import { DEFAULT_VIEWS } from '@/services/Sales/Receipts/constants'; // import { DEFAULT_VIEWS } from '@/services/Sales/Receipts/constants';
// import ModelSearchable from './ModelSearchable'; // import ModelSearchable from './ModelSearchable';
import { BaseModel } from '@/models/Model'; import { BaseModel } from '@/models/Model';
import { ItemEntry } from '@/modules/Items/models/ItemEntry';
import { Customer } from '@/modules/Customers/models/Customer';
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
import { Branch } from '@/modules/Branches/models/Branch.model';
import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model';
export class SaleReceipt extends BaseModel { export class SaleReceipt extends BaseModel {
amount: number; amount: number;
@@ -27,6 +32,12 @@ export class SaleReceipt extends BaseModel {
createdAt: Date; createdAt: Date;
updatedAt: Date | null; updatedAt: Date | null;
customer!: Customer;
entries!: ItemEntry[];
transactions!: AccountTransaction[];
branch!: Branch;
warehouse!: Warehouse;
/** /**
* Table name * Table name
*/ */

View File

@@ -0,0 +1,63 @@
import { Inject, Injectable } from '@nestjs/common';
import {
ISaleReceiptCreatedPayload,
ISaleReceiptEditedPayload,
ISaleReceiptEventDeletedPayload,
} from '../types/SaleReceipts.types';
import { SaleReceiptGLEntries } from '../ledger/SaleReceiptGLEntries';
import { OnEvent } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
@Injectable()
export class SaleReceiptGLEntriesSubscriber {
constructor(private readonly saleReceiptGLEntries: SaleReceiptGLEntries) {}
/**
* Handles writing sale receipt income journal entries once created.
* @param {ISaleReceiptCreatedPayload} payload -
*/
@OnEvent(events.saleReceipt.onCreated)
@OnEvent(events.saleReceipt.onClosed)
public async handleWriteReceiptIncomeJournalEntrieOnCreate({
saleReceiptId,
saleReceipt,
trx,
}: ISaleReceiptCreatedPayload) {
// Can't continue if the sale receipt is not closed yet.
if (!saleReceipt.closedAt) return null;
// Writes the sale receipt income journal entries.
await this.saleReceiptGLEntries.writeIncomeGLEntries(saleReceiptId, trx);
}
/**
* Handles sale receipt revert jouranl entries once be deleted.
* @param {ISaleReceiptEventDeletedPayload} payload -
*/
@OnEvent(events.saleReceipt.onDeleted)
public async handleRevertReceiptJournalEntriesOnDeleted({
saleReceiptId,
trx,
}: ISaleReceiptEventDeletedPayload) {
await this.saleReceiptGLEntries.revertReceiptGLEntries(saleReceiptId, trx);
}
/**
* Handles writing sale receipt income journal entries once be edited.
* @param {ISaleReceiptEditedPayload} payload -
*/
@OnEvent(events.saleReceipt.onEdited)
public async handleWriteReceiptIncomeJournalEntrieOnEdited({
saleReceipt,
trx,
}: ISaleReceiptEditedPayload) {
// Can't continue if the sale receipt is not closed yet.
if (!saleReceipt.closedAt) return null;
// Writes the sale receipt income journal entries.
await this.saleReceiptGLEntries.rewriteReceiptGLEntries(
saleReceipt.id,
trx,
);
}
}

View File

@@ -35,6 +35,12 @@ export class TenancyContext {
return query; return query;
} }
async getTenantMetadata() {
const tenant = await this.getTenant(true);
return tenant?.metadata;
}
/** /**
* Retrieves the current system user. * Retrieves the current system user.
* @returns {Promise<SystemUser>} * @returns {Promise<SystemUser>}

View File

@@ -0,0 +1,61 @@
import * as request from 'supertest';
import { faker } from '@faker-js/faker';
import { app } from './init-app-test';
const makeExpenseRequest = () => ({
exchangeRate: 1,
description: faker.lorem.sentence(),
paymentAccountId: 1000,
referenceNo: faker.string.alphanumeric(10),
publish: true,
paymentDate: faker.date.recent(),
categories: [
{
expenseAccountId: 1045,
amount: faker.number.float({ min: 10, max: 1000, precision: 0.01 }),
description: faker.lorem.sentence(),
},
],
// currencyCode: faker.finance.currencyCode(),
// userId: faker.number.int({ min: 1, max: 100 }),
// payeeId: faker.number.int({ min: 1, max: 100 }),
// branchId: faker.number.int({ min: 1, max: 100 }),
});
describe('Expenses (e2e)', () => {
it('/expenses (POST)', () => {
return request(app.getHttpServer())
.post('/expenses')
.set('organization-id', '4064541lv40nhca')
.send(makeExpenseRequest())
.expect(201);
});
it('/expenses/:id (GET)', async () => {
const response = await request(app.getHttpServer())
.post('/expenses')
.set('organization-id', '4064541lv40nhca')
.send(makeExpenseRequest());
const expenseId = response.body.id;
return request(app.getHttpServer())
.get(`/expenses/${expenseId}`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
it('/expenses/:id (DELETE)', async () => {
const response = await request(app.getHttpServer())
.post('/expenses')
.set('organization-id', '4064541lv40nhca')
.send(makeExpenseRequest());
const expenseId = response.body.id;
return request(app.getHttpServer())
.delete(`/expenses/${expenseId}`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
});

View File

@@ -1,4 +1,4 @@
import { INestApplication } from '@nestjs/common'; import { INestApplication, Logger } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { AppModule } from '../src/modules/App/App.module'; import { AppModule } from '../src/modules/App/App.module';
@@ -10,6 +10,8 @@ beforeAll(async () => {
}).compile(); }).compile();
app = moduleFixture.createNestApplication(); app = moduleFixture.createNestApplication();
app.useLogger(new Logger());
await app.init(); await app.init();
}); });