feat: changing items quantity on hand with sales invoices and bills.

This commit is contained in:
a.bouhuolia
2020-12-23 13:11:02 +02:00
parent 395ee71c53
commit 26452d9c05
6 changed files with 202 additions and 47 deletions

View File

@@ -11,6 +11,7 @@ import SettingRepository from 'repositories/SettingRepository';
import ExpenseEntryRepository from 'repositories/ExpenseEntryRepository'; 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';
export default (knex, cache) => { export default (knex, cache) => {
return { return {
@@ -27,5 +28,6 @@ export default (knex, cache) => {
settingRepository: new SettingRepository(knex, cache), settingRepository: new SettingRepository(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),
}; };
}; };

View File

@@ -1,19 +1,15 @@
import { difference, map } from 'lodash'; import { difference, map } from 'lodash';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { import { IItemEntry, IItemEntryDTO, IItem } from 'interfaces';
IItemEntry,
IItemEntryDTO,
IItem,
} from 'interfaces';
import { ServiceError } from 'exceptions'; import { ServiceError } from 'exceptions';
import TenancyService from 'services/Tenancy/TenancyService'; import TenancyService from 'services/Tenancy/TenancyService';
import { ItemEntry } from 'models'; import { entriesAmountDiff } from 'utils';
const ERRORS = { const ERRORS = {
ITEMS_NOT_FOUND: 'ITEMS_NOT_FOUND', ITEMS_NOT_FOUND: 'ITEMS_NOT_FOUND',
ENTRIES_IDS_NOT_FOUND: 'ENTRIES_IDS_NOT_FOUND', ENTRIES_IDS_NOT_FOUND: 'ENTRIES_IDS_NOT_FOUND',
NOT_PURCHASE_ABLE_ITEMS: 'NOT_PURCHASE_ABLE_ITEMS', NOT_PURCHASE_ABLE_ITEMS: 'NOT_PURCHASE_ABLE_ITEMS',
NOT_SELL_ABLE_ITEMS: 'NOT_SELL_ABLE_ITEMS' NOT_SELL_ABLE_ITEMS: 'NOT_SELL_ABLE_ITEMS',
}; };
@Service() @Service()
@@ -31,7 +27,7 @@ export default class ItemsEntriesService {
public async getInventoryEntries( public async getInventoryEntries(
tenantId: number, tenantId: number,
referenceType: string, referenceType: string,
referenceId: number, referenceId: number
): Promise<IItemEntry[]> { ): Promise<IItemEntry[]> {
const { Item, ItemEntry } = this.tenancy.models(tenantId); const { Item, ItemEntry } = this.tenancy.models(tenantId);
@@ -60,7 +56,10 @@ export default class ItemsEntriesService {
* @param {number} tenantId - * @param {number} tenantId -
* @param {IItemEntryDTO} itemEntries - * @param {IItemEntryDTO} itemEntries -
*/ */
public async validateItemsIdsExistance(tenantId: number, itemEntries: IItemEntryDTO[]) { public async validateItemsIdsExistance(
tenantId: number,
itemEntries: IItemEntryDTO[]
) {
const { Item } = this.tenancy.models(tenantId); const { Item } = this.tenancy.models(tenantId);
const itemsIds = itemEntries.map((e) => e.itemId); const itemsIds = itemEntries.map((e) => e.itemId);
@@ -80,7 +79,12 @@ export default class ItemsEntriesService {
* @param {number} billId - * @param {number} billId -
* @param {IItemEntry[]} billEntries - * @param {IItemEntry[]} billEntries -
*/ */
public async validateEntriesIdsExistance(tenantId: number, referenceId: number, referenceType: string, billEntries: IItemEntryDTO[]) { public async validateEntriesIdsExistance(
tenantId: number,
referenceId: number,
referenceType: string,
billEntries: IItemEntryDTO[]
) {
const { ItemEntry } = this.tenancy.models(tenantId); const { ItemEntry } = this.tenancy.models(tenantId);
const entriesIds = billEntries const entriesIds = billEntries
.filter((e: IItemEntry) => e.id) .filter((e: IItemEntry) => e.id)
@@ -94,15 +98,17 @@ export default class ItemsEntriesService {
const notFoundEntriesIds = difference(entriesIds, storedEntriesIds); const notFoundEntriesIds = difference(entriesIds, storedEntriesIds);
if (notFoundEntriesIds.length > 0) { if (notFoundEntriesIds.length > 0) {
throw new ServiceError(ERRORS.ENTRIES_IDS_NOT_FOUND) throw new ServiceError(ERRORS.ENTRIES_IDS_NOT_FOUND);
} }
} }
/** /**
* Validate the entries items that not purchase-able. * Validate the entries items that not purchase-able.
*/ */
public async validateNonPurchasableEntriesItems(tenantId: number, itemEntries: IItemEntryDTO[]) { public async validateNonPurchasableEntriesItems(
tenantId: number,
itemEntries: IItemEntryDTO[]
) {
const { Item } = this.tenancy.models(tenantId); const { Item } = this.tenancy.models(tenantId);
const itemsIds = itemEntries.map((e: IItemEntryDTO) => e.itemId); const itemsIds = itemEntries.map((e: IItemEntryDTO) => e.itemId);
@@ -121,7 +127,10 @@ export default class ItemsEntriesService {
/** /**
* Validate the entries items that not sell-able. * Validate the entries items that not sell-able.
*/ */
public async validateNonSellableEntriesItems(tenantId: number, itemEntries: IItemEntryDTO[]) { public async validateNonSellableEntriesItems(
tenantId: number,
itemEntries: IItemEntryDTO[]
) {
const { Item } = this.tenancy.models(tenantId); const { Item } = this.tenancy.models(tenantId);
const itemsIds = itemEntries.map((e: IItemEntryDTO) => e.itemId); const itemsIds = itemEntries.map((e: IItemEntryDTO) => e.itemId);
@@ -136,4 +145,65 @@ export default class ItemsEntriesService {
throw new ServiceError(ERRORS.NOT_SELL_ABLE_ITEMS); throw new ServiceError(ERRORS.NOT_SELL_ABLE_ITEMS);
} }
} }
/**
* Changes items quantity from the given items entries the new and old onces.
* @param {number} tenantId
* @param {IItemEntry} entries - Items entries.
* @param {IItemEntry} oldEntries - Old items entries.
*/
public async changeItemsQuantity(
tenantId: number,
entries: IItemEntry[],
oldEntries?: IItemEntry[]
): Promise<void> {
const { itemRepository } = this.tenancy.repositories(tenantId);
const opers = [];
const diffEntries = entriesAmountDiff(
entries,
oldEntries,
'quantity',
'itemId'
);
diffEntries.forEach((entry: IItemEntry) => {
const changeQuantityOper = itemRepository.changeNumber(
{ id: entry.itemId, type: 'inventory' },
'quantityOnHand',
entry.quantity
);
opers.push(changeQuantityOper);
});
await Promise.all(opers);
}
/**
* Increment items quantity from the given items entries.
* @param {number} tenantId - Tenant id.
* @param {IItemEntry} entries - Items entries.
*/
public async incrementItemsEntries(
tenantId: number,
entries: IItemEntry[]
): Promise<void> {
return this.changeItemsQuantity(tenantId, entries);
}
/**
* Decrement items quantity from the given items entries.
* @param {number} tenantId - Tenant id.
* @param {IItemEntry} entries - Items entries.
*/
public async decrementItemsQuantity(
tenantId: number,
entries: IItemEntry[]
): Promise<void> {
return this.changeItemsQuantity(
tenantId,
entries.map((entry) => ({
...entry,
quantity: entry.quantity * -1,
}))
);
}
} }

View File

@@ -251,6 +251,7 @@ export default class ItemsService implements IItemsService {
const storedItem = await Item.query().insertAndFetch({ const storedItem = await Item.query().insertAndFetch({
...itemDTO, ...itemDTO,
active: defaultTo(itemDTO.active, 1), active: defaultTo(itemDTO.active, 1),
quantityOnHand: itemDTO.type === 'inventory' ? 0 : null,
}); });
this.logger.info('[items] item inserted successfully.', { this.logger.info('[items] item inserted successfully.', {
tenantId, tenantId,

View File

@@ -1,17 +1,14 @@
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import { omit, sumBy, pick, map } from 'lodash'; import { omit, sumBy, map } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import uniqid from 'uniqid';
import { import {
EventDispatcher, EventDispatcher,
EventDispatcherInterface, EventDispatcherInterface,
} from 'decorators/eventDispatcher'; } from 'decorators/eventDispatcher';
import { import {
ISaleInvoice, ISaleInvoice,
IItemEntry,
ISaleInvoiceCreateDTO, ISaleInvoiceCreateDTO,
ISaleInvoiceEditDTO, ISaleInvoiceEditDTO,
IInventoryTransaction,
ISalesInvoicesFilter, ISalesInvoicesFilter,
IPaginationMeta, IPaginationMeta,
IFilterMeta, IFilterMeta,
@@ -109,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');
if (!saleInvoice) { if (!saleInvoice) {
throw new ServiceError(ERRORS.SALE_INVOICE_NOT_FOUND); throw new ServiceError(ERRORS.SALE_INVOICE_NOT_FOUND);
@@ -458,7 +456,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 }
); );
} }

View File

@@ -4,6 +4,7 @@ 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';
import JournalPosterService from 'services/Sales/JournalPosterService'; import JournalPosterService from 'services/Sales/JournalPosterService';
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
@EventSubscriber() @EventSubscriber()
export default class BillSubscriber { export default class BillSubscriber {
@@ -11,6 +12,7 @@ export default class BillSubscriber {
billsService: BillsService; billsService: BillsService;
logger: any; logger: any;
journalPosterService: JournalPosterService; journalPosterService: JournalPosterService;
itemsEntriesService: ItemsEntriesService;
/** /**
* Constructor method. * Constructor method.
@@ -20,6 +22,7 @@ export default class BillSubscriber {
this.billsService = Container.get(BillsService); this.billsService = Container.get(BillsService);
this.logger = Container.get('logger'); this.logger = Container.get('logger');
this.journalPosterService = Container.get(JournalPosterService); this.journalPosterService = Container.get(JournalPosterService);
this.itemsEntriesService = Container.get(ItemsEntriesService);
} }
/** /**
@@ -118,7 +121,7 @@ export default class BillSubscriber {
this.billsService.recordInventoryTransactions( this.billsService.recordInventoryTransactions(
tenantId, tenantId,
bill.id, bill.id,
bill.billDate, bill.billDate
); );
} }
@@ -127,12 +130,14 @@ export default class BillSubscriber {
*/ */
@On(events.bill.onEdited) @On(events.bill.onEdited)
async handleOverwritingInventoryTransactions({ tenantId, bill }) { async handleOverwritingInventoryTransactions({ tenantId, bill }) {
this.logger.info('[bill] overwriting the inventory transactions.', { tenantId }); this.logger.info('[bill] overwriting the inventory transactions.', {
tenantId,
});
this.billsService.recordInventoryTransactions( this.billsService.recordInventoryTransactions(
tenantId, tenantId,
bill.id, bill.id,
bill.billDate, bill.billDate,
true, true
); );
} }
@@ -141,11 +146,11 @@ export default class BillSubscriber {
*/ */
@On(events.bill.onDeleted) @On(events.bill.onDeleted)
async handleRevertInventoryTransactions({ tenantId, billId }) { async handleRevertInventoryTransactions({ tenantId, billId }) {
this.logger.info('[bill] reverting the bill inventory transactions', { tenantId, billId }); this.logger.info('[bill] reverting the bill inventory transactions', {
this.billsService.revertInventoryTransactions(
tenantId, tenantId,
billId, billId,
); });
this.billsService.revertInventoryTransactions(tenantId, billId);
} }
/** /**
@@ -155,11 +160,47 @@ export default class BillSubscriber {
@On(events.bill.onInventoryTransactionsCreated) @On(events.bill.onInventoryTransactionsCreated)
public async handleComputeItemsCosts({ tenantId, billId }) { public async handleComputeItemsCosts({ tenantId, billId }) {
this.logger.info('[bill] trying to compute the bill items cost.', { this.logger.info('[bill] trying to compute the bill items cost.', {
tenantId, billId,
});
await this.billsService.scheduleComputeBillItemsCost(
tenantId, tenantId,
billId, billId,
});
await this.billsService.scheduleComputeBillItemsCost(tenantId, billId);
}
/**
* Increments the sale invoice items once the invoice created.
*/
@On(events.bill.onCreated)
public async handleDecrementSaleInvoiceItemsQuantity({ tenantId, bill }) {
await this.itemsEntriesService.incrementItemsEntries(
tenantId,
bill.entries
);
}
/**
* Decrements the sale invoice items once the invoice deleted.
*/
@On(events.bill.onDeleted)
public async handleIncrementSaleInvoiceItemsQuantity({ tenantId, oldBill }) {
await this.itemsEntriesService.decrementItemsQuantity(
tenantId,
oldBill.entries
);
}
/**
* Handle increment/decrement the different items quantity once the sale invoice be edited.
*/
@On(events.bill.onEdited)
public async handleChangeSaleInvoiceItemsQuantityOnEdit({
tenantId,
bill,
oldBill,
}) {
await this.itemsEntriesService.changeItemsQuantity(
tenantId,
bill.entries,
oldBill.entries
); );
} }
} }

View File

@@ -5,6 +5,7 @@ import TenancyService from 'services/Tenancy/TenancyService';
import SettingsService from 'services/Settings/SettingsService'; 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';
@EventSubscriber() @EventSubscriber()
export default class SaleInvoiceSubscriber { export default class SaleInvoiceSubscriber {
@@ -13,6 +14,7 @@ export default class SaleInvoiceSubscriber {
settingsService: SettingsService; settingsService: SettingsService;
saleEstimatesService: SaleEstimateService; saleEstimatesService: SaleEstimateService;
saleInvoicesService: SaleInvoicesService; saleInvoicesService: SaleInvoicesService;
itemsEntriesService: ItemsEntriesService;
constructor() { constructor() {
this.logger = Container.get('logger'); this.logger = Container.get('logger');
@@ -20,6 +22,7 @@ export default class SaleInvoiceSubscriber {
this.settingsService = Container.get(SettingsService); this.settingsService = Container.get(SettingsService);
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);
} }
/** /**
@@ -188,4 +191,44 @@ export default class SaleInvoiceSubscriber {
saleInvoiceId, saleInvoiceId,
); );
} }
/**
* 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.
*/
@On(events.saleInvoice.onDeleted)
public async handleIncrementSaleInvoiceItemsQuantity({ tenantId, oldSaleInvoice }) {
await this.itemsEntriesService.incrementItemsEntries(
tenantId,
oldSaleInvoice.entries,
);
}
/**
* Handle increment/decrement the different items quantity once the sale invoice be edited.
*/
@On(events.saleInvoice.onEdited)
public async handleChangeSaleInvoiceItemsQuantityOnEdit({ tenantId, saleInvoice, oldSaleInvoice }) {
await this.itemsEntriesService.changeItemsQuantity(
tenantId,
saleInvoice.entries.map((entry) => ({
...entry,
quantity: entry.quantity * -1,
})),
oldSaleInvoice.entries.map((entry) => ({
...entry,
quantity: entry.quantity * -1,
})),
);
}
} }