mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 05:10:31 +00:00
feat: changing items quantity on hand with sales invoices and bills.
This commit is contained in:
@@ -11,6 +11,7 @@ import SettingRepository from 'repositories/SettingRepository';
|
||||
import ExpenseEntryRepository from 'repositories/ExpenseEntryRepository';
|
||||
import BillRepository from 'repositories/BillRepository';
|
||||
import SaleInvoiceRepository from 'repositories/SaleInvoiceRepository';
|
||||
import ItemRepository from 'repositories/ItemRepository';
|
||||
|
||||
export default (knex, cache) => {
|
||||
return {
|
||||
@@ -27,5 +28,6 @@ export default (knex, cache) => {
|
||||
settingRepository: new SettingRepository(knex, cache),
|
||||
billRepository: new BillRepository(knex, cache),
|
||||
saleInvoiceRepository: new SaleInvoiceRepository(knex, cache),
|
||||
itemRepository: new ItemRepository(knex, cache),
|
||||
};
|
||||
};
|
||||
@@ -1,19 +1,15 @@
|
||||
import { difference, map } from 'lodash';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import {
|
||||
IItemEntry,
|
||||
IItemEntryDTO,
|
||||
IItem,
|
||||
} from 'interfaces';
|
||||
import { IItemEntry, IItemEntryDTO, IItem } from 'interfaces';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import { ItemEntry } from 'models';
|
||||
import { entriesAmountDiff } from 'utils';
|
||||
|
||||
const ERRORS = {
|
||||
ITEMS_NOT_FOUND: 'ITEMS_NOT_FOUND',
|
||||
ENTRIES_IDS_NOT_FOUND: 'ENTRIES_IDS_NOT_FOUND',
|
||||
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()
|
||||
@@ -23,15 +19,15 @@ export default class ItemsEntriesService {
|
||||
|
||||
/**
|
||||
* Retrieve the inventory items entries of the reference id and type.
|
||||
* @param {number} tenantId
|
||||
* @param {string} referenceType
|
||||
* @param {string} referenceId
|
||||
* @param {number} tenantId
|
||||
* @param {string} referenceType
|
||||
* @param {string} referenceId
|
||||
* @return {Promise<IItemEntry[]>}
|
||||
*/
|
||||
public async getInventoryEntries(
|
||||
tenantId: number,
|
||||
referenceType: string,
|
||||
referenceId: number,
|
||||
referenceId: number
|
||||
): Promise<IItemEntry[]> {
|
||||
const { Item, ItemEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
@@ -46,7 +42,7 @@ export default class ItemsEntriesService {
|
||||
|
||||
// Inventory items ids.
|
||||
const inventoryItemsIds = map(inventoryItems, 'id');
|
||||
|
||||
|
||||
// Filtering the inventory items entries.
|
||||
const inventoryItemsEntries = itemsEntries.filter(
|
||||
(itemEntry) => inventoryItemsIds.indexOf(itemEntry.itemId) !== -1
|
||||
@@ -57,13 +53,16 @@ export default class ItemsEntriesService {
|
||||
/**
|
||||
* Validates the entries items ids.
|
||||
* @async
|
||||
* @param {number} tenantId -
|
||||
* @param {number} tenantId -
|
||||
* @param {IItemEntryDTO} itemEntries -
|
||||
*/
|
||||
public async validateItemsIdsExistance(tenantId: number, itemEntries: IItemEntryDTO[]) {
|
||||
public async validateItemsIdsExistance(
|
||||
tenantId: number,
|
||||
itemEntries: IItemEntryDTO[]
|
||||
) {
|
||||
const { Item } = this.tenancy.models(tenantId);
|
||||
const itemsIds = itemEntries.map((e) => e.itemId);
|
||||
|
||||
|
||||
const foundItems = await Item.query().whereIn('id', itemsIds);
|
||||
|
||||
const foundItemsIds = foundItems.map((item: IItem) => item.id);
|
||||
@@ -74,13 +73,18 @@ export default class ItemsEntriesService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Validates the entries ids existance on the storage.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} billId -
|
||||
* @param {number} tenantId -
|
||||
* @param {number} billId -
|
||||
* @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 entriesIds = billEntries
|
||||
.filter((e: IItemEntry) => e.id)
|
||||
@@ -94,18 +98,20 @@ export default class ItemsEntriesService {
|
||||
const notFoundEntriesIds = difference(entriesIds, storedEntriesIds);
|
||||
|
||||
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 itemsIds = itemEntries.map((e: IItemEntryDTO) => e.itemId);
|
||||
|
||||
|
||||
const purchasbleItems = await Item.query()
|
||||
.where('purchasable', true)
|
||||
.whereIn('id', itemsIds);
|
||||
@@ -119,12 +125,15 @@ 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 itemsIds = itemEntries.map((e: IItemEntryDTO) => e.itemId);
|
||||
|
||||
|
||||
const sellableItems = await Item.query()
|
||||
.where('sellable', true)
|
||||
.whereIn('id', itemsIds);
|
||||
@@ -136,4 +145,65 @@ export default class ItemsEntriesService {
|
||||
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,
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,6 +251,7 @@ export default class ItemsService implements IItemsService {
|
||||
const storedItem = await Item.query().insertAndFetch({
|
||||
...itemDTO,
|
||||
active: defaultTo(itemDTO.active, 1),
|
||||
quantityOnHand: itemDTO.type === 'inventory' ? 0 : null,
|
||||
});
|
||||
this.logger.info('[items] item inserted successfully.', {
|
||||
tenantId,
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { omit, sumBy, pick, map } from 'lodash';
|
||||
import { omit, sumBy, map } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import uniqid from 'uniqid';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
} from 'decorators/eventDispatcher';
|
||||
import {
|
||||
ISaleInvoice,
|
||||
IItemEntry,
|
||||
ISaleInvoiceCreateDTO,
|
||||
ISaleInvoiceEditDTO,
|
||||
IInventoryTransaction,
|
||||
ISalesInvoicesFilter,
|
||||
IPaginationMeta,
|
||||
IFilterMeta,
|
||||
@@ -109,7 +106,8 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
*/
|
||||
async getInvoiceOrThrowError(tenantId: number, saleInvoiceId: number) {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
const saleInvoice = await SaleInvoice.query().findById(saleInvoiceId);
|
||||
const saleInvoice = await SaleInvoice.query().findById(saleInvoiceId)
|
||||
.withGraphFetched('entries');
|
||||
|
||||
if (!saleInvoice) {
|
||||
throw new ServiceError(ERRORS.SALE_INVOICE_NOT_FOUND);
|
||||
@@ -355,7 +353,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
/**
|
||||
* Records the inventory transactions of the given sale invoice in case
|
||||
* the invoice has inventory entries only.
|
||||
*
|
||||
*
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {SaleInvoice} saleInvoice - Sale invoice DTO.
|
||||
* @param {number} saleInvoiceId - Sale invoice id.
|
||||
@@ -411,9 +409,9 @@ 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 {number} tenantId -
|
||||
* @param {number} saleInvoiceId
|
||||
* @param {boolean} override
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
@@ -458,7 +456,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
// Triggers 'onInventoryTransactionsDeleted' event.
|
||||
this.eventDispatcher.dispatch(
|
||||
events.saleInvoice.onInventoryTransactionsDeleted,
|
||||
{ tenantId, saleInvoiceId },
|
||||
{ tenantId, saleInvoiceId }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import events from 'subscribers/events';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import BillsService from 'services/Purchases/Bills';
|
||||
import JournalPosterService from 'services/Sales/JournalPosterService';
|
||||
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
||||
|
||||
@EventSubscriber()
|
||||
export default class BillSubscriber {
|
||||
@@ -11,6 +12,7 @@ export default class BillSubscriber {
|
||||
billsService: BillsService;
|
||||
logger: any;
|
||||
journalPosterService: JournalPosterService;
|
||||
itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
@@ -20,6 +22,7 @@ export default class BillSubscriber {
|
||||
this.billsService = Container.get(BillsService);
|
||||
this.logger = Container.get('logger');
|
||||
this.journalPosterService = Container.get(JournalPosterService);
|
||||
this.itemsEntriesService = Container.get(ItemsEntriesService);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,7 +121,7 @@ export default class BillSubscriber {
|
||||
this.billsService.recordInventoryTransactions(
|
||||
tenantId,
|
||||
bill.id,
|
||||
bill.billDate,
|
||||
bill.billDate
|
||||
);
|
||||
}
|
||||
|
||||
@@ -127,12 +130,14 @@ export default class BillSubscriber {
|
||||
*/
|
||||
@On(events.bill.onEdited)
|
||||
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(
|
||||
tenantId,
|
||||
bill.id,
|
||||
bill.billDate,
|
||||
true,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@@ -141,11 +146,11 @@ export default class BillSubscriber {
|
||||
*/
|
||||
@On(events.bill.onDeleted)
|
||||
async handleRevertInventoryTransactions({ tenantId, billId }) {
|
||||
this.logger.info('[bill] reverting the bill inventory transactions', { tenantId, billId });
|
||||
this.billsService.revertInventoryTransactions(
|
||||
this.logger.info('[bill] reverting the bill inventory transactions', {
|
||||
tenantId,
|
||||
billId,
|
||||
);
|
||||
});
|
||||
this.billsService.revertInventoryTransactions(tenantId, billId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,11 +160,47 @@ export default class BillSubscriber {
|
||||
@On(events.bill.onInventoryTransactionsCreated)
|
||||
public async handleComputeItemsCosts({ tenantId, billId }) {
|
||||
this.logger.info('[bill] trying to compute the bill items cost.', {
|
||||
tenantId, billId,
|
||||
});
|
||||
await this.billsService.scheduleComputeBillItemsCost(
|
||||
tenantId,
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import SettingsService from 'services/Settings/SettingsService';
|
||||
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
||||
import SaleInvoicesService from 'services/Sales/SalesInvoices';
|
||||
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
||||
|
||||
@EventSubscriber()
|
||||
export default class SaleInvoiceSubscriber {
|
||||
@@ -13,6 +14,7 @@ export default class SaleInvoiceSubscriber {
|
||||
settingsService: SettingsService;
|
||||
saleEstimatesService: SaleEstimateService;
|
||||
saleInvoicesService: SaleInvoicesService;
|
||||
itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
constructor() {
|
||||
this.logger = Container.get('logger');
|
||||
@@ -20,6 +22,7 @@ export default class SaleInvoiceSubscriber {
|
||||
this.settingsService = Container.get(SettingsService);
|
||||
this.saleEstimatesService = Container.get(SaleEstimateService);
|
||||
this.saleInvoicesService = Container.get(SaleInvoicesService);
|
||||
this.itemsEntriesService = Container.get(ItemsEntriesService);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,4 +191,44 @@ export default class SaleInvoiceSubscriber {
|
||||
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,
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user