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,
GetAccountTransactionsService,
],
exports: [
AccountRepository
]
})
export class AccountsModule {}

View File

@@ -1,20 +1,37 @@
import { Model, raw } from 'objection';
import moment from 'moment';
import moment, { unitOfTime } from 'moment';
import { isEmpty, castArray } from 'lodash';
import { BaseModel } from '@/models/Model';
import { Account } from './Account.model';
// import { getTransactionTypeLabel } from '@/utils/transactions-types';
export class AccountTransaction extends BaseModel {
referenceType: string;
referenceId: number;
accountId: number;
contactId: number;
credit: number;
debit: number;
exchangeRate: number;
taxRate: number;
date: string;
date: Date | string;
transactionType: string;
currencyCode: 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
@@ -61,153 +78,184 @@ export class AccountTransaction extends BaseModel {
// return getTransactionTypeLabel(this.referenceType, this.transactionType);
// }
// /**
// * Model modifiers.
// */
// static get modifiers() {
// return {
// /**
// * Filters accounts by the given ids.
// * @param {Query} query
// * @param {number[]} accountsIds
// */
// filterAccounts(query, accountsIds) {
// if (Array.isArray(accountsIds) && accountsIds.length > 0) {
// 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);
/**
* Model modifiers.
*/
static get modifiers() {
return {
/**
* Filters accounts by the given ids.
* @param {Query} query
* @param {number[]} accountsIds
*/
filterAccounts(query, accountsIds) {
if (Array.isArray(accountsIds) && accountsIds.length > 0) {
query.whereIn('account_id', accountsIds);
}
},
// if (startDate) {
// query.where('date', '>=', fromDate);
// }
// if (endDate) {
// query.where('date', '<=', toDate);
// }
// },
// filterAmountRange(query, fromAmount, toAmount) {
// if (fromAmount) {
// query.andWhere((q) => {
// 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']);
/**
* Filters the transaction types.
* @param {Query} query
* @param {string[]} types
*/
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);
}
},
// query.sum('credit as credit');
// 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);
/**
* Filters the date range.
* @param {Query} query
* @param {moment.MomentInput} startDate
* @param {moment.MomentInput} endDate
* @param {unitOfTime.StartOf} type
*/
filterDateRange(
query,
startDate: moment.MomentInput,
endDate: moment.MomentInput,
type: unitOfTime.StartOf = 'day',
) {
const dateFormat = 'YYYY-MM-DD';
const fromDate = moment(startDate).startOf(type).format(dateFormat);
const toDate = moment(endDate).endOf(type).format(dateFormat);
// // Filter by customers.
// query.whereNot('contactId', null);
// query.whereIn('accountId', castArray(receivableAccounts));
if (startDate) {
query.where('date', '>=', fromDate);
}
if (endDate) {
query.where('date', '<=', toDate);
}
},
// 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];
/**
* Filters the amount range.
* @param {Query} query
* @param {number} fromAmount
* @param {number} toAmount
*/
filterAmountRange(query, fromAmount, toAmount) {
if (fromAmount) {
query.andWhere((q) => {
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.select(raw(`DATE_FORMAT(DATE, '${dateFormat}')`).as('date'));
// query.groupByRaw(`DATE_FORMAT(DATE, '${dateFormat}')`);
// },
query.sum('credit as credit');
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) {
// const formattedBranchesIds = castArray(branchesIds);
// Filter by customers.
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) {
// const formattedProjectsIds = castArray(projectsIds);
query.select(raw(`DATE_FORMAT(DATE, '${dateFormat}')`).as('date'));
query.groupByRaw(`DATE_FORMAT(DATE, '${dateFormat}')`);
},
// query.whereIn('projectId', formattedProjectsIds);
// },
// };
// }
filterByBranches(query, branchesIds) {
const formattedBranchesIds = castArray(branchesIds);
// /**
// * Relationship mapping.
// */
// static get relationMappings() {
// const Account = require('models/Account');
// const Contact = require('models/Contact');
query.whereIn('branchId', formattedBranchesIds);
},
// return {
// account: {
// relation: Model.BelongsToOneRelation,
// modelClass: Account.default,
// join: {
// from: 'accounts_transactions.accountId',
// to: 'accounts.id',
// },
// },
// contact: {
// relation: Model.BelongsToOneRelation,
// modelClass: Contact.default,
// join: {
// from: 'accounts_transactions.contactId',
// to: 'contacts.id',
// },
// },
// };
// }
filterByProjects(query, projectsIds) {
const formattedProjectsIds = castArray(projectsIds);
query.whereIn('projectId', formattedProjectsIds);
},
filterByReference(query, referenceId: number, referenceType: string) {
query.where('reference_id', referenceId);
query.where('reference_type', referenceType);
},
};
}
/**
* 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.

View File

@@ -3,19 +3,26 @@ import { Inject, Injectable, Scope } from '@nestjs/common';
import { TenantRepository } from '@/common/repository/TenantRepository';
import { TENANCY_DB_CONNECTION } from '@/modules/Tenancy/TenancyDB/TenancyDB.constants';
import { Account } from '../models/Account.model';
// import { TenantMetadata } from '@/modules/System/models/TenantMetadataModel';
// import { IAccount } from '../Accounts.types';
// import {
// PrepardExpenses,
// StripeClearingAccount,
// TaxPayableAccount,
// UnearnedRevenueAccount,
// } from '../Accounts.constants';
import { I18nService } from 'nestjs-i18n';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import {
PrepardExpenses,
StripeClearingAccount,
TaxPayableAccount,
UnearnedRevenueAccount,
} from '../Accounts.constants';
@Injectable({ scope: Scope.REQUEST })
export class AccountRepository extends TenantRepository {
@Inject(TENANCY_DB_CONNECTION)
private readonly tenantDBKnex: Knex;
constructor(
private readonly i18n: I18nService,
private readonly tenancyContext: TenancyContext,
@Inject(TENANCY_DB_CONNECTION)
private readonly tenantDBKnex: Knex,
) {
super();
}
/**
* Gets the repository's model.
@@ -107,185 +114,274 @@ export class AccountRepository extends TenantRepository {
return results;
}
// /**
// *
// * @param {string} currencyCode
// * @param extraAttrs
// * @param trx
// * @returns
// */
// findOrCreateAccountReceivable = async (
// currencyCode: string = '',
// extraAttrs = {},
// trx?: Knex.Transaction,
// ) => {
// let result = await this.model
// .query(trx)
// .onBuild((query) => {
// if (currencyCode) {
// query.where('currencyCode', currencyCode);
// }
// query.where('accountType', 'accounts-receivable');
// })
// .first();
/**
*
* @param {string} currencyCode
* @param extraAttrs
* @param trx
* @returns
*/
findOrCreateAccountReceivable = async (
currencyCode: string = '',
extraAttrs = {},
trx?: Knex.Transaction,
) => {
let result = await this.model
.query(trx)
.onBuild((query) => {
if (currencyCode) {
query.where('currencyCode', currencyCode);
}
query.where('accountType', 'accounts-receivable');
})
.first();
// if (!result) {
// result = await this.model.query(trx).insertAndFetch({
// name: this.i18n.__('account.accounts_receivable.currency', {
// currency: currencyCode,
// }),
// accountType: 'accounts-receivable',
// currencyCode,
// active: 1,
// ...extraAttrs,
// });
// }
// return result;
// };
if (!result) {
result = await this.model.query(trx).insertAndFetch({
name: this.i18n.t('account.accounts_receivable.currency', {
args: { currency: currencyCode },
}),
accountType: 'accounts-receivable',
currencyCode,
active: 1,
...extraAttrs,
});
}
return result;
};
// /**
// * Find or create tax payable account.
// * @param {Record<string, string>}extraAttrs
// * @param {Knex.Transaction} trx
// * @returns
// */
// async findOrCreateTaxPayable(
// extraAttrs: Record<string, string> = {},
// trx?: Knex.Transaction,
// ) {
// let result = await this.model
// .query(trx)
// .findOne({ slug: TaxPayableAccount.slug, ...extraAttrs });
/**
* Find or create tax payable account.
* @param {Record<string, string>}extraAttrs
* @param {Knex.Transaction} trx
* @returns
*/
async findOrCreateTaxPayable(
extraAttrs: Record<string, string> = {},
trx?: Knex.Transaction,
) {
let result = await this.model
.query(trx)
.findOne({ slug: TaxPayableAccount.slug, ...extraAttrs });
// if (!result) {
// result = await this.model.query(trx).insertAndFetch({
// ...TaxPayableAccount,
// ...extraAttrs,
// });
// }
// return result;
// }
if (!result) {
result = await this.model.query(trx).insertAndFetch({
...TaxPayableAccount,
...extraAttrs,
});
}
return result;
}
// findOrCreateAccountsPayable = async (
// currencyCode: string = '',
// extraAttrs = {},
// trx?: Knex.Transaction,
// ) => {
// let result = await this.model
// .query(trx)
// .onBuild((query) => {
// if (currencyCode) {
// query.where('currencyCode', currencyCode);
// }
// query.where('accountType', 'accounts-payable');
// })
// .first();
findOrCreateAccountsPayable = async (
currencyCode: string = '',
extraAttrs = {},
trx?: Knex.Transaction,
) => {
let result = await this.model
.query(trx)
.onBuild((query) => {
if (currencyCode) {
query.where('currencyCode', currencyCode);
}
query.where('accountType', 'accounts-payable');
})
.first();
// if (!result) {
// result = await this.model.query(trx).insertAndFetch({
// name: this.i18n.__('account.accounts_payable.currency', {
// currency: currencyCode,
// }),
// accountType: 'accounts-payable',
// currencyCode,
// active: 1,
// ...extraAttrs,
// });
// }
// return result;
// };
if (!result) {
result = await this.model.query(trx).insertAndFetch({
name: this.i18n.t('account.accounts_payable.currency', {
args: { currency: currencyCode },
}),
accountType: 'accounts-payable',
currencyCode,
active: 1,
...extraAttrs,
});
}
return result;
};
// /**
// * Finds or creates the unearned revenue.
// * @param {Record<string, string>} extraAttrs
// * @param {Knex.Transaction} trx
// * @returns
// */
// public async findOrCreateUnearnedRevenue(
// extraAttrs: Record<string, string> = {},
// trx?: Knex.Transaction,
// ) {
// // Retrieves the given tenant metadata.
// const tenantMeta = await TenantMetadata.query().findOne({
// tenantId: this.tenantId,
// });
// const _extraAttrs = {
// currencyCode: tenantMeta.baseCurrency,
// ...extraAttrs,
// };
// let result = await this.model
// .query(trx)
// .findOne({ slug: UnearnedRevenueAccount.slug, ..._extraAttrs });
/**
* Finds or creates the unearned revenue.
* @param {Record<string, string>} extraAttrs
* @param {Knex.Transaction} trx
* @returns
*/
public async findOrCreateUnearnedRevenue(
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: UnearnedRevenueAccount.slug, ..._extraAttrs });
// if (!result) {
// result = await this.model.query(trx).insertAndFetch({
// ...UnearnedRevenueAccount,
// ..._extraAttrs,
// });
// }
// return result;
// }
if (!result) {
result = await this.model.query(trx).insertAndFetch({
...UnearnedRevenueAccount,
..._extraAttrs,
});
}
return result;
}
// /**
// * Finds or creates the prepard expenses account.
// * @param {Record<string, string>} extraAttrs
// * @param {Knex.Transaction} trx
// * @returns
// */
// public async findOrCreatePrepardExpenses(
// extraAttrs: Record<string, string> = {},
// trx?: Knex.Transaction,
// ) {
// // Retrieves the given tenant metadata.
// const tenantMeta = await TenantMetadata.query().findOne({
// tenantId: this.tenantId,
// });
// const _extraAttrs = {
// currencyCode: tenantMeta.baseCurrency,
// ...extraAttrs,
// };
/**
* Finds or creates the prepard expenses account.
* @param {Record<string, string>} extraAttrs
* @param {Knex.Transaction} trx
* @returns
*/
public async findOrCreatePrepardExpenses(
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: PrepardExpenses.slug, ..._extraAttrs });
let result = await this.model
.query(trx)
.findOne({ slug: PrepardExpenses.slug, ..._extraAttrs });
// if (!result) {
// result = await this.model.query(trx).insertAndFetch({
// ...PrepardExpenses,
// ..._extraAttrs,
// });
// }
// return result;
// }
if (!result) {
result = await this.model.query(trx).insertAndFetch({
...PrepardExpenses,
..._extraAttrs,
});
}
return result;
}
// /**
// * Finds or creates the stripe clearing account.
// * @param {Record<string, string>} extraAttrs
// * @param {Knex.Transaction} trx
// * @returns
// */
// public async findOrCreateStripeClearing(
// extraAttrs: Record<string, string> = {},
// trx?: Knex.Transaction,
// ) {
// // Retrieves the given tenant metadata.
// const tenantMeta = await TenantMetadata.query().findOne({
// tenantId: this.tenantId,
// });
// const _extraAttrs = {
// currencyCode: tenantMeta.baseCurrency,
// ...extraAttrs,
// };
// let result = await this.model
// .query(trx)
// .findOne({ slug: StripeClearingAccount.slug, ..._extraAttrs });
/**
* Finds or creates the stripe clearing account.
* @param {Record<string, string>} extraAttrs
* @param {Knex.Transaction} trx
* @returns
*/
public async findOrCreateStripeClearing(
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: StripeClearingAccount.slug, ..._extraAttrs });
// if (!result) {
// result = await this.model.query(trx).insertAndFetch({
// ...StripeClearingAccount,
// ..._extraAttrs,
// });
// }
// return result;
// }
if (!result) {
result = await this.model.query(trx).insertAndFetch({
...StripeClearingAccount,
..._extraAttrs,
});
}
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 { BillPaymentsModule } from '../BillPayments/BillPayments.module';
import { PaymentsReceivedModule } from '../PaymentReceived/PaymentsReceived.module';
import { LedgerModule } from '../Ledger/Ledger.module';
@Module({
imports: [
@@ -130,6 +131,7 @@ import { PaymentsReceivedModule } from '../PaymentReceived/PaymentsReceived.modu
CreditNoteRefundsModule,
BillPaymentsModule,
PaymentsReceivedModule,
LedgerModule,
],
controllers: [AppController],
providers: [

View File

@@ -17,6 +17,7 @@ import { ItemEntriesTaxTransactions } from '../TaxRates/ItemEntriesTaxTransactio
import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { BillsController } from './Bills.controller';
import { BillLandedCostsModule } from '../BillLandedCosts/BillLandedCosts.module';
import { BillGLEntriesSubscriber } from './subscribers/BillGLEntriesSubscriber';
@Module({
imports: [BillLandedCostsModule],
@@ -36,7 +37,8 @@ import { BillLandedCostsModule } from '../BillLandedCosts/BillLandedCosts.module
DeleteBill,
BillDTOTransformer,
BillsValidators,
ItemsEntriesService
ItemsEntriesService,
BillGLEntriesSubscriber
],
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 { BaseModel } from '@/models/Model';
import { ItemEntry } from '@/modules/Items/models/ItemEntry';
import { BillLandedCost } from '@/modules/BillLandedCosts/models/BillLandedCost';
export class Bill extends BaseModel{
public amount: number;
@@ -34,12 +35,13 @@ export class Bill extends BaseModel{
public branchId: number;
public warehouseId: number;
public projectId: number;
public createdAt: Date;
public updatedAt: Date | null;
public entries?: ItemEntry[];
public locatedLandedCosts?: BillLandedCost[];
/**
* 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 { CreditNoteBrandingTemplate } from './queries/CreditNoteBrandingTemplate.service';
import { AutoIncrementOrdersModule } from '../AutoIncrementOrders/AutoIncrementOrders.module';
import CreditNoteGLEntries from './commands/CreditNoteGLEntries';
import CreditNoteGLEntriesSubscriber from './subscribers/CreditNoteGLEntriesSubscriber';
@Module({
imports: [
@@ -40,7 +42,9 @@ import { AutoIncrementOrdersModule } from '../AutoIncrementOrders/AutoIncrementO
CreditNoteAutoIncrementService,
GetCreditNoteState,
CreditNoteApplication,
CreditNoteBrandingTemplate
CreditNoteBrandingTemplate,
CreditNoteGLEntries,
CreditNoteGLEntriesSubscriber
],
exports: [
CreateCreditNoteService,

View File

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

View File

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

View File

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

View File

@@ -12,9 +12,12 @@ import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
import { ExpensesWriteGLSubscriber } from './subscribers/ExpenseGLEntries.subscriber';
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({
imports: [],
imports: [LedgerModule, BranchesModule],
controllers: [ExpensesController],
providers: [
CreateExpense,
@@ -29,6 +32,7 @@ import { ExpenseGLEntriesStorageService } from './subscribers/ExpenseGLEntriesSt
TransformerInjectable,
ExpensesWriteGLSubscriber,
ExpenseGLEntriesStorageService,
ExpenseGLEntriesService
],
})
export class ExpensesModule {}

View File

@@ -2,10 +2,6 @@ import { Knex } from 'knex';
import { Expense } from './models/Expense.model';
import { SystemUser } from '../System/models/SystemUser';
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 {
total: number;
@@ -23,55 +19,6 @@ export interface IExpensesFilter {
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 {
currencyCode: string;
exchangeRate?: number;
@@ -103,68 +50,25 @@ export interface IExpenseCategoryDTO {
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 {
trx: Knex.Transaction;
// tenantId: number;
expenseDTO: IExpenseCreateDTO;
}
export interface IExpenseEventEditingPayload {
// tenantId: number;
oldExpense: Expense;
expenseDTO: IExpenseEditDTO;
trx: Knex.Transaction;
}
export interface IExpenseCreatedPayload {
// tenantId: number;
expenseId: number;
// authorizedUser: ISystemUser;
expense: Expense;
expenseDTO: IExpenseCreateDTO;
trx?: Knex.Transaction;
}
export interface IExpenseEventEditPayload {
// tenantId: number;
expenseId: number;
expense: Expense;
expenseDTO: IExpenseEditDTO;
@@ -174,7 +78,6 @@ export interface IExpenseEventEditPayload {
}
export interface IExpenseEventDeletePayload {
// tenantId: number;
expenseId: number;
authorizedUser: SystemUser;
oldExpense: Expense;
@@ -183,11 +86,9 @@ export interface IExpenseEventDeletePayload {
export interface IExpenseDeletingPayload {
trx: Knex.Transaction;
// tenantId: number;
oldExpense: Expense;
}
export interface IExpenseEventPublishedPayload {
// tenantId: number;
expenseId: number;
oldExpense: Expense;
expense: Expense;
@@ -198,7 +99,6 @@ export interface IExpenseEventPublishedPayload {
export interface IExpensePublishingPayload {
trx: Knex.Transaction;
oldExpense: Expense;
// tenantId: number;
}
export enum ExpenseAction {
Create = 'Create',

View File

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

View File

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

View File

@@ -77,7 +77,7 @@ export class ExpenseGL {
index: index + 2,
projectId: category.projectId,
};
}
},
);
/**
@@ -88,8 +88,9 @@ export class ExpenseGL {
const getCategoryEntry = this.getExpenseGLCategoryEntry();
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];
};

View File

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

View File

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

View File

@@ -1,6 +1,9 @@
import moment from 'moment';
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 {
readonly entries: ILedgerEntry[];
@@ -225,7 +228,7 @@ export class Ledger implements ILedger {
* @param {IAccountTransaction[]} entries
* @returns {ILedgerEntry[]}
*/
static mappingTransactions(entries: IAccountTransaction[]): ILedgerEntry[] {
static mappingTransactions(entries: AccountTransaction[]): ILedgerEntry[] {
return entries.map(this.mapTransaction);
}
@@ -234,7 +237,7 @@ export class Ledger implements ILedger {
* @param {IAccountTransaction} entry
* @returns {ILedgerEntry}
*/
static mapTransaction(entry: IAccountTransaction): ILedgerEntry {
static mapTransaction(entry: AccountTransaction): ILedgerEntry {
return {
credit: defaultTo(entry.credit, 0),
debit: defaultTo(entry.debit, 0),
@@ -274,7 +277,7 @@ export class Ledger implements ILedger {
* @param {IAccountTransaction[]} transactions
* @returns {ILedger}
*/
static fromTransactions(transactions: IAccountTransaction[]): Ledger {
static fromTransactions(transactions: AccountTransaction[]): Ledger {
const entries = Ledger.mappingTransactions(transactions);
return new Ledger(entries);
}

View File

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

View File

@@ -1,17 +1,26 @@
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { ILedger } from './types/Ledger.types';
import { LedgerContactsBalanceStorage } from './LedgerContactStorage.service';
import { LedegrAccountsStorage } from './LedgetAccountStorage.service';
import { LedgerEntriesStorageService } from './LedgerEntriesStorage.service';
import { AccountTransaction } from '../Accounts/models/AccountTransaction.model';
import { Ledger } from './Ledger';
import { Injectable } from '@nestjs/common';
@Injectable()
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(
private ledgerContactsBalance: LedgerContactsBalanceStorage,
private ledgerAccountsBalance: LedegrAccountsStorage,
private ledgerEntriesService: LedgerEntriesStorageService,
@Inject(AccountTransaction.name)
private accountTransactionModel: typeof AccountTransaction,
) {}
/**
@@ -43,10 +52,7 @@ export class LedgerStorageService {
* @param {Knex.Transaction} trx
* @returns {Promise<void>}
*/
public delete = async (
ledger: ILedger,
trx?: Knex.Transaction,
) => {
public delete = async (ledger: ILedger, trx?: Knex.Transaction) => {
const tasks = [
// Deletes the ledger entries.
this.ledgerEntriesService.deleteEntries(ledger, trx),
@@ -61,9 +67,10 @@ export class LedgerStorageService {
};
/**
* @param {number | number[]} referenceId
* @param {string | string[]} referenceType
* @param {Knex.Transaction} trx
* Deletes the ledger entries by the given reference.
* @param {number | number[]} referenceId - The reference ID.
* @param {string | string[]} referenceType - The reference type.
* @param {Knex.Transaction} trx - The knex transaction.
*/
public deleteByReference = async (
referenceId: number | number[],
@@ -71,15 +78,15 @@ export class LedgerStorageService {
trx?: Knex.Transaction,
) => {
// Retrieves the transactions of the given reference.
const transactions =
await transactionsRepository.getTransactionsByReference(
referenceId,
referenceType,
);
const transactions = await this.accountTransactionModel
.query(trx)
.modify('filterByReference', referenceId, referenceType)
.withGraphFetched('account');
// Creates a new ledger from transaction and reverse the entries.
const reversedLedger = Ledger.fromTransactions(transactions).reverse();
// 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 { Account } from '../Accounts/models/Account.model';
import { AccountRepository } from '../Accounts/repositories/Account.repository';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
@Injectable()
export class LedegrAccountsStorage {
@@ -16,10 +17,12 @@ export class LedegrAccountsStorage {
* @param {AccountRepository} accountRepository -
*/
constructor(
private tenancyContext: TenancyContext,
@Inject(Account.name)
private accountModel: typeof Account,
@Inject(AccountRepository.name)
@Inject(AccountRepository)
private accountRepository: AccountRepository,
) {}
@@ -43,7 +46,7 @@ export class LedegrAccountsStorage {
};
/**
*
* Finds the dependant accounts ids.
* @param {number[]} accountsIds
* @returns {number[]}
*/
@@ -60,9 +63,9 @@ export class LedegrAccountsStorage {
/**
* Atomic mutation for accounts balances.
* @param {number} tenantId
* @param {ILedger} ledger
* @param {Knex.Transaction} trx -
* @param {number} tenantId
* @param {ILedger} ledger
* @param {Knex.Transaction} trx -
* @returns {Promise<void>}
*/
public saveAccountsBalance = async (
@@ -95,9 +98,9 @@ export class LedegrAccountsStorage {
private saveAccountBalanceTask = async (
task: ISaveAccountsBalanceQueuePayload,
): 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>}
*/
private saveAccountBalanceFromLedger = async (
tenantId: number,
ledger: ILedger,
accountId: number,
trx?: Knex.Transaction,
@@ -120,10 +122,11 @@ export class LedegrAccountsStorage {
const accountLedger = ledger.whereAccountId(accountId);
// 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.
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
// foreign currency otherwise get closing balance.
@@ -133,7 +136,7 @@ export class LedegrAccountsStorage {
.getForeignClosingBalance()
: accountLedger.getClosingBalance();
await this.saveAccountBalance(tenantId, accountId, closingBalance, trx);
await this.saveAccountBalance(accountId, closingBalance, trx);
};
/**
@@ -156,11 +159,11 @@ export class LedegrAccountsStorage {
.whereNull('amount')
.patch({ amount: 0 });
await this.accountModel.changeAmount(
{ id: accountId },
'amount',
change,
trx,
);
// await this.accountModel.changeAmount(
// { id: accountId },
// 'amount',
// change,
// 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 = (
entry: ILedgerEntry
): IAccountTransaction => {
): Partial<AccountTransaction> => {
return {
date: entry.date,
@@ -33,7 +34,7 @@ export const transformLedgerEntryToTransaction = (
itemId: entry.itemId,
projectId: entry.projectId,
costable: entry.costable,
// costable: entry.costable,
taxRateId: entry.taxRateId,
taxRate: entry.taxRate,

View File

@@ -12,9 +12,12 @@ import { BranchesModule } from '../Branches/Branches.module';
import { ManualJournalsController } from './ManualJournals.controller';
import { ManualJournalsApplication } from './ManualJournalsApplication.service';
import { GetManualJournal } from './queries/GetManualJournal.service';
import { ManualJournalWriteGLSubscriber } from './commands/ManualJournalGLEntriesSubscriber';
import { ManualJournalGLEntries } from './commands/ManualJournalGLEntries';
import { LedgerModule } from '../Ledger/Ledger.module';
@Module({
imports: [BranchesModule],
imports: [BranchesModule, LedgerModule],
controllers: [ManualJournalsController],
providers: [
TenancyContext,
@@ -28,7 +31,9 @@ import { GetManualJournal } from './queries/GetManualJournal.service';
ManualJournalBranchesDTOTransformer,
AutoIncrementOrdersService,
ManualJournalsApplication,
GetManualJournal
GetManualJournal,
ManualJournalGLEntries,
ManualJournalWriteGLSubscriber
],
})
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 * as R from 'ramda';
// import {
// IManualJournal,
// IManualJournalEntry,
// 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';
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { LedgerStorageService } from '@/modules/Ledger/LedgerStorage.service';
import { ManualJournal } from '../models/ManualJournal';
import { ManualJournalGL } from './ManualJournalGL';
// @Service()
// export class ManualJournalGLEntries {
// @Inject()
// private ledgerStorage: LedgerStorageService;
@Injectable()
export class ManualJournalGLEntries {
/**
* @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');
// /**
// * Create manual journal GL entries.
// * @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 ledger entries of the given manual journal.
const ledger = new ManualJournalGL(manualJournal).getManualJournalGLedger();
// // Retrieves the given manual journal with associated entries.
// const manualJournal = await ManualJournal.query(trx)
// .findById(manualJournalId)
// .withGraphFetched('entries.account');
// Commits the given ledger on the storage.
await this.ledgerStorage.commit(ledger, trx);
};
// // 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.
// await this.ledgerStorage.commit(tenantId, ledger, trx);
// };
// Write the manual journal GL entries.
await this.createManualJournalGLEntries(manualJournalId, trx);
};
// /**
// * Edits manual journal GL entries.
// * @param {number} tenantId
// * @param {number} manualJournalId
// * @param {Knex.Transaction} trx
// */
// public editManualJournalGLEntries = async (
// tenantId: number,
// manualJournalId: number,
// trx?: Knex.Transaction
// ) => {
// // Reverts the manual journal GL entries.
// await this.revertManualJournalGLEntries(tenantId, manualJournalId, 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();
// };
// }
/**
* Deletes the manual journal GL entries.
* @param {number} manualJournalId - The manual journal ID.
* @param {Knex.Transaction} trx - The knex transaction.
*/
public revertManualJournalGLEntries = async (
manualJournalId: number,
trx?: Knex.Transaction,
): Promise<void> => {
return this.ledgerStorage.deleteByReference(
manualJournalId,
'Journal',
trx,
);
};
}

View File

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

View File

@@ -1,5 +1,8 @@
import { Model } from 'objection';
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 {
index: number;
@@ -12,6 +15,10 @@ export class ManualJournalEntry extends BaseModel {
branchId!: number;
projectId?: number;
contact?: Contact;
account?: Account;
branch?: Branch;
/**
* 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 { sumBy } from 'lodash';
// import { Knex } from 'knex';
// import Ledger from '@/services/Accounting/Ledger';
// import TenancyService from '@/services/Tenancy/TenancyService';
// import {
// IPaymentReceived,
// ILedgerEntry,
// AccountNormal,
// IPaymentReceiveGLCommonEntry,
// } from '@/interfaces';
// import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
// import { TenantMetadata } from '@/system/models';
import { Knex } from 'knex';
import { PaymentReceivedGL } from './PaymentReceivedGL';
import { PaymentReceived } from '../models/PaymentReceived';
import { LedgerStorageService } from '@/modules/Ledger/LedgerStorage.service';
import { Ledger } from '@/modules/Ledger/Ledger';
import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository';
import { Injectable } from '@nestjs/common';
import { Inject } from '@nestjs/common';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
// @Service()
// export class PaymentReceivedGLEntries {
// @Inject()
// private tenancy: TenancyService;
@Injectable()
export class PaymentReceivedGLEntries {
constructor(
private readonly ledgerStorage: LedgerStorageService,
private readonly accountRepository: AccountRepository,
private readonly tenancyContext: TenancyContext,
// @Inject()
// private ledgerStorage: LedgerStorageService;
@Inject(PaymentReceived.name)
private readonly paymentReceivedModel: typeof PaymentReceived,
) {}
// /**
// * Writes payment GL entries to the storage.
// * @param {number} tenantId
// * @param {number} paymentReceiveId
// * @param {Knex.Transaction} trx
// * @returns {Promise<void>}
// */
// public writePaymentGLEntries = async (
// tenantId: number,
// paymentReceiveId: number,
// trx?: Knex.Transaction
// ): Promise<void> => {
// const { PaymentReceive } = this.tenancy.models(tenantId);
/**
* Writes payment GL entries to the storage.
* @param {number} paymentReceiveId - Payment received id.
* @param {Knex.Transaction} trx - Knex transaction.
* @returns {Promise<void>}
*/
public writePaymentGLEntries = async (
paymentReceiveId: number,
trx?: Knex.Transaction
): Promise<void> => {
// Retrieves the given tenant metadata.
const tenantMeta = await this.tenancyContext.getTenantMetadata();
// // Retrieves the given tenant metadata.
// const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
// Retrieves the payment receive with associated entries.
const paymentReceive = await this.paymentReceivedModel
.query(trx)
.findById(paymentReceiveId)
.withGraphFetched('entries.invoice');
// // Retrieves the payment receive with associated entries.
// const paymentReceive = await PaymentReceive.query(trx)
// .findById(paymentReceiveId)
// .withGraphFetched('entries.invoice');
// Retrives the payment receive ledger.
const ledger = await this.getPaymentReceiveGLedger(
paymentReceive,
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(
// tenantId,
// paymentReceive,
// tenantMeta.baseCurrency,
// trx
// );
// // Commit the ledger entries to the storage.
// await this.ledgerStorage.commit(tenantId, ledger, trx);
// };
/**
* Reverts the given payment receive GL entries.
* @param {number} paymentReceiveId - Payment received id.
* @param {Knex.Transaction} trx - Knex transaction.
*/
public revertPaymentGLEntries = async (
paymentReceiveId: number,
trx?: Knex.Transaction
) => {
await this.ledgerStorage.deleteByReference(
paymentReceiveId,
'PaymentReceive',
trx
);
};
// /**
// * Reverts the given payment receive GL entries.
// * @param {number} tenantId
// * @param {number} paymentReceiveId
// * @param {Knex.Transaction} trx
// */
// public revertPaymentGLEntries = async (
// tenantId: number,
// paymentReceiveId: number,
// trx?: Knex.Transaction
// ) => {
// await this.ledgerStorage.deleteByReference(
// tenantId,
// paymentReceiveId,
// 'PaymentReceive',
// trx
// );
// };
/**
* Rewrites the given payment receive GL entries.
* @param {number} paymentReceiveId - Payment received id.
* @param {Knex.Transaction} trx - Knex transaction.
*/
public rewritePaymentGLEntries = async (
paymentReceiveId: number,
trx?: Knex.Transaction
) => {
// Reverts the payment GL entries.
await this.revertPaymentGLEntries(paymentReceiveId, trx);
// /**
// * Rewrites the given payment receive GL entries.
// * @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(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);
// /**
// * 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);
return paymentReceivedGL.getLedger();
};
// // 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;
oldSaleInvoice: SaleInvoice;
saleInvoiceId: number;
trx: Knex.Transaction;
}
export interface ISaleInvoiceDeletingPayload {

View File

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

View File

@@ -23,8 +23,8 @@ export class CreateSaleInvoice {
private readonly validators: CommandSaleInvoiceValidators,
private readonly transformerDTO: CommandSaleInvoiceDTOTransformer,
private readonly eventPublisher: EventEmitter2,
private readonly uow: UnitOfWork,
private readonly commandEstimateValidators: SaleEstimateValidators,
private readonly uow: UnitOfWork,
@Inject(SaleInvoice.name)
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 pdfTemplateId: number;
public userId: number;
public branchId: 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 { AutoIncrementOrdersModule } from '../AutoIncrementOrders/AutoIncrementOrders.module';
import { SaleReceiptsController } from './SaleReceipts.controller';
import { SaleReceiptGLEntriesSubscriber } from './subscribers/SaleReceiptGLEntriesSubscriber';
import { SaleReceiptGLEntries } from './ledger/SaleReceiptGLEntries';
@Module({
controllers: [SaleReceiptsController],
@@ -46,6 +48,8 @@ import { SaleReceiptsController } from './SaleReceipts.controller';
SaleReceiptDTOTransformer,
SaleReceiptBrandingTemplate,
SaleReceiptIncrement,
SaleReceiptGLEntries,
SaleReceiptGLEntriesSubscriber
],
})
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 ModelSearchable from './ModelSearchable';
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 {
amount: number;
@@ -27,6 +32,12 @@ export class SaleReceipt extends BaseModel {
createdAt: Date;
updatedAt: Date | null;
customer!: Customer;
entries!: ItemEntry[];
transactions!: AccountTransaction[];
branch!: Branch;
warehouse!: Warehouse;
/**
* 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;
}
async getTenantMetadata() {
const tenant = await this.getTenant(true);
return tenant?.metadata;
}
/**
* Retrieves the current system user.
* @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 { AppModule } from '../src/modules/App/App.module';
@@ -10,6 +10,8 @@ beforeAll(async () => {
}).compile();
app = moduleFixture.createNestApplication();
app.useLogger(new Logger());
await app.init();
});