fix: design flow of writing invoice journal entries.

This commit is contained in:
a.bouhuolia
2021-01-04 17:19:27 +02:00
parent 999e74b405
commit d5151c365e
15 changed files with 377 additions and 473 deletions

View File

@@ -272,10 +272,12 @@ export default class SaleInvoicesController extends BaseController {
next: NextFunction next: NextFunction
) { ) {
const { tenantId } = req; const { tenantId } = req;
const filter: ISalesInvoicesFilter = { const filter = {
filterRoles: [], filterRoles: [],
sortOrder: 'asc', sortOrder: 'asc',
columnSortBy: 'created_at', columnSortBy: 'created_at',
page: 1,
pageSize: 12,
...this.matchedQueryData(req), ...this.matchedQueryData(req),
}; };
if (filter.stringifiedFilterRoles) { if (filter.stringifiedFilterRoles) {

View File

@@ -164,4 +164,5 @@ export default {
protocol: '', protocol: '',
hostname: '', hostname: '',
scheduleComputeItemCost: 'in 5 seconds'
}; };

View File

@@ -9,7 +9,8 @@ export interface ISaleInvoice {
dueAmount: number, dueAmount: number,
customerId: number, customerId: number,
entries: IItemEntry[], entries: IItemEntry[],
deliveredAt: string|Date, deliveredAt: string | Date,
userId: number,
} }
export interface ISaleInvoiceDTO { export interface ISaleInvoiceDTO {

View File

@@ -2,12 +2,11 @@ import { Container } from 'typedi';
import SalesInvoicesCost from 'services/Sales/SalesInvoicesCost'; import SalesInvoicesCost from 'services/Sales/SalesInvoicesCost';
export default class WriteInvoicesJournalEntries { export default class WriteInvoicesJournalEntries {
constructor(agenda) { constructor(agenda) {
agenda.define( agenda.define(
'rewrite-invoices-journal-entries', 'rewrite-invoices-journal-entries',
{ priority: 'normal', concurrency: 1, }, { priority: 'normal', concurrency: 1 },
this.handler.bind(this), this.handler.bind(this)
); );
} }
@@ -17,15 +16,24 @@ export default class WriteInvoicesJournalEntries {
const salesInvoicesCost = Container.get(SalesInvoicesCost); const salesInvoicesCost = Container.get(SalesInvoicesCost);
Logger.info(`Write sales invoices journal entries - started: ${job.attrs.data}`); Logger.info(
`Write sales invoices journal entries - started: ${job.attrs.data}`
);
try { try {
await salesInvoicesCost.writeJournalEntries(tenantId, startingDate, true); await salesInvoicesCost.writeInventoryCostJournalEntries(
Logger.info(`Write sales invoices journal entries - completed: ${job.attrs.data}`); tenantId,
startingDate,
true
);
Logger.info(
`Write sales invoices journal entries - completed: ${job.attrs.data}`
);
done(); done();
} catch(e) { } catch (e) {
Logger.info(`Write sales invoices journal entries: ${job.attrs.data}, error: ${e}`); Logger.info(
done(e); `Write sales invoices journal entries: ${job.attrs.data}, error: ${e}`
);
done(e);
} }
} }
} }

View File

@@ -7,7 +7,6 @@ import dbManagerFactory from 'loaders/dbManager';
import i18n from 'loaders/i18n'; import i18n from 'loaders/i18n';
import repositoriesLoader from 'loaders/systemRepositories'; import repositoriesLoader from 'loaders/systemRepositories';
import Cache from 'services/Cache'; import Cache from 'services/Cache';
import redisLoader from './redisLoader';
import rateLimiterLoaders from './rateLimiterLoader'; import rateLimiterLoaders from './rateLimiterLoader';
export default ({ mongoConnection, knex }) => { export default ({ mongoConnection, knex }) => {

View File

@@ -23,14 +23,13 @@ export default class InventoryCostLotTracker extends TenantModel {
static get modifiers() { static get modifiers() {
return { return {
groupedEntriesCost(query) { groupedEntriesCost(query) {
query.select(['entry_id', 'transaction_id', 'transaction_type']); query.select(['date', 'item_id', 'transaction_id', 'transaction_type']);
query.sum('cost as cost');
query.groupBy('item_id');
query.groupBy('entry_id');
query.groupBy('transaction_id'); query.groupBy('transaction_id');
query.groupBy('transaction_type'); query.groupBy('transaction_type');
query.groupBy('date');
query.sum('cost as cost'); query.groupBy('item_id');
}, },
filterDateRange(query, startDate, endDate, type = 'day') { filterDateRange(query, startDate, endDate, type = 'day') {
const dateFormat = 'YYYY-MM-DD HH:mm:ss'; const dateFormat = 'YYYY-MM-DD HH:mm:ss';

View File

@@ -13,6 +13,7 @@ interface IJournalTransactionsFilter {
contactType?: string, contactType?: string,
referenceType?: string[], referenceType?: string[],
referenceId?: number[], referenceId?: number[],
index: number|number[]
}; };
export default class AccountTransactionsRepository extends TenantRepository { export default class AccountTransactionsRepository extends TenantRepository {
@@ -50,6 +51,13 @@ export default class AccountTransactionsRepository extends TenantRepository {
if (filter.referenceId && filter.referenceId.length > 0) { if (filter.referenceId && filter.referenceId.length > 0) {
query.whereIn('reference_id', filter.referenceId); query.whereIn('reference_id', filter.referenceId);
} }
if (filter.index) {
if (Array.isArray(filter.index)) {
query.whereIn('index', filter.index);
} else {
query.where('index', filter.index);
}
}
}); });
}); });
} }

View File

@@ -1,5 +1,5 @@
import { sumBy, chain } from 'lodash'; import { sumBy, chain } from 'lodash';
import moment from 'moment'; import moment, { LongDateFormatKey } from 'moment';
import { IBill, IManualJournalEntry, ISystemUser } from 'interfaces'; import { IBill, IManualJournalEntry, ISystemUser } from 'interfaces';
import JournalPoster from './JournalPoster'; import JournalPoster from './JournalPoster';
import JournalEntry from './JournalEntry'; import JournalEntry from './JournalEntry';
@@ -183,7 +183,7 @@ export default class JournalCommands {
async vendorOpeningBalance( async vendorOpeningBalance(
vendorId: number, vendorId: number,
openingBalance: number, openingBalance: number,
openingBalanceAt: Date|string, openingBalanceAt: Date | string,
authorizedUserId: ISystemUser authorizedUserId: ISystemUser
) { ) {
const { accountRepository } = this.repositories; const { accountRepository } = this.repositories;
@@ -225,10 +225,7 @@ export default class JournalCommands {
* Writes journal entries of expense model object. * Writes journal entries of expense model object.
* @param {IExpense} expense * @param {IExpense} expense
*/ */
expense( expense(expense: IExpense, userId: number) {
expense: IExpense,
userId: number,
) {
const mixinEntry = { const mixinEntry = {
referenceType: 'Expense', referenceType: 'Expense',
referenceId: expense.id, referenceId: expense.id,
@@ -279,14 +276,50 @@ export default class JournalCommands {
this.journal.removeEntries(); this.journal.removeEntries();
} }
/**
* Reverts the sale invoice cost journal entries.
* @param {Date|string} startingDate
* @return {Promise<void>}
*/
async revertInventoryCostJournalEntries(
startingDate: Date | string
): Promise<void> {
const { transactionsRepository } = this.repositories;
const transactions = await transactionsRepository.journal({
fromDate: startingDate,
referenceType: ['SaleInvoice'],
index: [3, 4],
});
console.log(transactions);
this.journal.fromTransactions(transactions);
this.journal.removeEntries();
}
/**
* Reverts sale invoice the income journal entries.
* @param {number} saleInvoiceId
*/
async revertInvoiceIncomeEntries(
saleInvoiceId: number,
) {
const { transactionsRepository } = this.repositories;
const transactions = await transactionsRepository.journal({
referenceType: ['SaleInvoice'],
referenceId: [saleInvoiceId],
});
this.journal.fromTransactions(transactions);
this.journal.removeEntries();
}
/** /**
* Writes journal entries from manual journal model object. * Writes journal entries from manual journal model object.
* @param {IManualJournal} manualJournalObj * @param {IManualJournal} manualJournalObj
* @param {number} manualJournalId * @param {number} manualJournalId
*/ */
async manualJournal( async manualJournal(manualJournalObj: IManualJournal) {
manualJournalObj: IManualJournal,
) {
manualJournalObj.entries.forEach((entry: IManualJournalEntry) => { manualJournalObj.entries.forEach((entry: IManualJournalEntry) => {
const jouranlEntry = new JournalEntry({ const jouranlEntry = new JournalEntry({
debit: entry.debit, debit: entry.debit,
@@ -310,261 +343,68 @@ export default class JournalCommands {
}); });
} }
/**
* Removes and revert accounts balance journal entries that associated
* to the given inventory transactions.
* @param {IInventoryTransaction[]} inventoryTransactions
* @param {Journal} journal
*/
revertEntriesFromInventoryTransactions(
inventoryTransactions: IInventoryTransaction[]
) {
const groupedInvTransactions = chain(inventoryTransactions)
.groupBy(
(invTransaction: IInventoryTransaction) =>
invTransaction.transactionType
)
.map((groupedTrans: IInventoryTransaction[], transType: string) => [
groupedTrans,
transType,
])
.value();
return Promise.all(
groupedInvTransactions.map(
async (grouped: [IInventoryTransaction[], string]) => {
const [invTransGroup, referenceType] = grouped;
const referencesIds = invTransGroup.map(
(trans: IInventoryTransaction) => trans.transactionId
);
const _transactions = await AccountTransaction.tenant()
.query()
.where('reference_type', referenceType)
.whereIn('reference_id', referencesIds)
.withGraphFetched('account.type');
if (_transactions.length > 0) {
this.journal.loadEntries(_transactions);
this.journal.removeEntries(_transactions.map((t: any) => t.id));
}
}
)
);
}
public async nonInventoryEntries(transactions: NonInventoryJEntries[]) {
const receivableAccount = { id: 10 };
const payableAccount = { id: 11 };
transactions.forEach((trans: NonInventoryJEntries) => {
const commonEntry = {
date: trans.date,
referenceId: trans.referenceId,
referenceType: trans.referenceType,
};
switch (trans.referenceType) {
case 'Bill':
const payableEntry: JournalEntry = new JournalEntry({
...commonEntry,
credit: trans.payable,
account: payableAccount.id,
});
const costEntry: JournalEntry = new JournalEntry({
...commonEntry,
});
this.journal.credit(payableEntry);
this.journal.debit(costEntry);
break;
case 'SaleInvoice':
const receivableEntry: JournalEntry = new JournalEntry({
...commonEntry,
debit: trans.receivable,
account: receivableAccount.id,
});
const saleIncomeEntry: JournalEntry = new JournalEntry({
...commonEntry,
credit: trans.income,
account: trans.incomeAccountId,
});
this.journal.debit(receivableEntry);
this.journal.credit(saleIncomeEntry);
break;
}
});
}
/**
*
* @param {string} referenceType -
* @param {number} referenceId -
* @param {ISaleInvoice[]} sales -
*/
public async inventoryEntries(transactions: IInventoryCostEntity[]) {
const receivableAccount = { id: 10 };
const payableAccount = { id: 11 };
transactions.forEach((sale: IInventoryCostEntity) => {
const commonEntry = {
date: sale.date,
referenceId: sale.referenceId,
referenceType: sale.referenceType,
};
switch (sale.referenceType) {
case 'Bill':
const inventoryDebit: JournalEntry = new JournalEntry({
...commonEntry,
debit: sale.inventory,
account: sale.inventoryAccount,
});
const payableEntry: JournalEntry = new JournalEntry({
...commonEntry,
credit: sale.inventory,
account: payableAccount.id,
});
this.journal.debit(inventoryDebit);
this.journal.credit(payableEntry);
break;
case 'SaleInvoice':
const receivableEntry: JournalEntry = new JournalEntry({
...commonEntry,
debit: sale.income,
account: receivableAccount.id,
});
const incomeEntry: JournalEntry = new JournalEntry({
...commonEntry,
credit: sale.income,
account: sale.incomeAccount,
});
// Cost journal transaction.
const costEntry: JournalEntry = new JournalEntry({
...commonEntry,
debit: sale.cost,
account: sale.costAccount,
});
const inventoryCredit: JournalEntry = new JournalEntry({
...commonEntry,
credit: sale.cost,
account: sale.inventoryAccount,
});
this.journal.debit(receivableEntry);
this.journal.debit(costEntry);
this.journal.credit(incomeEntry);
this.journal.credit(inventoryCredit);
break;
}
});
}
/** /**
* Writes journal entries for given sale invoice. * Writes journal entries for given sale invoice.
* ---------- * -------
* - Receivable accounts -> Debit -> XXXX
* - Income -> Credit -> XXXX
*
* - Cost of goods sold -> Debit -> YYYY * - Cost of goods sold -> Debit -> YYYY
* - Inventory assets -> YYYY * - Inventory assets -> Credit -> YYYY
* *
* @param {ISaleInvoice} saleInvoice * @param {ISaleInvoice} saleInvoice
* @param {JournalPoster} journal * @param {JournalPoster} journal
*/ */
saleInvoice( saleInvoiceInventoryCost(
saleInvoice: ISaleInvoice & { inventoryCostLot: IInventoryLotCost & { item: IItem }
costTransactions: IInventoryLotCost[];
entries: IItemEntry & { item: IItem };
},
receivableAccountsId: number
) { ) {
let inventoryTotal: number = 0;
const commonEntry = { const commonEntry = {
referenceType: 'SaleInvoice', referenceType: 'SaleInvoice',
referenceId: saleInvoice.id, referenceId: inventoryCostLot.transactionId,
date: saleInvoice.invoiceDate, date: inventoryCostLot.date,
}; };
const costTransactions: Map<number, number> = new Map( // XXX Debit - Cost account.
saleInvoice.costTransactions.map((trans: IInventoryLotCost) => [ const costEntry = new JournalEntry({
trans.entryId,
trans.cost,
])
);
// XXX Debit - Receivable account.
const receivableEntry = new JournalEntry({
...commonEntry, ...commonEntry,
debit: saleInvoice.balance, debit: inventoryCostLot.cost,
account: receivableAccountsId, account: inventoryCostLot.item.costAccountId,
index: 1, index: 3,
}); });
this.journal.debit(receivableEntry); // XXX Credit - Inventory account.
const inventoryEntry = new JournalEntry({
saleInvoice.entries.forEach( ...commonEntry,
(entry: IItemEntry & { item: IItem }, index) => { credit: inventoryCostLot.cost,
const cost: number = costTransactions.get(entry.id); account: inventoryCostLot.item.inventoryAccountId,
const income: number = entry.quantity * entry.rate; index: 4,
});
if (entry.item.type === 'inventory' && cost) { this.journal.credit(inventoryEntry);
// XXX Debit - Cost account. this.journal.debit(costEntry);
const costEntry = new JournalEntry({
...commonEntry,
debit: cost,
account: entry.item.costAccountId,
note: entry.description,
index: index + 3,
});
this.journal.debit(costEntry);
inventoryTotal += cost;
}
// XXX Credit - Income account.
const incomeEntry = new JournalEntry({
...commonEntry,
credit: income,
account: entry.item.sellAccountId,
note: entry.description,
index: index + 2,
});
this.journal.credit(incomeEntry);
if (inventoryTotal > 0) {
// XXX Credit - Inventory account.
const inventoryEntry = new JournalEntry({
...commonEntry,
credit: inventoryTotal,
account: entry.item.inventoryAccountId,
index: index + 4,
});
this.journal.credit(inventoryEntry);
}
}
);
} }
/** /**
* Writes the sale invoice income journal entries.
* -----
* - Receivable accounts -> Debit -> XXXX
* - Income -> Credit -> XXXX
* *
* @param {ISaleInvoice} saleInvoice * @param {ISaleInvoice} saleInvoice
* @param {number} receivableAccountsId * @param {number} receivableAccountsId
* @param {number} authorizedUserId * @param {number} authorizedUserId
*/ */
saleInvoiceNonInventory( async saleInvoiceIncomeEntries(
saleInvoice: ISaleInvoice & { saleInvoice: ISaleInvoice & {
entries: IItemEntry & { item: IItem }; entries: IItemEntry & { item: IItem };
}, },
receivableAccountsId: number, receivableAccountId: number
authorizedUserId: number, ): Promise<void> {
) {
const commonEntry = { const commonEntry = {
referenceType: 'SaleInvoice', referenceType: 'SaleInvoice',
referenceId: saleInvoice.id, referenceId: saleInvoice.id,
date: saleInvoice.invoiceDate, date: saleInvoice.invoiceDate,
userId: authorizedUserId, userId: saleInvoice.userId,
}; };
// XXX Debit - Receivable account. // XXX Debit - Receivable account.
const receivableEntry = new JournalEntry({ const receivableEntry = new JournalEntry({
...commonEntry, ...commonEntry,
debit: saleInvoice.balance, debit: saleInvoice.balance,
account: receivableAccountsId, account: receivableAccountId,
index: 1, index: 1,
}); });
this.journal.debit(receivableEntry); this.journal.debit(receivableEntry);

View File

@@ -17,7 +17,6 @@ import {
IContactEditDTO, IContactEditDTO,
IContact, IContact,
ISaleInvoice, ISaleInvoice,
ISystemService,
ISystemUser, ISystemUser,
} from 'interfaces'; } from 'interfaces';
import { ServiceError } from 'exceptions'; import { ServiceError } from 'exceptions';

View File

@@ -1,10 +1,16 @@
import { Container, Service, Inject } from 'typedi'; import { Container, Service, Inject } from 'typedi';
import { pick } from 'lodash'; import { pick } from 'lodash';
import config from 'config';
import { import {
EventDispatcher, EventDispatcher,
EventDispatcherInterface, EventDispatcherInterface,
} from 'decorators/eventDispatcher'; } from 'decorators/eventDispatcher';
import { IInventoryLotCost, IInventoryTransaction, IItem, IItemEntry } from 'interfaces' import {
IInventoryLotCost,
IInventoryTransaction,
IItem,
IItemEntry,
} from 'interfaces';
import InventoryAverageCost from 'services/Inventory/InventoryAverageCost'; import InventoryAverageCost from 'services/Inventory/InventoryAverageCost';
import InventoryCostLotTracker from 'services/Inventory/InventoryCostLotTracker'; import InventoryCostLotTracker from 'services/Inventory/InventoryCostLotTracker';
import TenancyService from 'services/Tenancy/TenancyService'; import TenancyService from 'services/Tenancy/TenancyService';
@@ -27,9 +33,9 @@ export default class InventoryService {
itemEntries: IItemEntry[], itemEntries: IItemEntry[],
transactionType: string, transactionType: string,
transactionId: number, transactionId: number,
direction: 'IN'|'OUT', direction: 'IN' | 'OUT',
date: Date|string, date: Date | string,
lotNumber: number, lotNumber: number
) { ) {
return itemEntries.map((entry: IItemEntry) => ({ return itemEntries.map((entry: IItemEntry) => ({
...pick(entry, ['itemId', 'quantity', 'rate']), ...pick(entry, ['itemId', 'quantity', 'rate']),
@@ -62,13 +68,21 @@ export default class InventoryService {
let costMethodComputer: IInventoryCostMethod; let costMethodComputer: IInventoryCostMethod;
// Switch between methods based on the item cost method. // Switch between methods based on the item cost method.
switch('AVG') { switch ('AVG') {
case 'FIFO': case 'FIFO':
case 'LIFO': case 'LIFO':
costMethodComputer = new InventoryCostLotTracker(tenantId, fromDate, itemId); costMethodComputer = new InventoryCostLotTracker(
tenantId,
fromDate,
itemId
);
break; break;
case 'AVG': case 'AVG':
costMethodComputer = new InventoryAverageCost(tenantId, fromDate, itemId); costMethodComputer = new InventoryAverageCost(
tenantId,
fromDate,
itemId
);
break; break;
} }
return costMethodComputer.computeItemCost(); return costMethodComputer.computeItemCost();
@@ -77,20 +91,24 @@ export default class InventoryService {
/** /**
* Schedule item cost compute job. * Schedule item cost compute job.
* @param {number} tenantId * @param {number} tenantId
* @param {number} itemId * @param {number} itemId
* @param {Date} startingDate * @param {Date} startingDate
*/ */
async scheduleComputeItemCost(tenantId: number, itemId: number, startingDate: Date|string) { async scheduleComputeItemCost(
tenantId: number,
itemId: number,
startingDate: Date | string
) {
const agenda = Container.get('agenda'); const agenda = Container.get('agenda');
// Cancel any `compute-item-cost` in the queue has upper starting date // Cancel any `compute-item-cost` in the queue has upper starting date
// with the same given item. // with the same given item.
await agenda.cancel({ await agenda.cancel({
name: 'compute-item-cost', name: 'compute-item-cost',
nextRunAt: { $ne: null }, nextRunAt: { $ne: null },
'data.tenantId': tenantId, 'data.tenantId': tenantId,
'data.itemId': itemId, 'data.itemId': itemId,
'data.startingDate': { "$gt": startingDate } 'data.startingDate': { $gt: startingDate },
}); });
// Retrieve any `compute-item-cost` in the queue has lower starting date // Retrieve any `compute-item-cost` in the queue has lower starting date
@@ -100,23 +118,29 @@ export default class InventoryService {
nextRunAt: { $ne: null }, nextRunAt: { $ne: null },
'data.tenantId': tenantId, 'data.tenantId': tenantId,
'data.itemId': itemId, 'data.itemId': itemId,
'data.startingDate': { "$lte": startingDate } 'data.startingDate': { $lte: startingDate },
}); });
if (dependsJobs.length === 0) { if (dependsJobs.length === 0) {
await agenda.schedule('in 30 seconds', 'compute-item-cost', { await agenda.schedule(
startingDate, itemId, tenantId, config.scheduleComputeItemCost,
}); 'compute-item-cost',
{
startingDate,
itemId,
tenantId,
}
);
// Triggers `onComputeItemCostJobScheduled` event. // Triggers `onComputeItemCostJobScheduled` event.
await this.eventDispatcher.dispatch( await this.eventDispatcher.dispatch(
events.inventory.onComputeItemCostJobScheduled, events.inventory.onComputeItemCostJobScheduled,
{ startingDate, itemId, tenantId }, { startingDate, itemId, tenantId }
); );
} }
} }
/** /**
* Records the inventory transactions. * Records the inventory transactions.
* @param {number} tenantId - Tenant id. * @param {number} tenantId - Tenant id.
* @param {Bill} bill - Bill model object. * @param {Bill} bill - Bill model object.
* @param {number} billId - Bill id. * @param {number} billId - Bill id.
@@ -125,27 +149,23 @@ export default class InventoryService {
async recordInventoryTransactions( async recordInventoryTransactions(
tenantId: number, tenantId: number,
inventoryEntries: IInventoryTransaction[], inventoryEntries: IInventoryTransaction[],
deleteOld: boolean, deleteOld: boolean
): Promise<void> { ): Promise<void> {
inventoryEntries.forEach(async (entry: IInventoryTransaction) => { inventoryEntries.forEach(async (entry: IInventoryTransaction) => {
await this.recordInventoryTransaction( await this.recordInventoryTransaction(tenantId, entry, deleteOld);
tenantId,
entry,
deleteOld,
);
}); });
} }
/** /**
* *
* @param {number} tenantId * @param {number} tenantId
* @param {IInventoryTransaction} inventoryEntry * @param {IInventoryTransaction} inventoryEntry
* @param {boolean} deleteOld * @param {boolean} deleteOld
*/ */
async recordInventoryTransaction( async recordInventoryTransaction(
tenantId: number, tenantId: number,
inventoryEntry: IInventoryTransaction, inventoryEntry: IInventoryTransaction,
deleteOld: boolean = false, deleteOld: boolean = false
): Promise<IInventoryTransaction> { ): Promise<IInventoryTransaction> {
const { InventoryTransaction, Item } = this.tenancy.models(tenantId); const { InventoryTransaction, Item } = this.tenancy.models(tenantId);
@@ -153,7 +173,7 @@ export default class InventoryService {
await this.deleteInventoryTransactions( await this.deleteInventoryTransactions(
tenantId, tenantId,
inventoryEntry.transactionId, inventoryEntry.transactionId,
inventoryEntry.transactionType, inventoryEntry.transactionType
); );
} }
return InventoryTransaction.query().insert({ return InventoryTransaction.query().insert({
@@ -165,14 +185,14 @@ export default class InventoryService {
/** /**
* Deletes the given inventory transactions. * Deletes the given inventory transactions.
* @param {number} tenantId - Tenant id. * @param {number} tenantId - Tenant id.
* @param {string} transactionType * @param {string} transactionType
* @param {number} transactionId * @param {number} transactionId
* @return {Promise} * @return {Promise}
*/ */
async deleteInventoryTransactions( async deleteInventoryTransactions(
tenantId: number, tenantId: number,
transactionId: number, transactionId: number,
transactionType: string, transactionType: string
): Promise<void> { ): Promise<void> {
const { InventoryTransaction } = this.tenancy.models(tenantId); const { InventoryTransaction } = this.tenancy.models(tenantId);
@@ -184,16 +204,16 @@ export default class InventoryService {
/** /**
* Records the inventory cost lot transaction. * Records the inventory cost lot transaction.
* @param {number} tenantId * @param {number} tenantId
* @param {IInventoryLotCost} inventoryLotEntry * @param {IInventoryLotCost} inventoryLotEntry
* @return {Promise<IInventoryLotCost>} * @return {Promise<IInventoryLotCost>}
*/ */
async recordInventoryCostLotTransaction( async recordInventoryCostLotTransaction(
tenantId: number, tenantId: number,
inventoryLotEntry: IInventoryLotCost, inventoryLotEntry: IInventoryLotCost
): Promise<void> { ): Promise<void> {
const { InventoryCostLotTracker } = this.tenancy.models(tenantId); const { InventoryCostLotTracker } = this.tenancy.models(tenantId);
return InventoryCostLotTracker.query().insert({ return InventoryCostLotTracker.query().insert({
...inventoryLotEntry, ...inventoryLotEntry,
}); });
@@ -209,13 +229,14 @@ export default class InventoryService {
const LOT_NUMBER_KEY = 'lot_number_increment'; const LOT_NUMBER_KEY = 'lot_number_increment';
const storedLotNumber = settings.find({ key: LOT_NUMBER_KEY }); const storedLotNumber = settings.find({ key: LOT_NUMBER_KEY });
return (storedLotNumber && storedLotNumber.value) ? return storedLotNumber && storedLotNumber.value
parseInt(storedLotNumber.value, 10) : 1; ? parseInt(storedLotNumber.value, 10)
: 1;
} }
/** /**
* Increment the next inventory LOT number. * Increment the next inventory LOT number.
* @param {number} tenantId * @param {number} tenantId
* @return {Promise<number>} * @return {Promise<number>}
*/ */
async incrementNextLotNumber(tenantId: number) { async incrementNextLotNumber(tenantId: number) {
@@ -236,4 +257,4 @@ export default class InventoryService {
return lotNumber; return lotNumber;
} }
} }

View File

@@ -19,7 +19,7 @@ export default class JournalPosterService {
tenantId: number, tenantId: number,
referenceId: number|number[], referenceId: number|number[],
referenceType: string referenceType: string
) { ): Promise<void> {
const journal = new JournalPoster(tenantId); const journal = new JournalPoster(tenantId);
const journalCommand = new JournalCommands(journal); const journalCommand = new JournalCommands(journal);

View File

@@ -13,10 +13,13 @@ import {
IPaginationMeta, IPaginationMeta,
IFilterMeta, IFilterMeta,
ISystemUser, ISystemUser,
IItem,
IItemEntry,
} from 'interfaces'; } from 'interfaces';
import JournalPoster from 'services/Accounting/JournalPoster';
import JournalCommands from 'services/Accounting/JournalCommands';
import events from 'subscribers/events'; import events from 'subscribers/events';
import InventoryService from 'services/Inventory/Inventory'; import InventoryService from 'services/Inventory/Inventory';
import SalesInvoicesCost from 'services/Sales/SalesInvoicesCost';
import TenancyService from 'services/Tenancy/TenancyService'; import TenancyService from 'services/Tenancy/TenancyService';
import DynamicListingService from 'services/DynamicListing/DynamicListService'; import DynamicListingService from 'services/DynamicListing/DynamicListService';
import { formatDateFields } from 'utils'; import { formatDateFields } from 'utils';
@@ -25,6 +28,7 @@ import ItemsService from 'services/Items/ItemsService';
import ItemsEntriesService from 'services/Items/ItemsEntriesService'; import ItemsEntriesService from 'services/Items/ItemsEntriesService';
import CustomersService from 'services/Contacts/CustomersService'; import CustomersService from 'services/Contacts/CustomersService';
import SaleEstimateService from 'services/Sales/SalesEstimate'; import SaleEstimateService from 'services/Sales/SalesEstimate';
import JournalPosterService from './JournalPosterService';
const ERRORS = { const ERRORS = {
INVOICE_NUMBER_NOT_UNIQUE: 'INVOICE_NUMBER_NOT_UNIQUE', INVOICE_NUMBER_NOT_UNIQUE: 'INVOICE_NUMBER_NOT_UNIQUE',
@@ -42,7 +46,7 @@ const ERRORS = {
* @service * @service
*/ */
@Service() @Service()
export default class SaleInvoicesService extends SalesInvoicesCost { export default class SaleInvoicesService {
@Inject() @Inject()
tenancy: TenancyService; tenancy: TenancyService;
@@ -70,6 +74,9 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
@Inject() @Inject()
saleEstimatesService: SaleEstimateService; saleEstimatesService: SaleEstimateService;
@Inject()
journalService: JournalPosterService;
/** /**
* Validate whether sale invoice number unqiue on the storage. * Validate whether sale invoice number unqiue on the storage.
*/ */
@@ -101,6 +108,28 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
} }
} }
/**
* Validate the sale invoice has no payment entries.
* @param {number} tenantId
* @param {number} saleInvoiceId
*/
async validateInvoiceHasNoPaymentEntries(
tenantId: number,
saleInvoiceId: number
) {
const { PaymentReceiveEntry } = this.tenancy.models(tenantId);
// Retrieve the sale invoice associated payment receive entries.
const entries = await PaymentReceiveEntry.query().where(
'invoice_id',
saleInvoiceId
);
if (entries.length > 0) {
throw new ServiceError(ERRORS.INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES);
}
return entries;
}
/** /**
* Validate whether sale invoice exists on the storage. * Validate whether sale invoice exists on the storage.
* @param {Request} req * @param {Request} req
@@ -148,7 +177,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
balance, balance,
paymentAmount: 0, paymentAmount: 0,
entries: saleInvoiceDTO.entries.map((entry) => ({ entries: saleInvoiceDTO.entries.map((entry) => ({
reference_type: 'SaleInvoice', referenceType: 'SaleInvoice',
...omit(entry, ['amount', 'id']), ...omit(entry, ['amount', 'id']),
})), })),
}; };
@@ -208,7 +237,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
}); });
this.logger.info('[sale_invoice] successfully inserted.', { this.logger.info('[sale_invoice] successfully inserted.', {
tenantId, tenantId,
saleInvoice, saleInvoiceId: saleInvoice.id,
}); });
return saleInvoice; return saleInvoice;
@@ -238,19 +267,19 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
const saleInvoiceObj = this.transformDTOToModel( const saleInvoiceObj = this.transformDTOToModel(
tenantId, tenantId,
saleInvoiceDTO, saleInvoiceDTO,
oldSaleInvoice, oldSaleInvoice
); );
// Validate customer existance. // Validate customer existance.
await this.customersService.getCustomerByIdOrThrowError( await this.customersService.getCustomerByIdOrThrowError(
tenantId, tenantId,
saleInvoiceDTO.customerId, saleInvoiceDTO.customerId
); );
// Validate sale invoice number uniquiness. // Validate sale invoice number uniquiness.
if (saleInvoiceDTO.invoiceNo) { if (saleInvoiceDTO.invoiceNo) {
await this.validateInvoiceNumberUnique( await this.validateInvoiceNumberUnique(
tenantId, tenantId,
saleInvoiceDTO.invoiceNo, saleInvoiceDTO.invoiceNo,
saleInvoiceId, saleInvoiceId
); );
} }
// Validate items ids existance. // Validate items ids existance.
@@ -261,7 +290,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
// Validate non-sellable entries items. // Validate non-sellable entries items.
await this.itemsEntriesService.validateNonSellableEntriesItems( await this.itemsEntriesService.validateNonSellableEntriesItems(
tenantId, tenantId,
saleInvoiceDTO.entries, saleInvoiceDTO.entries
); );
// Validate the items entries existance. // Validate the items entries existance.
await this.itemsEntriesService.validateEntriesIdsExistance( await this.itemsEntriesService.validateEntriesIdsExistance(
@@ -270,7 +299,6 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
'SaleInvoice', 'SaleInvoice',
saleInvoiceDTO.entries saleInvoiceDTO.entries
); );
this.logger.info('[sale_invoice] trying to update sale invoice.'); this.logger.info('[sale_invoice] trying to update sale invoice.');
const saleInvoice: ISaleInvoice = await SaleInvoice.query().upsertGraphAndFetch( const saleInvoice: ISaleInvoice = await SaleInvoice.query().upsertGraphAndFetch(
{ {
@@ -280,10 +308,10 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
); );
// Triggers `onSaleInvoiceEdited` event. // Triggers `onSaleInvoiceEdited` event.
await this.eventDispatcher.dispatch(events.saleInvoice.onEdited, { await this.eventDispatcher.dispatch(events.saleInvoice.onEdited, {
saleInvoice,
oldSaleInvoice,
tenantId, tenantId,
saleInvoiceId, saleInvoiceId,
saleInvoice,
oldSaleInvoice,
authorizedUser, authorizedUser,
}); });
return saleInvoice; return saleInvoice;
@@ -303,51 +331,33 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId); const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
// Retrieve details of the given sale invoice id. // Retrieve details of the given sale invoice id.
const saleInvoice = await this.getInvoiceOrThrowError( const oldSaleInvoice = await this.getInvoiceOrThrowError(
tenantId, tenantId,
saleInvoiceId saleInvoiceId
); );
// Throws error in case the sale invoice already published. // Throws error in case the sale invoice already published.
if (saleInvoice.isDelivered) { if (oldSaleInvoice.isDelivered) {
throw new ServiceError(ERRORS.SALE_INVOICE_ALREADY_DELIVERED); throw new ServiceError(ERRORS.SALE_INVOICE_ALREADY_DELIVERED);
} }
// Record the delivered at on the storage. // Record the delivered at on the storage.
await saleInvoiceRepository.update( await saleInvoiceRepository.update(
{ { deliveredAt: moment().toMySqlDateTime(), },
deliveredAt: moment().toMySqlDateTime(),
},
{ id: saleInvoiceId } { id: saleInvoiceId }
); );
} // Triggers `onSaleInvoiceDelivered` event.
this.eventDispatcher.dispatch(events.saleInvoice.onDelivered, {
/** tenantId,
* Validate the sale invoice has no payment entries. saleInvoiceId,
* @param {number} tenantId oldSaleInvoice,
* @param {number} saleInvoiceId });
*/
async validateInvoiceHasNoPaymentEntries(
tenantId: number,
saleInvoiceId: number
) {
const { PaymentReceiveEntry } = this.tenancy.models(tenantId);
// Retrieve the sale invoice associated payment receive entries.
const entries = await PaymentReceiveEntry.query().where(
'invoice_id',
saleInvoiceId
);
if (entries.length > 0) {
throw new ServiceError(ERRORS.INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES);
}
return entries;
} }
/** /**
* Deletes the given sale invoice with associated entries * Deletes the given sale invoice with associated entries
* and journal transactions. * and journal transactions.
* @async * @param {number} tenantId - Tenant id.
* @param {Number} saleInvoiceId - The given sale invoice id. * @param {Number} saleInvoiceId - The given sale invoice id.
* @param {ISystemUser} authorizedUser -
*/ */
public async deleteSaleInvoice( public async deleteSaleInvoice(
tenantId: number, tenantId: number,
@@ -376,15 +386,15 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
tenantId, tenantId,
saleInvoiceId saleInvoiceId
); );
this.logger.info('[sale_invoice] delete sale invoice with entries.'); this.logger.info('[sale_invoice] delete sale invoice with entries.');
await saleInvoiceRepository.deleteById(saleInvoiceId);
await ItemEntry.query() await ItemEntry.query()
.where('reference_id', saleInvoiceId) .where('reference_id', saleInvoiceId)
.where('reference_type', 'SaleInvoice') .where('reference_type', 'SaleInvoice')
.delete(); .delete();
await saleInvoiceRepository.deleteById(saleInvoiceId);
// Triggers `onSaleInvoiceDeleted` event. // Triggers `onSaleInvoiceDeleted` event.
await this.eventDispatcher.dispatch(events.saleInvoice.onDeleted, { await this.eventDispatcher.dispatch(events.saleInvoice.onDeleted, {
tenantId, tenantId,
@@ -408,7 +418,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
tenantId: number, tenantId: number,
saleInvoiceId: number, saleInvoiceId: number,
saleInvoiceDate: Date, saleInvoiceDate: Date,
override?: boolean, override?: boolean
): Promise<void> { ): Promise<void> {
// Gets the next inventory lot number. // Gets the next inventory lot number.
const lotNumber = this.inventoryService.getNextLotNumber(tenantId); const lotNumber = this.inventoryService.getNextLotNumber(tenantId);
@@ -451,41 +461,38 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
} }
/** /**
* Records the journal entries of the given sale invoice just * Writes the sale invoice income journal entries.
* in case the invoice has no inventory items entries. * @param {number} tenantId - Tenant id.
* * @param {ISaleInvoice} saleInvoice - Sale invoice id.
* @param {number} tenantId -
* @param {number} saleInvoiceId
* @param {boolean} override
* @return {Promise<void>}
*/ */
public async recordNonInventoryJournalEntries( public async writesIncomeJournalEntries(
tenantId: number, tenantId: number,
saleInvoiceId: number, saleInvoice: ISaleInvoice & {
authorizedUserId: number, entries: IItemEntry & { item: IItem };
},
override: boolean = false override: boolean = false
): Promise<void> { ): Promise<void> {
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId); const { accountRepository } = this.tenancy.repositories(tenantId);
// Loads the inventory items entries of the given sale invoice. const journal = new JournalPoster(tenantId);
const inventoryEntries = await this.itemsEntriesService.getInventoryEntries( const journalCommands = new JournalCommands(journal);
tenantId,
'SaleInvoice',
saleInvoiceId
);
// Can't continue if the sale invoice has inventory items entries.
if (inventoryEntries.length > 0) return;
const saleInvoice = await saleInvoiceRepository.findOneById( const receivableAccount = await accountRepository.findOne({
saleInvoiceId, slug: 'accounts-receivable',
'entries.item' });
); if (override) {
await this.writeNonInventoryInvoiceEntries( await journalCommands.revertInvoiceIncomeEntries(saleInvoice.id);
tenantId, }
// Records the sale invoice journal entries.
await journalCommands.saleInvoiceIncomeEntries(
saleInvoice, saleInvoice,
authorizedUserId, receivableAccount.id
override
); );
await Promise.all([
journal.deleteEntries(),
journal.saveBalance(),
journal.saveEntries()
]);
} }
/** /**
@@ -519,6 +526,23 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
); );
} }
/**
* Reverting the sale invoice journal entries.
* @param {number} tenantId
* @param {number} saleInvoiceId
* @return {Promise<void>}
*/
public async revertInvoiceJournalEntries(
tenantId: number,
saleInvoiceId: number | number[]
): Promise<void> {
return this.journalService.revertJournalTransactions(
tenantId,
saleInvoiceId,
'SaleInvoice'
);
}
/** /**
* Retrieve sale invoice with associated entries. * Retrieve sale invoice with associated entries.
* @async * @async

View File

@@ -24,7 +24,7 @@ export default class SaleInvoicesCost {
async scheduleComputeCostByItemsIds( async scheduleComputeCostByItemsIds(
tenantId: number, tenantId: number,
inventoryItemsIds: number[], inventoryItemsIds: number[],
startingDate: Date, startingDate: Date
) { ) {
const asyncOpers: Promise<[]>[] = []; const asyncOpers: Promise<[]>[] = [];
@@ -32,7 +32,7 @@ export default class SaleInvoicesCost {
const oper: Promise<[]> = this.inventoryService.scheduleComputeItemCost( const oper: Promise<[]> = this.inventoryService.scheduleComputeItemCost(
tenantId, tenantId,
inventoryItemId, inventoryItemId,
startingDate, startingDate
); );
asyncOpers.push(oper); asyncOpers.push(oper);
}); });
@@ -49,7 +49,7 @@ export default class SaleInvoicesCost {
*/ */
async scheduleComputeCostByInvoiceId( async scheduleComputeCostByInvoiceId(
tenantId: number, tenantId: number,
saleInvoiceId: number, saleInvoiceId: number
) { ) {
const { SaleInvoice } = this.tenancy.models(tenantId); const { SaleInvoice } = this.tenancy.models(tenantId);
@@ -62,7 +62,7 @@ export default class SaleInvoicesCost {
return this.scheduleComputeCostByEntries( return this.scheduleComputeCostByEntries(
tenantId, tenantId,
saleInvoice.entries, saleInvoice.entries,
saleInvoice.invoiceDate, saleInvoice.invoiceDate
); );
} }
@@ -82,24 +82,24 @@ export default class SaleInvoicesCost {
const bill = await Bill.query() const bill = await Bill.query()
.findById(billId) .findById(billId)
.withGraphFetched('entries'); .withGraphFetched('entries');
return this.scheduleComputeCostByEntries( return this.scheduleComputeCostByEntries(
tenantId, tenantId,
bill.entries, bill.entries,
bill.billDate, bill.billDate
); );
} }
/** /**
* Schedules the compute inventory items by the given invoice. * Schedules the compute inventory items by the given invoice.
* @param {number} tenantId * @param {number} tenantId
* @param {ISaleInvoice & { entries: IItemEntry[] }} saleInvoice * @param {ISaleInvoice & { entries: IItemEntry[] }} saleInvoice
* @param {boolean} override * @param {boolean} override
*/ */
async scheduleComputeCostByEntries( async scheduleComputeCostByEntries(
tenantId: number, tenantId: number,
entries: IItemEntry[], entries: IItemEntry[],
startingDate: Date, startingDate: Date
) { ) {
const { Item } = this.tenancy.models(tenantId); const { Item } = this.tenancy.models(tenantId);
@@ -121,105 +121,57 @@ export default class SaleInvoicesCost {
/** /**
* Schedule writing journal entries. * Schedule writing journal entries.
* @param {Date} startingDate * @param {Date} startingDate
* @return {Promise<agenda>} * @return {Promise<agenda>}
*/ */
scheduleWriteJournalEntries(tenantId: number, startingDate?: Date) { scheduleWriteJournalEntries(tenantId: number, startingDate?: Date) {
const agenda = Container.get('agenda'); const agenda = Container.get('agenda');
return agenda.schedule('in 3 seconds', 'rewrite-invoices-journal-entries', { return agenda.schedule('in 3 seconds', 'rewrite-invoices-journal-entries', {
startingDate, tenantId, startingDate,
tenantId,
}); });
} }
/** /**
* Writes journal entries from sales invoices. * Writes journal entries from sales invoices.
* @param {number} tenantId - The tenant id. * @param {number} tenantId - The tenant id.
* @param {Date} startingDate * @param {Date} startingDate - Starting date.
* @param {boolean} override * @param {boolean} override
*/ */
async writeJournalEntries(tenantId: number, startingDate: Date, override: boolean) { async writeInventoryCostJournalEntries(
const { AccountTransaction, SaleInvoice, Account } = this.tenancy.models(tenantId); tenantId: number,
startingDate: Date,
override: boolean
) {
const { InventoryCostLotTracker } = this.tenancy.models(tenantId);
const { accountRepository } = this.tenancy.repositories(tenantId); const { accountRepository } = this.tenancy.repositories(tenantId);
const receivableAccount = await accountRepository.findOne({ const inventoryCostLotTrans = await InventoryCostLotTracker.query()
slug: 'accounts-receivable', .where('direction', 'OUT')
}); .modify('groupedEntriesCost')
const salesInvoices = await SaleInvoice.query() .modify('filterDateRange', startingDate)
.onBuild((builder: any) => { .orderBy('date', 'ASC')
builder.modify('filterDateRange', startingDate); .where('cost', '>', 0)
builder.orderBy('invoice_date', 'ASC'); .withGraphFetched('item');
builder.withGraphFetched('entries.item');
builder.withGraphFetched('costTransactions(groupedEntriesCost)');
});
const accountsDepGraph = await accountRepository.getDependencyGraph(); const accountsDepGraph = await accountRepository.getDependencyGraph();
const journal = new JournalPoster(tenantId, accountsDepGraph);
const journal = new JournalPoster(tenantId, accountsDepGraph);
const journalCommands = new JournalCommands(journal); const journalCommands = new JournalCommands(journal);
if (override) { if (override) {
const oldTransactions = await AccountTransaction.query() await journalCommands.revertInventoryCostJournalEntries(startingDate);
.whereIn('reference_type', ['SaleInvoice'])
.onBuild((builder: any) => {
builder.modify('filterDateRange', startingDate);
})
.withGraphFetched('account.type');
journal.fromTransactions(oldTransactions);
journal.removeEntries();
} }
salesInvoices.forEach((saleInvoice: ISaleInvoice & { inventoryCostLotTrans.forEach(
costTransactions: IInventoryLotCost[], (inventoryCostLot: IInventoryLotCost & { item: IItem }) => {
entries: IItemEntry & { item: IItem }, journalCommands.saleInvoiceInventoryCost(inventoryCostLot);
}) => { }
journalCommands.saleInvoice(saleInvoice, receivableAccount.id); );
});
return Promise.all([ return Promise.all([
journal.deleteEntries(), journal.deleteEntries(),
journal.saveEntries(), journal.saveEntries(),
journal.saveBalance(), journal.saveBalance()
]); ]);
} }
}
/**
* Writes the sale invoice journal entries.
*/
async writeNonInventoryInvoiceEntries(
tenantId: number,
saleInvoice: ISaleInvoice,
authorizedUserId: number,
override: boolean = false,
) {
const { accountRepository } = this.tenancy.repositories(tenantId);
const { AccountTransaction } = this.tenancy.models(tenantId);
// Receivable account.
const receivableAccount = await accountRepository.findOne({
slug: 'accounts-receivable',
});
const journal = new JournalPoster(tenantId);
const journalCommands = new JournalCommands(journal);
if (override) {
const oldTransactions = await AccountTransaction.query()
.where('reference_type', 'SaleInvoice')
.where('reference_id', saleInvoice.id)
.withGraphFetched('account.type');
journal.fromTransactions(oldTransactions);
journal.removeEntries();
}
journalCommands.saleInvoiceNonInventory(
saleInvoice,
receivableAccount.id,
authorizedUserId,
);
await Promise.all([
journal.deleteEntries(),
journal.saveEntries(),
journal.saveBalance(),
]);
}
}

View File

@@ -80,6 +80,7 @@ export default {
onEdited: 'onSaleInvoiceEdited', onEdited: 'onSaleInvoiceEdited',
onDelete: 'onSaleInvoiceDelete', onDelete: 'onSaleInvoiceDelete',
onDeleted: 'onSaleInvoiceDeleted', onDeleted: 'onSaleInvoiceDeleted',
onDelivered: 'onSaleInvoiceDelivered',
onBulkDelete: 'onSaleInvoiceBulkDeleted', onBulkDelete: 'onSaleInvoiceBulkDeleted',
onPublished: 'onSaleInvoicePublished', onPublished: 'onSaleInvoicePublished',
onInventoryTransactionsCreated: 'onInvoiceInventoryTransactionsCreated', onInventoryTransactionsCreated: 'onInvoiceInventoryTransactionsCreated',

View File

@@ -7,6 +7,7 @@ import SettingsService from 'services/Settings/SettingsService';
import SaleEstimateService from 'services/Sales/SalesEstimate'; import SaleEstimateService from 'services/Sales/SalesEstimate';
import SaleInvoicesService from 'services/Sales/SalesInvoices'; import SaleInvoicesService from 'services/Sales/SalesInvoices';
import ItemsEntriesService from 'services/Items/ItemsEntriesService'; import ItemsEntriesService from 'services/Items/ItemsEntriesService';
import SalesInvoicesCost from 'services/Sales/SalesInvoicesCost';
@EventSubscriber() @EventSubscriber()
export default class SaleInvoiceSubscriber { export default class SaleInvoiceSubscriber {
@@ -16,6 +17,7 @@ export default class SaleInvoiceSubscriber {
saleEstimatesService: SaleEstimateService; saleEstimatesService: SaleEstimateService;
saleInvoicesService: SaleInvoicesService; saleInvoicesService: SaleInvoicesService;
itemsEntriesService: ItemsEntriesService; itemsEntriesService: ItemsEntriesService;
salesInvoicesCost: SalesInvoicesCost;
constructor() { constructor() {
this.logger = Container.get('logger'); this.logger = Container.get('logger');
@@ -24,6 +26,7 @@ export default class SaleInvoiceSubscriber {
this.saleEstimatesService = Container.get(SaleEstimateService); this.saleEstimatesService = Container.get(SaleEstimateService);
this.saleInvoicesService = Container.get(SaleInvoicesService); this.saleInvoicesService = Container.get(SaleInvoicesService);
this.itemsEntriesService = Container.get(ItemsEntriesService); this.itemsEntriesService = Container.get(ItemsEntriesService);
this.salesInvoicesCost = Container.get(SalesInvoicesCost);
} }
/** /**
@@ -95,15 +98,38 @@ export default class SaleInvoiceSubscriber {
} }
/** /**
* Records journal entries of the non-inventory invoice. * Handles handle write income journal entries of sale invoice.
*/ */
@On(events.saleInvoice.onCreated) @On(events.saleInvoice.onCreated)
@On(events.saleInvoice.onEdited) public async handleWriteInvoiceIncomeJournalEntries({
public async handleWritingNonInventoryEntries({ tenantId, saleInvoice, authorizedUser }) { tenantId,
await this.saleInvoicesService.recordNonInventoryJournalEntries( saleInvoiceId,
saleInvoice,
authorizedUser,
}) {
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
const saleInvoiceWithItems = await saleInvoiceRepository.findOneById(
saleInvoiceId,
'entries.item'
);
await this.saleInvoicesService.writesIncomeJournalEntries(
tenantId, tenantId,
saleInvoice.id, saleInvoiceWithItems
authorizedUser.id, );
}
/**
* Increments the sale invoice items once the invoice created.
*/
@On(events.saleInvoice.onCreated)
public async handleDecrementSaleInvoiceItemsQuantity({
tenantId,
saleInvoice,
}) {
await this.itemsEntriesService.decrementItemsQuantity(
tenantId,
saleInvoice.entries
); );
} }
@@ -146,6 +172,29 @@ export default class SaleInvoiceSubscriber {
); );
} }
/**
* Records journal entries of the non-inventory invoice.
*/
@On(events.saleInvoice.onEdited)
public async handleRewriteJournalEntriesOnceInvoiceEdit({
tenantId,
saleInvoiceId,
saleInvoice,
authorizedUser,
}) {
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
const saleInvoiceWithItems = await saleInvoiceRepository.findOneById(
saleInvoiceId,
'entries.item'
);
await this.saleInvoicesService.writesIncomeJournalEntries(
tenantId,
saleInvoiceWithItems,
true
);
}
/** /**
* Handles customer balance decrement once sale invoice deleted. * Handles customer balance decrement once sale invoice deleted.
*/ */
@@ -166,6 +215,20 @@ export default class SaleInvoiceSubscriber {
); );
} }
/**
* Handle reverting journal entries once sale invoice delete.
*/
@On(events.saleInvoice.onDelete)
public async handleRevertingInvoiceJournalEntriesOnDelete({
tenantId,
saleInvoiceId,
}) {
await this.saleInvoicesService.revertInvoiceJournalEntries(
tenantId,
saleInvoiceId,
);
}
/** /**
* Handles deleting the inventory transactions once the invoice deleted. * Handles deleting the inventory transactions once the invoice deleted.
*/ */
@@ -203,7 +266,7 @@ export default class SaleInvoiceSubscriber {
saleInvoiceId, saleInvoiceId,
} }
); );
await this.saleInvoicesService.scheduleComputeCostByInvoiceId( await this.salesInvoicesCost.scheduleComputeCostByInvoiceId(
tenantId, tenantId,
saleInvoiceId saleInvoiceId
); );
@@ -222,27 +285,13 @@ export default class SaleInvoiceSubscriber {
const startingDates = map(oldInventoryTransactions, 'date'); const startingDates = map(oldInventoryTransactions, 'date');
const startingDate = head(startingDates); const startingDate = head(startingDates);
await this.saleInvoicesService.scheduleComputeCostByItemsIds( await this.salesInvoicesCost.scheduleComputeCostByItemsIds(
tenantId, tenantId,
inventoryItemsIds, inventoryItemsIds,
startingDate startingDate
); );
} }
/**
* Increments the sale invoice items once the invoice created.
*/
@On(events.saleInvoice.onCreated)
public async handleDecrementSaleInvoiceItemsQuantity({
tenantId,
saleInvoice,
}) {
await this.itemsEntriesService.decrementItemsQuantity(
tenantId,
saleInvoice.entries
);
}
/** /**
* Decrements the sale invoice items once the invoice deleted. * Decrements the sale invoice items once the invoice deleted.
*/ */