feat: trigger compute items cost once the sale invoice and bill be edited or deleted.

This commit is contained in:
a.bouhuolia
2020-12-23 18:57:17 +02:00
parent 26452d9c05
commit b07bb2df53
9 changed files with 228 additions and 69 deletions

View File

@@ -12,6 +12,7 @@ import ExpenseEntryRepository from 'repositories/ExpenseEntryRepository';
import BillRepository from 'repositories/BillRepository'; import BillRepository from 'repositories/BillRepository';
import SaleInvoiceRepository from 'repositories/SaleInvoiceRepository'; import SaleInvoiceRepository from 'repositories/SaleInvoiceRepository';
import ItemRepository from 'repositories/ItemRepository'; import ItemRepository from 'repositories/ItemRepository';
import InventoryTransactionRepository from 'repositories/InventoryTransactionRepository';
export default (knex, cache) => { export default (knex, cache) => {
return { return {
@@ -29,5 +30,6 @@ export default (knex, cache) => {
billRepository: new BillRepository(knex, cache), billRepository: new BillRepository(knex, cache),
saleInvoiceRepository: new SaleInvoiceRepository(knex, cache), saleInvoiceRepository: new SaleInvoiceRepository(knex, cache),
itemRepository: new ItemRepository(knex, cache), itemRepository: new ItemRepository(knex, cache),
inventoryTransactionRepository: new InventoryTransactionRepository(knex, cache),
}; };
}; };

View File

@@ -0,0 +1,11 @@
import TenantRepository from 'repositories/TenantRepository';
import { InventoryTransaction } from 'models';
export default class InventoryTransactionRepository extends TenantRepository {
/**
* Gets the repository's model.
*/
get model() {
return InventoryTransaction.bindKnex(this.knex);
}
}

View File

@@ -239,7 +239,7 @@ export default class JournalCommands{
.whereIn('reference_id', Array.isArray(referenceId) ? referenceId : [referenceId]) .whereIn('reference_id', Array.isArray(referenceId) ? referenceId : [referenceId])
.withGraphFetched('account.type'); .withGraphFetched('account.type');
this.journal.loadEntries(transactions); this.journal.fromTransactions(transactions);
this.journal.removeEntries(); this.journal.removeEntries();
} }

View File

@@ -392,7 +392,7 @@ export default class BillsService extends SalesInvoicesCost {
billId, billId,
'IN', 'IN',
billDate, billDate,
lotNumber, lotNumber
); );
// Records the inventory transactions. // Records the inventory transactions.
await this.inventoryService.recordInventoryTransactions( await this.inventoryService.recordInventoryTransactions(
@@ -418,11 +418,25 @@ export default class BillsService extends SalesInvoicesCost {
* @return {Promise<void>} * @return {Promise<void>}
*/ */
public async revertInventoryTransactions(tenantId: number, billId: number) { public async revertInventoryTransactions(tenantId: number, billId: number) {
const { inventoryTransactionRepository } = this.tenancy.repositories(
tenantId
);
// Retrieve the inventory transactions of the given sale invoice.
const oldInventoryTransactions = await inventoryTransactionRepository.find({
transactionId: billId,
transactionType: 'Bill',
});
await this.inventoryService.deleteInventoryTransactions( await this.inventoryService.deleteInventoryTransactions(
tenantId, tenantId,
billId, billId,
'Bill' 'Bill'
); );
// Triggers 'onInventoryTransactionsDeleted' event.
this.eventDispatcher.dispatch(
events.bill.onInventoryTransactionsDeleted,
{ tenantId, billId, oldInventoryTransactions }
);
} }
/** /**

View File

@@ -1,5 +1,5 @@
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import { omit, sumBy, map } from 'lodash'; import { omit, sumBy } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import { import {
EventDispatcher, EventDispatcher,
@@ -106,7 +106,8 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
*/ */
async getInvoiceOrThrowError(tenantId: number, saleInvoiceId: number) { async getInvoiceOrThrowError(tenantId: number, saleInvoiceId: number) {
const { SaleInvoice } = this.tenancy.models(tenantId); const { SaleInvoice } = this.tenancy.models(tenantId);
const saleInvoice = await SaleInvoice.query().findById(saleInvoiceId) const saleInvoice = await SaleInvoice.query()
.findById(saleInvoiceId)
.withGraphFetched('entries'); .withGraphFetched('entries');
if (!saleInvoice) { if (!saleInvoice) {
@@ -183,7 +184,6 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
tenantId, tenantId,
saleInvoiceDTO.entries saleInvoiceDTO.entries
); );
// Validate items should be sellable items. // Validate items should be sellable items.
await this.itemsEntriesService.validateNonSellableEntriesItems( await this.itemsEntriesService.validateNonSellableEntriesItems(
tenantId, tenantId,
@@ -227,7 +227,6 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
tenantId, tenantId,
saleInvoiceId saleInvoiceId
); );
// Transform DTO object to model object. // Transform DTO object to model object.
const saleInvoiceObj = this.transformDTOToModel( const saleInvoiceObj = this.transformDTOToModel(
tenantId, tenantId,
@@ -325,10 +324,17 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
): Promise<void> { ): Promise<void> {
const { SaleInvoice, ItemEntry } = this.tenancy.models(tenantId); const { SaleInvoice, ItemEntry } = this.tenancy.models(tenantId);
// Retrieve the given sale invoice with associated entries or throw not found error.
const oldSaleInvoice = await this.getInvoiceOrThrowError( const oldSaleInvoice = await this.getInvoiceOrThrowError(
tenantId, tenantId,
saleInvoiceId saleInvoiceId
); );
// Triggers `onSaleInvoiceDelete` event.
await this.eventDispatcher.dispatch(events.saleInvoice.onDelete, {
tenantId,
saleInvoice: oldSaleInvoice,
saleInvoiceId,
});
// Unlink the converted sale estimates from the given sale invoice. // Unlink the converted sale estimates from the given sale invoice.
await this.saleEstimatesService.unlinkConvertedEstimateFromInvoice( await this.saleEstimatesService.unlinkConvertedEstimateFromInvoice(
tenantId, tenantId,
@@ -448,6 +454,14 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
tenantId: number, tenantId: number,
saleInvoiceId: number saleInvoiceId: number
): Promise<void> { ): Promise<void> {
const { inventoryTransactionRepository } = this.tenancy.repositories(tenantId);
// Retrieve the inventory transactions of the given sale invoice.
const oldInventoryTransactions = await inventoryTransactionRepository.find({
transactionId: saleInvoiceId,
transactionType: 'SaleInvoice',
});
// Delete the inventory transaction of the given sale invoice.
await this.inventoryService.deleteInventoryTransactions( await this.inventoryService.deleteInventoryTransactions(
tenantId, tenantId,
saleInvoiceId, saleInvoiceId,
@@ -456,7 +470,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
// Triggers 'onInventoryTransactionsDeleted' event. // Triggers 'onInventoryTransactionsDeleted' event.
this.eventDispatcher.dispatch( this.eventDispatcher.dispatch(
events.saleInvoice.onInventoryTransactionsDeleted, events.saleInvoice.onInventoryTransactionsDeleted,
{ tenantId, saleInvoiceId } { tenantId, saleInvoiceId, oldInventoryTransactions }
); );
} }
@@ -482,40 +496,6 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
return saleInvoice; return saleInvoice;
} }
/**
* Schedules compute sale invoice items cost based on each item
* cost method.
* @param {ISaleInvoice} saleInvoice
* @return {Promise}
*/
async scheduleComputeInvoiceItemsCost(
tenantId: number,
saleInvoiceId: number,
override?: boolean
) {
const { SaleInvoice, Item } = this.tenancy.models(tenantId);
// Retrieve the sale invoice with associated entries.
const saleInvoice: ISaleInvoice = await SaleInvoice.query()
.findById(saleInvoiceId)
.withGraphFetched('entries');
// Retrieve the inventory items that associated to the sale invoice entries.
const inventoryItems = await Item.query()
.whereIn('id', map(saleInvoice.entries, 'itemId'))
.where('type', 'inventory');
const inventoryItemsIds = map(inventoryItems, 'id');
if (inventoryItemsIds.length > 0) {
await this.scheduleComputeItemsCost(
tenantId,
inventoryItemsIds,
saleInvoice.invoiceDate
);
}
}
/** /**
* Retrieve sales invoices filterable and paginated list. * Retrieve sales invoices filterable and paginated list.
* @param {Request} req * @param {Request} req

View File

@@ -1,6 +1,6 @@
import { Container, Service, Inject } from 'typedi'; import { Container, Service, Inject } from 'typedi';
import { map } from 'lodash';
import JournalPoster from 'services/Accounting/JournalPoster'; import JournalPoster from 'services/Accounting/JournalPoster';
import JournalEntry from 'services/Accounting/JournalEntry';
import InventoryService from 'services/Inventory/Inventory'; import InventoryService from 'services/Inventory/Inventory';
import TenancyService from 'services/Tenancy/TenancyService'; import TenancyService from 'services/Tenancy/TenancyService';
import { ISaleInvoice, IItemEntry, IInventoryLotCost, IItem } from 'interfaces'; import { ISaleInvoice, IItemEntry, IInventoryLotCost, IItem } from 'interfaces';
@@ -21,10 +21,10 @@ export default class SaleInvoicesCost {
* @param {Date} startingDate - Starting compute cost date. * @param {Date} startingDate - Starting compute cost date.
* @return {Promise<Agenda>} * @return {Promise<Agenda>}
*/ */
async scheduleComputeItemsCost( async scheduleComputeCostByItemsIds(
tenantId: number, tenantId: number,
inventoryItemsIds: number[], inventoryItemsIds: number[],
startingDate: Date startingDate: Date,
) { ) {
const asyncOpers: Promise<[]>[] = []; const asyncOpers: Promise<[]>[] = [];
@@ -39,6 +39,86 @@ export default class SaleInvoicesCost {
return Promise.all([...asyncOpers]); return Promise.all([...asyncOpers]);
} }
/**
* Schedules compute sale invoice items cost based on each item
* cost method.
* @param {number} tenantId - Tenant id.
* @param {ISaleInvoice} saleInvoiceId - Sale invoice id.
* @param {boolean} override - Allow to override old computes in edit mode.
* @return {Promise}
*/
async scheduleComputeCostByInvoiceId(
tenantId: number,
saleInvoiceId: number,
) {
const { SaleInvoice } = this.tenancy.models(tenantId);
// Retrieve the sale invoice with associated entries.
const saleInvoice: ISaleInvoice = await SaleInvoice.query()
.findById(saleInvoiceId)
.withGraphFetched('entries');
// Schedule compute inventory items cost by the given invoice model object.
return this.scheduleComputeCostByEntries(
tenantId,
saleInvoice.entries,
saleInvoice.invoiceDate,
);
}
/**
* Schedules the compute inventory items cost by the given bill id.
* @param {number} tenantId - Tenant id.
* @param {number} billId - Bill id.
* @return {Promise<void>}
*/
async scheduleComputeCostByBillId(
tenantId: number,
billId: number
): Promise<void> {
const { Bill } = this.tenancy.models(tenantId);
// Retrieve the bill with associated entries.
const bill = await Bill.query()
.findById(billId)
.withGraphFetched('entries');
return this.scheduleComputeCostByEntries(
tenantId,
bill.entries,
bill.billDate,
);
}
/**
* Schedules the compute inventory items by the given invoice.
* @param {number} tenantId
* @param {ISaleInvoice & { entries: IItemEntry[] }} saleInvoice
* @param {boolean} override
*/
async scheduleComputeCostByEntries(
tenantId: number,
entries: IItemEntry[],
startingDate: Date,
) {
const { Item } = this.tenancy.models(tenantId);
// Retrieve the inventory items that associated to the sale invoice entries.
const inventoryItems = await Item.query()
.whereIn('id', map(entries, 'itemId'))
.where('type', 'inventory');
const inventoryItemsIds = map(inventoryItems, 'id');
if (inventoryItemsIds.length > 0) {
await this.scheduleComputeCostByItemsIds(
tenantId,
inventoryItemsIds,
startingDate
);
}
}
/** /**
* Schedule writing journal entries. * Schedule writing journal entries.
* @param {Date} startingDate * @param {Date} startingDate

View File

@@ -1,5 +1,6 @@
import { Container } from 'typedi'; import { Container } from 'typedi';
import { EventSubscriber, On } from 'event-dispatch'; import { EventSubscriber, On } from 'event-dispatch';
import { map, head } from 'lodash';
import events from 'subscribers/events'; import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService'; import TenancyService from 'services/Tenancy/TenancyService';
import BillsService from 'services/Purchases/Bills'; import BillsService from 'services/Purchases/Bills';
@@ -163,7 +164,27 @@ export default class BillSubscriber {
tenantId, tenantId,
billId, billId,
}); });
await this.billsService.scheduleComputeBillItemsCost(tenantId, billId); await this.billsService.scheduleComputeCostByBillId(tenantId, billId);
}
/**
* Handles computes items costs once the inventory transactions deleted.
*/
@On(events.bill.onInventoryTransactionsDeleted)
public async handleComputeCostsOnInventoryTransactionsDeleted({
tenantId,
billId,
oldInventoryTransactions,
}) {
const inventoryItemsIds = map(oldInventoryTransactions, 'itemId');
const startingDates = map(oldInventoryTransactions, 'date');
const startingDate = head(startingDates);
await this.billsService.scheduleComputeCostByItemsIds(
tenantId,
inventoryItemsIds,
startingDate
);
} }
/** /**

View File

@@ -79,6 +79,7 @@ export default {
saleInvoice: { saleInvoice: {
onCreated: 'onSaleInvoiceCreated', onCreated: 'onSaleInvoiceCreated',
onEdited: 'onSaleInvoiceEdited', onEdited: 'onSaleInvoiceEdited',
onDelete: 'onSaleInvoiceDelete',
onDeleted: 'onSaleInvoiceDeleted', onDeleted: 'onSaleInvoiceDeleted',
onBulkDelete: 'onSaleInvoiceBulkDeleted', onBulkDelete: 'onSaleInvoiceBulkDeleted',
onPublished: 'onSaleInvoicePublished', onPublished: 'onSaleInvoicePublished',
@@ -127,7 +128,8 @@ export default {
onDeleted: 'onBillDeleted', onDeleted: 'onBillDeleted',
onBulkDeleted: 'onBillBulkDeleted', onBulkDeleted: 'onBillBulkDeleted',
onPublished: 'onBillPublished', onPublished: 'onBillPublished',
onInventoryTransactionsCreated: 'onBillInventoryTransactionsCreated' onInventoryTransactionsCreated: 'onBillInventoryTransactionsCreated',
onInventoryTransactionsDeleted: 'onBillInventoryTransactionsDeleted'
}, },
/** /**
@@ -163,6 +165,9 @@ export default {
onOpeningBalanceChanged: 'onOpeingBalanceChanged', onOpeningBalanceChanged: 'onOpeingBalanceChanged',
}, },
/**
* Items service.
*/
items: { items: {
onCreated: 'onItemCreated', onCreated: 'onItemCreated',
onEdited: 'onItemEdited', onEdited: 'onItemEdited',

View File

@@ -1,4 +1,5 @@
import { Container } from 'typedi'; import { Container } from 'typedi';
import { head, map } from 'lodash';
import { On, EventSubscriber } from 'event-dispatch'; import { On, EventSubscriber } from 'event-dispatch';
import events from 'subscribers/events'; import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService'; import TenancyService from 'services/Tenancy/TenancyService';
@@ -89,7 +90,7 @@ export default class SaleInvoiceSubscriber {
await this.saleInvoicesService.recordInventoryTranscactions( await this.saleInvoicesService.recordInventoryTranscactions(
tenantId, tenantId,
saleInvoice.id, saleInvoice.id,
saleInvoice.invoiceDate, saleInvoice.invoiceDate
); );
} }
@@ -101,12 +102,12 @@ export default class SaleInvoiceSubscriber {
public async handleWritingNonInventoryEntries({ tenantId, saleInvoice }) { public async handleWritingNonInventoryEntries({ tenantId, saleInvoice }) {
await this.saleInvoicesService.recordNonInventoryJournalEntries( await this.saleInvoicesService.recordNonInventoryJournalEntries(
tenantId, tenantId,
saleInvoice.id, saleInvoice.id
); );
} }
/** /**
* * Rewriting the inventory transactions once the sale invoice be edited.
*/ */
@On(events.saleInvoice.onEdited) @On(events.saleInvoice.onEdited)
public async handleRewritingInventoryTransactions({ tenantId, saleInvoice }) { public async handleRewritingInventoryTransactions({ tenantId, saleInvoice }) {
@@ -117,7 +118,7 @@ export default class SaleInvoiceSubscriber {
tenantId, tenantId,
saleInvoice.id, saleInvoice.id,
saleInvoice.invoiceDate, saleInvoice.invoiceDate,
true, true
); );
} }
@@ -167,14 +168,22 @@ export default class SaleInvoiceSubscriber {
/** /**
* Handles deleting the inventory transactions once the invoice deleted. * Handles deleting the inventory transactions once the invoice deleted.
*/ */
@On(events.saleInvoice.onDeleted) @On(events.saleInvoice.onDelete)
public async handleDeletingInventoryTransactions({ tenantId, saleInvoiceId }) { public async handleDeletingInventoryTransactions({
this.logger.info('[sale_invoice] trying to revert inventory transactions.', { tenantId,
tenantId, saleInvoiceId, saleInvoiceId,
}); oldSaleInvoice,
}) {
this.logger.info(
'[sale_invoice] trying to revert inventory transactions.',
{
tenantId,
saleInvoiceId,
}
);
await this.saleInvoicesService.revertInventoryTransactions( await this.saleInvoicesService.revertInventoryTransactions(
tenantId, tenantId,
saleInvoiceId, saleInvoiceId
); );
} }
@@ -182,13 +191,40 @@ export default class SaleInvoiceSubscriber {
* Schedules compute invoice items cost job. * Schedules compute invoice items cost job.
*/ */
@On(events.saleInvoice.onInventoryTransactionsCreated) @On(events.saleInvoice.onInventoryTransactionsCreated)
public async handleComputeItemsCosts({ tenantId, saleInvoiceId }) { public async handleComputeCostsOnInventoryTransactionsCreated({
this.logger.info('[sale_invoice] trying to compute the invoice items cost.', { tenantId,
tenantId, saleInvoiceId, saleInvoiceId,
}); }) {
await this.saleInvoicesService.scheduleComputeInvoiceItemsCost( this.logger.info(
'[sale_invoice] trying to compute the invoice items cost.',
{
tenantId,
saleInvoiceId,
}
);
await this.saleInvoicesService.scheduleComputeCostByInvoiceId(
tenantId, tenantId,
saleInvoiceId, saleInvoiceId
);
}
/**
* Schedules compute items cost once the inventory transactions deleted.
*/
@On(events.saleInvoice.onInventoryTransactionsDeleted)
public async handleComputeCostsOnInventoryTransactionsDeleted({
tenantId,
saleInvoiceId,
oldInventoryTransactions,
}) {
const inventoryItemsIds = map(oldInventoryTransactions, 'itemId');
const startingDates = map(oldInventoryTransactions, 'date');
const startingDate = head(startingDates);
await this.saleInvoicesService.scheduleComputeCostByItemsIds(
tenantId,
inventoryItemsIds,
startingDate
); );
} }
@@ -196,10 +232,13 @@ export default class SaleInvoiceSubscriber {
* Increments the sale invoice items once the invoice created. * Increments the sale invoice items once the invoice created.
*/ */
@On(events.saleInvoice.onCreated) @On(events.saleInvoice.onCreated)
public async handleDecrementSaleInvoiceItemsQuantity({ tenantId, saleInvoice }) { public async handleDecrementSaleInvoiceItemsQuantity({
tenantId,
saleInvoice,
}) {
await this.itemsEntriesService.decrementItemsQuantity( await this.itemsEntriesService.decrementItemsQuantity(
tenantId, tenantId,
saleInvoice.entries, saleInvoice.entries
); );
} }
@@ -207,10 +246,13 @@ export default class SaleInvoiceSubscriber {
* Decrements the sale invoice items once the invoice deleted. * Decrements the sale invoice items once the invoice deleted.
*/ */
@On(events.saleInvoice.onDeleted) @On(events.saleInvoice.onDeleted)
public async handleIncrementSaleInvoiceItemsQuantity({ tenantId, oldSaleInvoice }) { public async handleIncrementSaleInvoiceItemsQuantity({
tenantId,
oldSaleInvoice,
}) {
await this.itemsEntriesService.incrementItemsEntries( await this.itemsEntriesService.incrementItemsEntries(
tenantId, tenantId,
oldSaleInvoice.entries, oldSaleInvoice.entries
); );
} }
@@ -218,7 +260,11 @@ export default class SaleInvoiceSubscriber {
* Handle increment/decrement the different items quantity once the sale invoice be edited. * Handle increment/decrement the different items quantity once the sale invoice be edited.
*/ */
@On(events.saleInvoice.onEdited) @On(events.saleInvoice.onEdited)
public async handleChangeSaleInvoiceItemsQuantityOnEdit({ tenantId, saleInvoice, oldSaleInvoice }) { public async handleChangeSaleInvoiceItemsQuantityOnEdit({
tenantId,
saleInvoice,
oldSaleInvoice,
}) {
await this.itemsEntriesService.changeItemsQuantity( await this.itemsEntriesService.changeItemsQuantity(
tenantId, tenantId,
saleInvoice.entries.map((entry) => ({ saleInvoice.entries.map((entry) => ({
@@ -228,7 +274,7 @@ export default class SaleInvoiceSubscriber {
oldSaleInvoice.entries.map((entry) => ({ oldSaleInvoice.entries.map((entry) => ({
...entry, ...entry,
quantity: entry.quantity * -1, quantity: entry.quantity * -1,
})), }))
); );
} }
} }