mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 20:30:33 +00:00
refactor: GL entries
This commit is contained in:
@@ -33,5 +33,8 @@ import { GetAccountTransactionsService } from './GetAccountTransactions.service'
|
||||
GetAccountTypesService,
|
||||
GetAccountTransactionsService,
|
||||
],
|
||||
exports: [
|
||||
AccountRepository
|
||||
]
|
||||
})
|
||||
export class AccountsModule {}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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],
|
||||
})
|
||||
|
||||
@@ -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);
|
||||
// };
|
||||
// }
|
||||
@@ -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);
|
||||
// };
|
||||
// }
|
||||
243
packages/server-nest/src/modules/Bills/commands/BillsGL.ts
Normal file
243
packages/server-nest/src/modules/Bills/commands/BillsGL.ts
Normal 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);
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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];
|
||||
};
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
// );
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
// );
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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());
|
||||
};
|
||||
}
|
||||
@@ -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];
|
||||
// };
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
// };
|
||||
// }
|
||||
@@ -117,6 +117,7 @@ export interface ISaleInvoiceDeletePayload {
|
||||
// tenantId: number;
|
||||
oldSaleInvoice: SaleInvoice;
|
||||
saleInvoiceId: number;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface ISaleInvoiceDeletingPayload {
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -39,6 +39,7 @@ export class SaleInvoice extends BaseModel {
|
||||
public referenceNo: string;
|
||||
|
||||
public pdfTemplateId: number;
|
||||
public userId: number;
|
||||
|
||||
public branchId: number;
|
||||
public warehouseId: number;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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];
|
||||
// };
|
||||
// }
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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>}
|
||||
|
||||
61
packages/server-nest/test/expenses.e2e-spec.ts
Normal file
61
packages/server-nest/test/expenses.e2e-spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user