mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 12:20:31 +00:00
fix: design flow of writing invoice journal entries.
This commit is contained in:
@@ -272,10 +272,12 @@ export default class SaleInvoicesController extends BaseController {
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const filter: ISalesInvoicesFilter = {
|
||||
const filter = {
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
|
||||
@@ -164,4 +164,5 @@ export default {
|
||||
|
||||
protocol: '',
|
||||
hostname: '',
|
||||
scheduleComputeItemCost: 'in 5 seconds'
|
||||
};
|
||||
@@ -9,7 +9,8 @@ export interface ISaleInvoice {
|
||||
dueAmount: number,
|
||||
customerId: number,
|
||||
entries: IItemEntry[],
|
||||
deliveredAt: string|Date,
|
||||
deliveredAt: string | Date,
|
||||
userId: number,
|
||||
}
|
||||
|
||||
export interface ISaleInvoiceDTO {
|
||||
|
||||
@@ -2,12 +2,11 @@ import { Container } from 'typedi';
|
||||
import SalesInvoicesCost from 'services/Sales/SalesInvoicesCost';
|
||||
|
||||
export default class WriteInvoicesJournalEntries {
|
||||
|
||||
constructor(agenda) {
|
||||
agenda.define(
|
||||
'rewrite-invoices-journal-entries',
|
||||
{ priority: 'normal', concurrency: 1, },
|
||||
this.handler.bind(this),
|
||||
{ priority: 'normal', concurrency: 1 },
|
||||
this.handler.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,15 +16,24 @@ export default class WriteInvoicesJournalEntries {
|
||||
|
||||
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 {
|
||||
await salesInvoicesCost.writeJournalEntries(tenantId, startingDate, true);
|
||||
Logger.info(`Write sales invoices journal entries - completed: ${job.attrs.data}`);
|
||||
await salesInvoicesCost.writeInventoryCostJournalEntries(
|
||||
tenantId,
|
||||
startingDate,
|
||||
true
|
||||
);
|
||||
Logger.info(
|
||||
`Write sales invoices journal entries - completed: ${job.attrs.data}`
|
||||
);
|
||||
done();
|
||||
} catch(e) {
|
||||
Logger.info(`Write sales invoices journal entries: ${job.attrs.data}, error: ${e}`);
|
||||
done(e);
|
||||
} catch (e) {
|
||||
Logger.info(
|
||||
`Write sales invoices journal entries: ${job.attrs.data}, error: ${e}`
|
||||
);
|
||||
done(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import dbManagerFactory from 'loaders/dbManager';
|
||||
import i18n from 'loaders/i18n';
|
||||
import repositoriesLoader from 'loaders/systemRepositories';
|
||||
import Cache from 'services/Cache';
|
||||
import redisLoader from './redisLoader';
|
||||
import rateLimiterLoaders from './rateLimiterLoader';
|
||||
|
||||
export default ({ mongoConnection, knex }) => {
|
||||
|
||||
@@ -23,14 +23,13 @@ export default class InventoryCostLotTracker extends TenantModel {
|
||||
static get modifiers() {
|
||||
return {
|
||||
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_type');
|
||||
|
||||
query.sum('cost as cost');
|
||||
query.groupBy('date');
|
||||
query.groupBy('item_id');
|
||||
},
|
||||
filterDateRange(query, startDate, endDate, type = 'day') {
|
||||
const dateFormat = 'YYYY-MM-DD HH:mm:ss';
|
||||
|
||||
@@ -13,6 +13,7 @@ interface IJournalTransactionsFilter {
|
||||
contactType?: string,
|
||||
referenceType?: string[],
|
||||
referenceId?: number[],
|
||||
index: number|number[]
|
||||
};
|
||||
|
||||
export default class AccountTransactionsRepository extends TenantRepository {
|
||||
@@ -50,6 +51,13 @@ export default class AccountTransactionsRepository extends TenantRepository {
|
||||
if (filter.referenceId && filter.referenceId.length > 0) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { sumBy, chain } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import moment, { LongDateFormatKey } from 'moment';
|
||||
import { IBill, IManualJournalEntry, ISystemUser } from 'interfaces';
|
||||
import JournalPoster from './JournalPoster';
|
||||
import JournalEntry from './JournalEntry';
|
||||
@@ -183,7 +183,7 @@ export default class JournalCommands {
|
||||
async vendorOpeningBalance(
|
||||
vendorId: number,
|
||||
openingBalance: number,
|
||||
openingBalanceAt: Date|string,
|
||||
openingBalanceAt: Date | string,
|
||||
authorizedUserId: ISystemUser
|
||||
) {
|
||||
const { accountRepository } = this.repositories;
|
||||
@@ -225,10 +225,7 @@ export default class JournalCommands {
|
||||
* Writes journal entries of expense model object.
|
||||
* @param {IExpense} expense
|
||||
*/
|
||||
expense(
|
||||
expense: IExpense,
|
||||
userId: number,
|
||||
) {
|
||||
expense(expense: IExpense, userId: number) {
|
||||
const mixinEntry = {
|
||||
referenceType: 'Expense',
|
||||
referenceId: expense.id,
|
||||
@@ -279,14 +276,50 @@ export default class JournalCommands {
|
||||
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.
|
||||
* @param {IManualJournal} manualJournalObj
|
||||
* @param {number} manualJournalId
|
||||
*/
|
||||
async manualJournal(
|
||||
manualJournalObj: IManualJournal,
|
||||
) {
|
||||
async manualJournal(manualJournalObj: IManualJournal) {
|
||||
manualJournalObj.entries.forEach((entry: IManualJournalEntry) => {
|
||||
const jouranlEntry = new JournalEntry({
|
||||
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.
|
||||
* ----------
|
||||
* - Receivable accounts -> Debit -> XXXX
|
||||
* - Income -> Credit -> XXXX
|
||||
*
|
||||
* -------
|
||||
* - Cost of goods sold -> Debit -> YYYY
|
||||
* - Inventory assets -> YYYY
|
||||
* - Inventory assets -> Credit -> YYYY
|
||||
*
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {JournalPoster} journal
|
||||
*/
|
||||
saleInvoice(
|
||||
saleInvoice: ISaleInvoice & {
|
||||
costTransactions: IInventoryLotCost[];
|
||||
entries: IItemEntry & { item: IItem };
|
||||
},
|
||||
receivableAccountsId: number
|
||||
saleInvoiceInventoryCost(
|
||||
inventoryCostLot: IInventoryLotCost & { item: IItem }
|
||||
) {
|
||||
let inventoryTotal: number = 0;
|
||||
|
||||
const commonEntry = {
|
||||
referenceType: 'SaleInvoice',
|
||||
referenceId: saleInvoice.id,
|
||||
date: saleInvoice.invoiceDate,
|
||||
referenceId: inventoryCostLot.transactionId,
|
||||
date: inventoryCostLot.date,
|
||||
};
|
||||
const costTransactions: Map<number, number> = new Map(
|
||||
saleInvoice.costTransactions.map((trans: IInventoryLotCost) => [
|
||||
trans.entryId,
|
||||
trans.cost,
|
||||
])
|
||||
);
|
||||
// XXX Debit - Receivable account.
|
||||
const receivableEntry = new JournalEntry({
|
||||
// XXX Debit - Cost account.
|
||||
const costEntry = new JournalEntry({
|
||||
...commonEntry,
|
||||
debit: saleInvoice.balance,
|
||||
account: receivableAccountsId,
|
||||
index: 1,
|
||||
debit: inventoryCostLot.cost,
|
||||
account: inventoryCostLot.item.costAccountId,
|
||||
index: 3,
|
||||
});
|
||||
this.journal.debit(receivableEntry);
|
||||
|
||||
saleInvoice.entries.forEach(
|
||||
(entry: IItemEntry & { item: IItem }, index) => {
|
||||
const cost: number = costTransactions.get(entry.id);
|
||||
const income: number = entry.quantity * entry.rate;
|
||||
|
||||
if (entry.item.type === 'inventory' && cost) {
|
||||
// XXX Debit - Cost account.
|
||||
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);
|
||||
}
|
||||
}
|
||||
);
|
||||
// XXX Credit - Inventory account.
|
||||
const inventoryEntry = new JournalEntry({
|
||||
...commonEntry,
|
||||
credit: inventoryCostLot.cost,
|
||||
account: inventoryCostLot.item.inventoryAccountId,
|
||||
index: 4,
|
||||
});
|
||||
this.journal.credit(inventoryEntry);
|
||||
this.journal.debit(costEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the sale invoice income journal entries.
|
||||
* -----
|
||||
* - Receivable accounts -> Debit -> XXXX
|
||||
* - Income -> Credit -> XXXX
|
||||
*
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {number} receivableAccountsId
|
||||
* @param {number} authorizedUserId
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {number} receivableAccountsId
|
||||
* @param {number} authorizedUserId
|
||||
*/
|
||||
saleInvoiceNonInventory(
|
||||
async saleInvoiceIncomeEntries(
|
||||
saleInvoice: ISaleInvoice & {
|
||||
entries: IItemEntry & { item: IItem };
|
||||
},
|
||||
receivableAccountsId: number,
|
||||
authorizedUserId: number,
|
||||
) {
|
||||
receivableAccountId: number
|
||||
): Promise<void> {
|
||||
const commonEntry = {
|
||||
referenceType: 'SaleInvoice',
|
||||
referenceId: saleInvoice.id,
|
||||
date: saleInvoice.invoiceDate,
|
||||
userId: authorizedUserId,
|
||||
userId: saleInvoice.userId,
|
||||
};
|
||||
|
||||
// XXX Debit - Receivable account.
|
||||
const receivableEntry = new JournalEntry({
|
||||
...commonEntry,
|
||||
debit: saleInvoice.balance,
|
||||
account: receivableAccountsId,
|
||||
account: receivableAccountId,
|
||||
index: 1,
|
||||
});
|
||||
this.journal.debit(receivableEntry);
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
IContactEditDTO,
|
||||
IContact,
|
||||
ISaleInvoice,
|
||||
ISystemService,
|
||||
ISystemUser,
|
||||
} from 'interfaces';
|
||||
import { ServiceError } from 'exceptions';
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import { Container, Service, Inject } from 'typedi';
|
||||
import { pick } from 'lodash';
|
||||
import config from 'config';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
} from 'decorators/eventDispatcher';
|
||||
import { IInventoryLotCost, IInventoryTransaction, IItem, IItemEntry } from 'interfaces'
|
||||
import {
|
||||
IInventoryLotCost,
|
||||
IInventoryTransaction,
|
||||
IItem,
|
||||
IItemEntry,
|
||||
} from 'interfaces';
|
||||
import InventoryAverageCost from 'services/Inventory/InventoryAverageCost';
|
||||
import InventoryCostLotTracker from 'services/Inventory/InventoryCostLotTracker';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
@@ -27,9 +33,9 @@ export default class InventoryService {
|
||||
itemEntries: IItemEntry[],
|
||||
transactionType: string,
|
||||
transactionId: number,
|
||||
direction: 'IN'|'OUT',
|
||||
date: Date|string,
|
||||
lotNumber: number,
|
||||
direction: 'IN' | 'OUT',
|
||||
date: Date | string,
|
||||
lotNumber: number
|
||||
) {
|
||||
return itemEntries.map((entry: IItemEntry) => ({
|
||||
...pick(entry, ['itemId', 'quantity', 'rate']),
|
||||
@@ -62,13 +68,21 @@ export default class InventoryService {
|
||||
let costMethodComputer: IInventoryCostMethod;
|
||||
|
||||
// Switch between methods based on the item cost method.
|
||||
switch('AVG') {
|
||||
switch ('AVG') {
|
||||
case 'FIFO':
|
||||
case 'LIFO':
|
||||
costMethodComputer = new InventoryCostLotTracker(tenantId, fromDate, itemId);
|
||||
costMethodComputer = new InventoryCostLotTracker(
|
||||
tenantId,
|
||||
fromDate,
|
||||
itemId
|
||||
);
|
||||
break;
|
||||
case 'AVG':
|
||||
costMethodComputer = new InventoryAverageCost(tenantId, fromDate, itemId);
|
||||
costMethodComputer = new InventoryAverageCost(
|
||||
tenantId,
|
||||
fromDate,
|
||||
itemId
|
||||
);
|
||||
break;
|
||||
}
|
||||
return costMethodComputer.computeItemCost();
|
||||
@@ -77,20 +91,24 @@ export default class InventoryService {
|
||||
/**
|
||||
* Schedule item cost compute job.
|
||||
* @param {number} tenantId
|
||||
* @param {number} itemId
|
||||
* @param {Date} startingDate
|
||||
* @param {number} itemId
|
||||
* @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');
|
||||
|
||||
// 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.
|
||||
await agenda.cancel({
|
||||
name: 'compute-item-cost',
|
||||
nextRunAt: { $ne: null },
|
||||
'data.tenantId': tenantId,
|
||||
'data.itemId': itemId,
|
||||
'data.startingDate': { "$gt": startingDate }
|
||||
'data.startingDate': { $gt: startingDate },
|
||||
});
|
||||
|
||||
// Retrieve any `compute-item-cost` in the queue has lower starting date
|
||||
@@ -100,23 +118,29 @@ export default class InventoryService {
|
||||
nextRunAt: { $ne: null },
|
||||
'data.tenantId': tenantId,
|
||||
'data.itemId': itemId,
|
||||
'data.startingDate': { "$lte": startingDate }
|
||||
'data.startingDate': { $lte: startingDate },
|
||||
});
|
||||
if (dependsJobs.length === 0) {
|
||||
await agenda.schedule('in 30 seconds', 'compute-item-cost', {
|
||||
startingDate, itemId, tenantId,
|
||||
});
|
||||
await agenda.schedule(
|
||||
config.scheduleComputeItemCost,
|
||||
'compute-item-cost',
|
||||
{
|
||||
startingDate,
|
||||
itemId,
|
||||
tenantId,
|
||||
}
|
||||
);
|
||||
|
||||
// Triggers `onComputeItemCostJobScheduled` event.
|
||||
await this.eventDispatcher.dispatch(
|
||||
events.inventory.onComputeItemCostJobScheduled,
|
||||
{ startingDate, itemId, tenantId },
|
||||
{ startingDate, itemId, tenantId }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the inventory transactions.
|
||||
* Records the inventory transactions.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {Bill} bill - Bill model object.
|
||||
* @param {number} billId - Bill id.
|
||||
@@ -125,27 +149,23 @@ export default class InventoryService {
|
||||
async recordInventoryTransactions(
|
||||
tenantId: number,
|
||||
inventoryEntries: IInventoryTransaction[],
|
||||
deleteOld: boolean,
|
||||
deleteOld: boolean
|
||||
): Promise<void> {
|
||||
inventoryEntries.forEach(async (entry: IInventoryTransaction) => {
|
||||
await this.recordInventoryTransaction(
|
||||
tenantId,
|
||||
entry,
|
||||
deleteOld,
|
||||
);
|
||||
await this.recordInventoryTransaction(tenantId, entry, deleteOld);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} tenantId
|
||||
* @param {IInventoryTransaction} inventoryEntry
|
||||
* @param {boolean} deleteOld
|
||||
*
|
||||
* @param {number} tenantId
|
||||
* @param {IInventoryTransaction} inventoryEntry
|
||||
* @param {boolean} deleteOld
|
||||
*/
|
||||
async recordInventoryTransaction(
|
||||
tenantId: number,
|
||||
inventoryEntry: IInventoryTransaction,
|
||||
deleteOld: boolean = false,
|
||||
deleteOld: boolean = false
|
||||
): Promise<IInventoryTransaction> {
|
||||
const { InventoryTransaction, Item } = this.tenancy.models(tenantId);
|
||||
|
||||
@@ -153,7 +173,7 @@ export default class InventoryService {
|
||||
await this.deleteInventoryTransactions(
|
||||
tenantId,
|
||||
inventoryEntry.transactionId,
|
||||
inventoryEntry.transactionType,
|
||||
inventoryEntry.transactionType
|
||||
);
|
||||
}
|
||||
return InventoryTransaction.query().insert({
|
||||
@@ -165,14 +185,14 @@ export default class InventoryService {
|
||||
/**
|
||||
* Deletes the given inventory transactions.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {string} transactionType
|
||||
* @param {number} transactionId
|
||||
* @param {string} transactionType
|
||||
* @param {number} transactionId
|
||||
* @return {Promise}
|
||||
*/
|
||||
async deleteInventoryTransactions(
|
||||
tenantId: number,
|
||||
transactionId: number,
|
||||
transactionType: string,
|
||||
transactionType: string
|
||||
): Promise<void> {
|
||||
const { InventoryTransaction } = this.tenancy.models(tenantId);
|
||||
|
||||
@@ -184,16 +204,16 @@ export default class InventoryService {
|
||||
|
||||
/**
|
||||
* Records the inventory cost lot transaction.
|
||||
* @param {number} tenantId
|
||||
* @param {IInventoryLotCost} inventoryLotEntry
|
||||
* @param {number} tenantId
|
||||
* @param {IInventoryLotCost} inventoryLotEntry
|
||||
* @return {Promise<IInventoryLotCost>}
|
||||
*/
|
||||
async recordInventoryCostLotTransaction(
|
||||
tenantId: number,
|
||||
inventoryLotEntry: IInventoryLotCost,
|
||||
inventoryLotEntry: IInventoryLotCost
|
||||
): Promise<void> {
|
||||
const { InventoryCostLotTracker } = this.tenancy.models(tenantId);
|
||||
|
||||
|
||||
return InventoryCostLotTracker.query().insert({
|
||||
...inventoryLotEntry,
|
||||
});
|
||||
@@ -209,13 +229,14 @@ export default class InventoryService {
|
||||
const LOT_NUMBER_KEY = 'lot_number_increment';
|
||||
const storedLotNumber = settings.find({ key: LOT_NUMBER_KEY });
|
||||
|
||||
return (storedLotNumber && storedLotNumber.value) ?
|
||||
parseInt(storedLotNumber.value, 10) : 1;
|
||||
return storedLotNumber && storedLotNumber.value
|
||||
? parseInt(storedLotNumber.value, 10)
|
||||
: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the next inventory LOT number.
|
||||
* @param {number} tenantId
|
||||
* @param {number} tenantId
|
||||
* @return {Promise<number>}
|
||||
*/
|
||||
async incrementNextLotNumber(tenantId: number) {
|
||||
@@ -236,4 +257,4 @@ export default class InventoryService {
|
||||
|
||||
return lotNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export default class JournalPosterService {
|
||||
tenantId: number,
|
||||
referenceId: number|number[],
|
||||
referenceType: string
|
||||
) {
|
||||
): Promise<void> {
|
||||
const journal = new JournalPoster(tenantId);
|
||||
const journalCommand = new JournalCommands(journal);
|
||||
|
||||
|
||||
@@ -13,10 +13,13 @@ import {
|
||||
IPaginationMeta,
|
||||
IFilterMeta,
|
||||
ISystemUser,
|
||||
IItem,
|
||||
IItemEntry,
|
||||
} from 'interfaces';
|
||||
import JournalPoster from 'services/Accounting/JournalPoster';
|
||||
import JournalCommands from 'services/Accounting/JournalCommands';
|
||||
import events from 'subscribers/events';
|
||||
import InventoryService from 'services/Inventory/Inventory';
|
||||
import SalesInvoicesCost from 'services/Sales/SalesInvoicesCost';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||
import { formatDateFields } from 'utils';
|
||||
@@ -25,6 +28,7 @@ import ItemsService from 'services/Items/ItemsService';
|
||||
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
||||
import CustomersService from 'services/Contacts/CustomersService';
|
||||
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
||||
import JournalPosterService from './JournalPosterService';
|
||||
|
||||
const ERRORS = {
|
||||
INVOICE_NUMBER_NOT_UNIQUE: 'INVOICE_NUMBER_NOT_UNIQUE',
|
||||
@@ -42,7 +46,7 @@ const ERRORS = {
|
||||
* @service
|
||||
*/
|
||||
@Service()
|
||||
export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
export default class SaleInvoicesService {
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
@@ -70,6 +74,9 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
@Inject()
|
||||
saleEstimatesService: SaleEstimateService;
|
||||
|
||||
@Inject()
|
||||
journalService: JournalPosterService;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param {Request} req
|
||||
@@ -148,7 +177,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
balance,
|
||||
paymentAmount: 0,
|
||||
entries: saleInvoiceDTO.entries.map((entry) => ({
|
||||
reference_type: 'SaleInvoice',
|
||||
referenceType: 'SaleInvoice',
|
||||
...omit(entry, ['amount', 'id']),
|
||||
})),
|
||||
};
|
||||
@@ -208,7 +237,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
});
|
||||
this.logger.info('[sale_invoice] successfully inserted.', {
|
||||
tenantId,
|
||||
saleInvoice,
|
||||
saleInvoiceId: saleInvoice.id,
|
||||
});
|
||||
|
||||
return saleInvoice;
|
||||
@@ -238,19 +267,19 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
const saleInvoiceObj = this.transformDTOToModel(
|
||||
tenantId,
|
||||
saleInvoiceDTO,
|
||||
oldSaleInvoice,
|
||||
oldSaleInvoice
|
||||
);
|
||||
// Validate customer existance.
|
||||
await this.customersService.getCustomerByIdOrThrowError(
|
||||
tenantId,
|
||||
saleInvoiceDTO.customerId,
|
||||
saleInvoiceDTO.customerId
|
||||
);
|
||||
// Validate sale invoice number uniquiness.
|
||||
if (saleInvoiceDTO.invoiceNo) {
|
||||
await this.validateInvoiceNumberUnique(
|
||||
tenantId,
|
||||
saleInvoiceDTO.invoiceNo,
|
||||
saleInvoiceId,
|
||||
saleInvoiceId
|
||||
);
|
||||
}
|
||||
// Validate items ids existance.
|
||||
@@ -261,7 +290,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
// Validate non-sellable entries items.
|
||||
await this.itemsEntriesService.validateNonSellableEntriesItems(
|
||||
tenantId,
|
||||
saleInvoiceDTO.entries,
|
||||
saleInvoiceDTO.entries
|
||||
);
|
||||
// Validate the items entries existance.
|
||||
await this.itemsEntriesService.validateEntriesIdsExistance(
|
||||
@@ -270,7 +299,6 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
'SaleInvoice',
|
||||
saleInvoiceDTO.entries
|
||||
);
|
||||
|
||||
this.logger.info('[sale_invoice] trying to update sale invoice.');
|
||||
const saleInvoice: ISaleInvoice = await SaleInvoice.query().upsertGraphAndFetch(
|
||||
{
|
||||
@@ -280,10 +308,10 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
);
|
||||
// Triggers `onSaleInvoiceEdited` event.
|
||||
await this.eventDispatcher.dispatch(events.saleInvoice.onEdited, {
|
||||
saleInvoice,
|
||||
oldSaleInvoice,
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
saleInvoice,
|
||||
oldSaleInvoice,
|
||||
authorizedUser,
|
||||
});
|
||||
return saleInvoice;
|
||||
@@ -303,51 +331,33 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
// Retrieve details of the given sale invoice id.
|
||||
const saleInvoice = await this.getInvoiceOrThrowError(
|
||||
const oldSaleInvoice = await this.getInvoiceOrThrowError(
|
||||
tenantId,
|
||||
saleInvoiceId
|
||||
);
|
||||
|
||||
// Throws error in case the sale invoice already published.
|
||||
if (saleInvoice.isDelivered) {
|
||||
if (oldSaleInvoice.isDelivered) {
|
||||
throw new ServiceError(ERRORS.SALE_INVOICE_ALREADY_DELIVERED);
|
||||
}
|
||||
// Record the delivered at on the storage.
|
||||
await saleInvoiceRepository.update(
|
||||
{
|
||||
deliveredAt: moment().toMySqlDateTime(),
|
||||
},
|
||||
{ deliveredAt: moment().toMySqlDateTime(), },
|
||||
{ id: saleInvoiceId }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
// Triggers `onSaleInvoiceDelivered` event.
|
||||
this.eventDispatcher.dispatch(events.saleInvoice.onDelivered, {
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
oldSaleInvoice,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given sale invoice with associated entries
|
||||
* and journal transactions.
|
||||
* @async
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {Number} saleInvoiceId - The given sale invoice id.
|
||||
* @param {ISystemUser} authorizedUser -
|
||||
*/
|
||||
public async deleteSaleInvoice(
|
||||
tenantId: number,
|
||||
@@ -376,15 +386,15 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
tenantId,
|
||||
saleInvoiceId
|
||||
);
|
||||
|
||||
this.logger.info('[sale_invoice] delete sale invoice with entries.');
|
||||
await saleInvoiceRepository.deleteById(saleInvoiceId);
|
||||
|
||||
await ItemEntry.query()
|
||||
.where('reference_id', saleInvoiceId)
|
||||
.where('reference_type', 'SaleInvoice')
|
||||
.delete();
|
||||
|
||||
await saleInvoiceRepository.deleteById(saleInvoiceId);
|
||||
|
||||
// Triggers `onSaleInvoiceDeleted` event.
|
||||
await this.eventDispatcher.dispatch(events.saleInvoice.onDeleted, {
|
||||
tenantId,
|
||||
@@ -408,7 +418,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
saleInvoiceDate: Date,
|
||||
override?: boolean,
|
||||
override?: boolean
|
||||
): Promise<void> {
|
||||
// Gets the next inventory lot number.
|
||||
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
|
||||
* in case the invoice has no inventory items entries.
|
||||
*
|
||||
* @param {number} tenantId -
|
||||
* @param {number} saleInvoiceId
|
||||
* @param {boolean} override
|
||||
* @return {Promise<void>}
|
||||
* Writes the sale invoice income journal entries.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {ISaleInvoice} saleInvoice - Sale invoice id.
|
||||
*/
|
||||
public async recordNonInventoryJournalEntries(
|
||||
public async writesIncomeJournalEntries(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
authorizedUserId: number,
|
||||
saleInvoice: ISaleInvoice & {
|
||||
entries: IItemEntry & { item: IItem };
|
||||
},
|
||||
override: boolean = false
|
||||
): 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 inventoryEntries = await this.itemsEntriesService.getInventoryEntries(
|
||||
tenantId,
|
||||
'SaleInvoice',
|
||||
saleInvoiceId
|
||||
);
|
||||
// Can't continue if the sale invoice has inventory items entries.
|
||||
if (inventoryEntries.length > 0) return;
|
||||
const journal = new JournalPoster(tenantId);
|
||||
const journalCommands = new JournalCommands(journal);
|
||||
|
||||
const saleInvoice = await saleInvoiceRepository.findOneById(
|
||||
saleInvoiceId,
|
||||
'entries.item'
|
||||
);
|
||||
await this.writeNonInventoryInvoiceEntries(
|
||||
tenantId,
|
||||
const receivableAccount = await accountRepository.findOne({
|
||||
slug: 'accounts-receivable',
|
||||
});
|
||||
if (override) {
|
||||
await journalCommands.revertInvoiceIncomeEntries(saleInvoice.id);
|
||||
}
|
||||
// Records the sale invoice journal entries.
|
||||
await journalCommands.saleInvoiceIncomeEntries(
|
||||
saleInvoice,
|
||||
authorizedUserId,
|
||||
override
|
||||
receivableAccount.id
|
||||
);
|
||||
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.
|
||||
* @async
|
||||
|
||||
@@ -24,7 +24,7 @@ export default class SaleInvoicesCost {
|
||||
async scheduleComputeCostByItemsIds(
|
||||
tenantId: number,
|
||||
inventoryItemsIds: number[],
|
||||
startingDate: Date,
|
||||
startingDate: Date
|
||||
) {
|
||||
const asyncOpers: Promise<[]>[] = [];
|
||||
|
||||
@@ -32,7 +32,7 @@ export default class SaleInvoicesCost {
|
||||
const oper: Promise<[]> = this.inventoryService.scheduleComputeItemCost(
|
||||
tenantId,
|
||||
inventoryItemId,
|
||||
startingDate,
|
||||
startingDate
|
||||
);
|
||||
asyncOpers.push(oper);
|
||||
});
|
||||
@@ -49,7 +49,7 @@ export default class SaleInvoicesCost {
|
||||
*/
|
||||
async scheduleComputeCostByInvoiceId(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
saleInvoiceId: number
|
||||
) {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
@@ -62,7 +62,7 @@ export default class SaleInvoicesCost {
|
||||
return this.scheduleComputeCostByEntries(
|
||||
tenantId,
|
||||
saleInvoice.entries,
|
||||
saleInvoice.invoiceDate,
|
||||
saleInvoice.invoiceDate
|
||||
);
|
||||
}
|
||||
|
||||
@@ -82,24 +82,24 @@ export default class SaleInvoicesCost {
|
||||
const bill = await Bill.query()
|
||||
.findById(billId)
|
||||
.withGraphFetched('entries');
|
||||
|
||||
|
||||
return this.scheduleComputeCostByEntries(
|
||||
tenantId,
|
||||
bill.entries,
|
||||
bill.billDate,
|
||||
bill.billDate
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the compute inventory items by the given invoice.
|
||||
* @param {number} tenantId
|
||||
* @param {ISaleInvoice & { entries: IItemEntry[] }} saleInvoice
|
||||
* @param {boolean} override
|
||||
* @param {number} tenantId
|
||||
* @param {ISaleInvoice & { entries: IItemEntry[] }} saleInvoice
|
||||
* @param {boolean} override
|
||||
*/
|
||||
async scheduleComputeCostByEntries(
|
||||
tenantId: number,
|
||||
entries: IItemEntry[],
|
||||
startingDate: Date,
|
||||
startingDate: Date
|
||||
) {
|
||||
const { Item } = this.tenancy.models(tenantId);
|
||||
|
||||
@@ -121,105 +121,57 @@ export default class SaleInvoicesCost {
|
||||
|
||||
/**
|
||||
* Schedule writing journal entries.
|
||||
* @param {Date} startingDate
|
||||
* @param {Date} startingDate
|
||||
* @return {Promise<agenda>}
|
||||
*/
|
||||
scheduleWriteJournalEntries(tenantId: number, startingDate?: Date) {
|
||||
const agenda = Container.get('agenda');
|
||||
|
||||
return agenda.schedule('in 3 seconds', 'rewrite-invoices-journal-entries', {
|
||||
startingDate, tenantId,
|
||||
startingDate,
|
||||
tenantId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes journal entries from sales invoices.
|
||||
* @param {number} tenantId - The tenant id.
|
||||
* @param {Date} startingDate
|
||||
* @param {boolean} override
|
||||
* @param {Date} startingDate - Starting date.
|
||||
* @param {boolean} override
|
||||
*/
|
||||
async writeJournalEntries(tenantId: number, startingDate: Date, override: boolean) {
|
||||
const { AccountTransaction, SaleInvoice, Account } = this.tenancy.models(tenantId);
|
||||
async writeInventoryCostJournalEntries(
|
||||
tenantId: number,
|
||||
startingDate: Date,
|
||||
override: boolean
|
||||
) {
|
||||
const { InventoryCostLotTracker } = this.tenancy.models(tenantId);
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
const receivableAccount = await accountRepository.findOne({
|
||||
slug: 'accounts-receivable',
|
||||
});
|
||||
const salesInvoices = await SaleInvoice.query()
|
||||
.onBuild((builder: any) => {
|
||||
builder.modify('filterDateRange', startingDate);
|
||||
builder.orderBy('invoice_date', 'ASC');
|
||||
const inventoryCostLotTrans = await InventoryCostLotTracker.query()
|
||||
.where('direction', 'OUT')
|
||||
.modify('groupedEntriesCost')
|
||||
.modify('filterDateRange', startingDate)
|
||||
.orderBy('date', 'ASC')
|
||||
.where('cost', '>', 0)
|
||||
.withGraphFetched('item');
|
||||
|
||||
builder.withGraphFetched('entries.item');
|
||||
builder.withGraphFetched('costTransactions(groupedEntriesCost)');
|
||||
});
|
||||
const accountsDepGraph = await accountRepository.getDependencyGraph();
|
||||
const journal = new JournalPoster(tenantId, accountsDepGraph);
|
||||
|
||||
const journal = new JournalPoster(tenantId, accountsDepGraph);
|
||||
const journalCommands = new JournalCommands(journal);
|
||||
|
||||
if (override) {
|
||||
const oldTransactions = await AccountTransaction.query()
|
||||
.whereIn('reference_type', ['SaleInvoice'])
|
||||
.onBuild((builder: any) => {
|
||||
builder.modify('filterDateRange', startingDate);
|
||||
})
|
||||
.withGraphFetched('account.type');
|
||||
|
||||
journal.fromTransactions(oldTransactions);
|
||||
journal.removeEntries();
|
||||
await journalCommands.revertInventoryCostJournalEntries(startingDate);
|
||||
}
|
||||
salesInvoices.forEach((saleInvoice: ISaleInvoice & {
|
||||
costTransactions: IInventoryLotCost[],
|
||||
entries: IItemEntry & { item: IItem },
|
||||
}) => {
|
||||
journalCommands.saleInvoice(saleInvoice, receivableAccount.id);
|
||||
});
|
||||
inventoryCostLotTrans.forEach(
|
||||
(inventoryCostLot: IInventoryLotCost & { item: IItem }) => {
|
||||
journalCommands.saleInvoiceInventoryCost(inventoryCostLot);
|
||||
}
|
||||
);
|
||||
return Promise.all([
|
||||
journal.deleteEntries(),
|
||||
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(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ export default {
|
||||
onEdited: 'onSaleInvoiceEdited',
|
||||
onDelete: 'onSaleInvoiceDelete',
|
||||
onDeleted: 'onSaleInvoiceDeleted',
|
||||
onDelivered: 'onSaleInvoiceDelivered',
|
||||
onBulkDelete: 'onSaleInvoiceBulkDeleted',
|
||||
onPublished: 'onSaleInvoicePublished',
|
||||
onInventoryTransactionsCreated: 'onInvoiceInventoryTransactionsCreated',
|
||||
|
||||
@@ -7,6 +7,7 @@ import SettingsService from 'services/Settings/SettingsService';
|
||||
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
||||
import SaleInvoicesService from 'services/Sales/SalesInvoices';
|
||||
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
||||
import SalesInvoicesCost from 'services/Sales/SalesInvoicesCost';
|
||||
|
||||
@EventSubscriber()
|
||||
export default class SaleInvoiceSubscriber {
|
||||
@@ -16,6 +17,7 @@ export default class SaleInvoiceSubscriber {
|
||||
saleEstimatesService: SaleEstimateService;
|
||||
saleInvoicesService: SaleInvoicesService;
|
||||
itemsEntriesService: ItemsEntriesService;
|
||||
salesInvoicesCost: SalesInvoicesCost;
|
||||
|
||||
constructor() {
|
||||
this.logger = Container.get('logger');
|
||||
@@ -24,6 +26,7 @@ export default class SaleInvoiceSubscriber {
|
||||
this.saleEstimatesService = Container.get(SaleEstimateService);
|
||||
this.saleInvoicesService = Container.get(SaleInvoicesService);
|
||||
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.onEdited)
|
||||
public async handleWritingNonInventoryEntries({ tenantId, saleInvoice, authorizedUser }) {
|
||||
await this.saleInvoicesService.recordNonInventoryJournalEntries(
|
||||
public async handleWriteInvoiceIncomeJournalEntries({
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
saleInvoice,
|
||||
authorizedUser,
|
||||
}) {
|
||||
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
const saleInvoiceWithItems = await saleInvoiceRepository.findOneById(
|
||||
saleInvoiceId,
|
||||
'entries.item'
|
||||
);
|
||||
await this.saleInvoicesService.writesIncomeJournalEntries(
|
||||
tenantId,
|
||||
saleInvoice.id,
|
||||
authorizedUser.id,
|
||||
saleInvoiceWithItems
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -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.
|
||||
*/
|
||||
@@ -203,7 +266,7 @@ export default class SaleInvoiceSubscriber {
|
||||
saleInvoiceId,
|
||||
}
|
||||
);
|
||||
await this.saleInvoicesService.scheduleComputeCostByInvoiceId(
|
||||
await this.salesInvoicesCost.scheduleComputeCostByInvoiceId(
|
||||
tenantId,
|
||||
saleInvoiceId
|
||||
);
|
||||
@@ -222,27 +285,13 @@ export default class SaleInvoiceSubscriber {
|
||||
const startingDates = map(oldInventoryTransactions, 'date');
|
||||
const startingDate = head(startingDates);
|
||||
|
||||
await this.saleInvoicesService.scheduleComputeCostByItemsIds(
|
||||
await this.salesInvoicesCost.scheduleComputeCostByItemsIds(
|
||||
tenantId,
|
||||
inventoryItemsIds,
|
||||
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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user