- fix: Schedule write journal entries after item compute cost.

- fix: active vouchers query.
- fix: remove babel loader in server-side.
This commit is contained in:
Ahmed Bouhuolia
2020-08-29 22:11:42 +02:00
parent e4270dc039
commit 74321a2886
12 changed files with 217 additions and 104 deletions

View File

@@ -84,8 +84,7 @@ export default class InventoryCostLotTracker extends InventoryCostMethod impleme
*/
private async fetchInvINTransactions() {
const commonBuilder = (builder: any) => {
builder.where('direction', 'IN');
builder.orderBy('date', 'ASC');
builder.orderBy('date', (this.costMethod === 'LIFO') ? 'DESC': 'ASC');
builder.where('item_id', this.itemId);
};
const afterInvTransactions: IInventoryTransaction[] =
@@ -93,8 +92,8 @@ export default class InventoryCostLotTracker extends InventoryCostMethod impleme
.query()
.modify('filterDateRange', this.startingDate)
.orderByRaw("FIELD(direction, 'IN', 'OUT')")
.orderBy('lot_number', (this.costMethod === 'LIFO') ? 'DESC' : 'ASC')
.onBuild(commonBuilder)
.orderBy('lot_number', (this.costMethod === 'LIFO') ? 'DESC' : 'ASC')
.withGraphFetched('item');
const availiableINLots: IInventoryLotCost[] =
@@ -102,7 +101,8 @@ export default class InventoryCostLotTracker extends InventoryCostMethod impleme
.query()
.modify('filterDateRange', null, this.startingDate)
.orderBy('date', 'ASC')
.orderBy('lot_number', 'ASC')
.where('direction', 'IN')
.orderBy('lot_number', 'ASC')
.onBuild(commonBuilder)
.whereNot('remaining', 0);

View File

@@ -348,10 +348,18 @@ export default class BillsService extends SalesInvoicesCost {
* @param {IBill} bill
* @return {Promise}
*/
static scheduleComputeBillItemsCost(bill) {
return this.scheduleComputeItemsCost(
bill.entries.map((e) => e.item_id),
bill.bill_date,
);
static async scheduleComputeBillItemsCost(bill) {
const billItemsIds = bill.entries.map((entry) => entry.item_id);
// Retrieves inventory items only.
const inventoryItems = await Item.tenant().query()
.whereIn('id', billItemsIds)
.where('type', 'inventory');
const inventoryItemsIds = inventoryItems.map(i => i.id);
if (inventoryItemsIds.length > 0) {
await this.scheduleComputeItemsCost(inventoryItemsIds, bill.bill_date);
}
}
}

View File

@@ -1,4 +1,4 @@
import { omit, sumBy, difference, pick } from 'lodash';
import { omit, sumBy, difference, pick, chain } from 'lodash';
import {
SaleInvoice,
AccountTransaction,
@@ -6,12 +6,14 @@ import {
Account,
ItemEntry,
Customer,
Item,
} from '@/models';
import JournalPoster from '@/services/Accounting/JournalPoster';
import HasItemsEntries from '@/services/Sales/HasItemsEntries';
import CustomerRepository from '@/repositories/CustomerRepository';
import InventoryService from '@/services/Inventory/Inventory';
import SalesInvoicesCost from '@/services/Sales/SalesInvoicesCost';
import { ISaleInvoice, ISaleInvoiceOTD, IItemEntry } from '@/interfaces';
import { formatDateFields } from '@/utils';
/**
@@ -26,11 +28,11 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* @param {ISaleInvoice}
* @return {ISaleInvoice}
*/
static async createSaleInvoice(saleInvoiceDTO: any) {
static async createSaleInvoice(saleInvoiceDTO: ISaleInvoiceOTD) {
const balance = sumBy(saleInvoiceDTO.entries, 'amount');
const invLotNumber = await InventoryService.nextLotNumber();
const saleInvoice = {
...formatDateFields(saleInvoiceDTO, ['invoice_date', 'due_date']),
const saleInvoice: ISaleInvoice = {
...formatDateFields(saleInvoiceDTO, ['invoiceDate', 'dueDate']),
balance,
paymentAmount: 0,
invLotNumber,
@@ -70,7 +72,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
);
// Schedule sale invoice re-compute based on the item cost
// method and starting date.
await this.scheduleComputeInvoiceItemsCost(saleInvoice);
await this.scheduleComputeInvoiceItemsCost(storedInvoice.id);
return storedInvoice;
}
@@ -92,7 +94,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
balance,
invLotNumber: oldSaleInvoice.invLotNumber,
};
const updatedSaleInvoices = await SaleInvoice.tenant()
const updatedSaleInvoices: ISaleInvoice = await SaleInvoice.tenant()
.query()
.where('id', saleInvoiceId)
.update({
@@ -124,10 +126,9 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
changeCustomerBalanceOper,
recordInventoryTransOper,
]);
// Schedule sale invoice re-compute based on the item cost
// method and starting date.
await this.scheduleComputeInvoiceItemsCost(saleInvoice);
await this.scheduleComputeInvoiceItemsCost(saleInvoiceId, true);
}
/**
@@ -313,10 +314,51 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* @param {ISaleInvoice} saleInvoice
* @return {Promise}
*/
static scheduleComputeInvoiceItemsCost(saleInvoice) {
return this.scheduleComputeItemsCost(
saleInvoice.entries.map((e) => e.item_id),
saleInvoice.invoice_date,
);
static async scheduleComputeInvoiceItemsCost(saleInvoiceId: number, override?: boolean) {
const saleInvoice: ISaleInvoice = await SaleInvoice.tenant()
.query()
.findById(saleInvoiceId)
.withGraphFetched('entries.item');
const inventoryItemsIds = chain(saleInvoice.entries)
.filter((entry: IItemEntry) => entry.item.type === 'inventory')
.map((entry: IItemEntry) => entry.itemId)
.uniq().value();
if (inventoryItemsIds.length === 0) {
await this.writeNonInventoryInvoiceJournals(saleInvoice, override);
} else {
await this.scheduleComputeItemsCost(
inventoryItemsIds,
saleInvoice.invoice_date,
);
}
}
/**
* Writes the sale invoice journal entries.
* @param {SaleInvoice} saleInvoice -
*/
static async writeNonInventoryInvoiceJournals(saleInvoice: ISaleInvoice, override: boolean) {
const accountsDepGraph = await Account.tenant().depGraph().query();
const journal = new JournalPoster(accountsDepGraph);
if (override) {
const oldTransactions = await AccountTransaction.tenant()
.query()
.where('reference_type', 'SaleInvoice')
.where('reference_id', saleInvoice.id)
.withGraphFetched('account.type');
journal.loadEntries(oldTransactions);
journal.removeEntries();
}
this.saleInvoiceJournal(saleInvoice, journal);
await Promise.all([
journal.deleteEntries(),
journal.saveEntries(),
journal.saveBalance(),
]);
}
}

View File

@@ -9,31 +9,27 @@ import JournalPoster from '@/services/Accounting/JournalPoster';
import JournalEntry from '@/services/Accounting/JournalEntry';
import InventoryService from '@/services/Inventory/Inventory';
import { ISaleInvoice, IItemEntry, IItem } from '@/interfaces';
import { ISaleInvoice } from '../../interfaces';
export default class SaleInvoicesCost {
/**
* Schedule sale invoice re-compute based on the item
* cost method and starting date.
* @param {number[]} itemIds -
* @param {Date} startingDate -
* @param {number[]} itemIds - Inventory items ids.
* @param {Date} startingDate - Starting compute cost date.
* @return {Promise<Agenda>}
*/
static async scheduleComputeItemsCost(itemIds: number[], startingDate: Date) {
const items: IItem[] = await Item.tenant().query().whereIn('id', itemIds);
const inventoryItems: IItem[] = items.filter((item: IItem) => item.type === 'inventory');
static async scheduleComputeItemsCost(inventoryItemsIds: number[], startingDate: Date) {
const asyncOpers: Promise<[]>[] = [];
inventoryItems.forEach((item: IItem) => {
inventoryItemsIds.forEach((inventoryItemId: number) => {
const oper: Promise<[]> = InventoryService.scheduleComputeItemCost(
item.id,
inventoryItemId,
startingDate,
);
asyncOpers.push(oper);
});
const writeJEntriesOper: Promise<any> = this.scheduleWriteJournalEntries(startingDate);
return Promise.all([...asyncOpers, writeJEntriesOper]);
return Promise.all([...asyncOpers]);
}
/**
@@ -63,7 +59,6 @@ export default class SaleInvoicesCost {
builder.withGraphFetched('entries.item')
builder.withGraphFetched('costTransactions(groupedEntriesCost)');
});
const accountsDepGraph = await Account.tenant().depGraph().query();
const journal = new JournalPoster(accountsDepGraph);
@@ -79,62 +74,9 @@ export default class SaleInvoicesCost {
journal.loadEntries(oldTransactions);
journal.removeEntries();
}
const receivableAccount = { id: 10 };
salesInvoices.forEach((saleInvoice: ISaleInvoice) => {
let inventoryTotal: number = 0;
const commonEntry = {
referenceType: 'SaleInvoice',
referenceId: saleInvoice.id,
date: saleInvoice.invoiceDate,
};
const costTransactions: Map<number, number> = new Map(
saleInvoice.costTransactions.map((trans: IItemEntry) => [
trans.entryId, trans.cost,
]),
);
// XXX Debit - Receivable account.
const receivableEntry = new JournalEntry({
...commonEntry,
debit: saleInvoice.balance,
account: receivableAccount.id,
});
journal.debit(receivableEntry);
saleInvoice.entries.forEach((entry: IItemEntry) => {
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,
});
journal.debit(costEntry);
inventoryTotal += cost;
}
// XXX Credit - Income account.
const incomeEntry = new JournalEntry({
...commonEntry,
credit: income,
account: entry.item.sellAccountId,
note: entry.description,
});
journal.credit(incomeEntry);
if (inventoryTotal > 0) {
// XXX Credit - Inventory account.
const inventoryEntry = new JournalEntry({
...commonEntry,
credit: inventoryTotal,
account: entry.item.inventoryAccountId,
});
journal.credit(inventoryEntry);
}
});
salesInvoices.forEach((saleInvoice: ISaleInvoice) => {
this.saleInvoiceJournal(saleInvoice, journal);
});
return Promise.all([
journal.deleteEntries(),
@@ -142,4 +84,66 @@ export default class SaleInvoicesCost {
journal.saveBalance(),
]);
}
/**
* Writes journal entries for given sale invoice.
* @param {ISaleInvoice} saleInvoice
* @param {JournalPoster} journal
*/
static saleInvoiceJournal(saleInvoice: ISaleInvoice, journal: JournalPoster) {
let inventoryTotal: number = 0;
const receivableAccount = { id: 10 };
const commonEntry = {
referenceType: 'SaleInvoice',
referenceId: saleInvoice.id,
date: saleInvoice.invoiceDate,
};
const costTransactions: Map<number, number> = new Map(
saleInvoice?.costTransactions?.map((trans: IItemEntry) => [
trans.entryId, trans.cost,
]),
);
// XXX Debit - Receivable account.
const receivableEntry = new JournalEntry({
...commonEntry,
debit: saleInvoice.balance,
account: receivableAccount.id,
});
journal.debit(receivableEntry);
saleInvoice.entries.forEach((entry: IItemEntry) => {
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,
});
journal.debit(costEntry);
inventoryTotal += cost;
}
// XXX Credit - Income account.
const incomeEntry = new JournalEntry({
...commonEntry,
credit: income,
account: entry.item.sellAccountId,
note: entry.description,
});
journal.credit(incomeEntry);
if (inventoryTotal > 0) {
// XXX Credit - Inventory account.
const inventoryEntry = new JournalEntry({
...commonEntry,
credit: inventoryTotal,
account: entry.item.inventoryAccountId,
});
journal.credit(inventoryEntry);
}
});
}
}