refactor: inventory adjustments to nestjs

This commit is contained in:
Ahmed Bouhuolia
2025-01-07 22:17:23 +02:00
parent abf92ac83f
commit 1773df1858
15 changed files with 1154 additions and 1 deletions

View File

@@ -0,0 +1,147 @@
import { Knex } from 'knex';
import { Inject } from '@nestjs/common';
import * as R from 'ramda';
import { omit } from 'lodash';
import { events } from '@/common/events/events';
import { InventoryAdjustment } from '../models/InventoryAdjustment';
import { InventoryAdjustmentEntry } from '../models/InventoryAdjustmentEntry';
import {
IInventoryAdjustmentCreatingPayload,
IInventoryAdjustmentEventCreatedPayload,
IQuickInventoryAdjustmentDTO,
} from '../types/InventoryAdjustments.types';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { ServiceError } from '@/modules/Items/ServiceError';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { Item } from '@/modules/Items/models/Item';
import { Account } from '@/modules/Accounts/models/Account.model';
import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations/BranchTransactionDTOTransform';
import { WarehouseTransactionDTOTransform } from '@/modules/Warehouses/Integrations/WarehouseTransactionDTOTransform';
export class CreateQuickInventoryAdjustmentService {
constructor(
@Inject(InventoryAdjustment.name)
private readonly inventoryAdjustmentModel: typeof InventoryAdjustment,
@Inject(InventoryAdjustmentEntry.name)
private readonly inventoryAdjustmentEntryModel: typeof InventoryAdjustmentEntry,
@Inject(Item.name)
private readonly itemModel: typeof Item,
@Inject(Account.name)
private readonly accountModel: typeof Account,
private readonly eventEmitter: EventEmitter2,
private readonly uow: UnitOfWork,
private readonly warehouseDTOTransform: WarehouseTransactionDTOTransform,
private readonly branchDTOTransform: BranchTransactionDTOTransformer,
) {}
/**
* Transformes the quick inventory adjustment DTO to model object.
* @param {IQuickInventoryAdjustmentDTO} adjustmentDTO -
* @return {IInventoryAdjustment}
*/
private transformQuickAdjToModel(
adjustmentDTO: IQuickInventoryAdjustmentDTO,
): InventoryAdjustment {
const entries = [
{
index: 1,
itemId: adjustmentDTO.itemId,
...('increment' === adjustmentDTO.type
? {
quantity: adjustmentDTO.quantity,
cost: adjustmentDTO.cost,
}
: {}),
...('decrement' === adjustmentDTO.type
? {
quantity: adjustmentDTO.quantity,
}
: {}),
},
];
const initialDTO = {
...omit(adjustmentDTO, ['quantity', 'cost', 'itemId', 'publish']),
userId: authorizedUser.id,
...(adjustmentDTO.publish
? {
publishedAt: moment().toMySqlDateTime(),
}
: {}),
entries,
};
return R.compose(
this.warehouseDTOTransform.transformDTO<InventoryAdjustment>,
this.branchDTOTransform.transformDTO<InventoryAdjustment>,
)(initialDTO) as InventoryAdjustment;
}
/**
* Creates a quick inventory adjustment for specific item.
* @param {number} tenantId - Tenant id.
* @param {IQuickInventoryAdjustmentDTO} quickAdjustmentDTO - qucik adjustment DTO.
*/
public async createQuickAdjustment(
quickAdjustmentDTO: IQuickInventoryAdjustmentDTO,
): Promise<InventoryAdjustment> {
// Retrieve the adjustment account or throw not found error.
const adjustmentAccount = await this.accountModel
.query()
.findById(quickAdjustmentDTO.adjustmentAccountId)
.throwIfNotFound();
// Retrieve the item model or throw not found service error.
const item = await this.itemModel
.query()
.findById(quickAdjustmentDTO.itemId)
.throwIfNotFound();
// Validate item inventory type.
this.validateItemInventoryType(item);
// Transform the DTO to inventory adjustment model.
const invAdjustmentObject =
this.transformQuickAdjToModel(quickAdjustmentDTO);
// Writes inventory adjustment transaction with associated transactions
// under unit-of-work envirment.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onInventoryAdjustmentCreating` event.
await this.eventEmitter.emitAsync(
events.inventoryAdjustment.onQuickCreating,
{
quickAdjustmentDTO,
trx,
} as IInventoryAdjustmentCreatingPayload,
);
// Saves the inventory adjustment with associated entries to the storage.
const inventoryAdjustment = await this.inventoryAdjustmentModel
.query(trx)
.upsertGraph({
...invAdjustmentObject,
});
// Triggers `onInventoryAdjustmentQuickCreated` event.
await this.eventEmitter.emitAsync(
events.inventoryAdjustment.onQuickCreated,
{
inventoryAdjustment,
inventoryAdjustmentId: inventoryAdjustment.id,
trx,
} as IInventoryAdjustmentEventCreatedPayload,
);
return inventoryAdjustment;
});
}
/**
* Validate the item inventory type.
* @param {IItem} item
*/
validateItemInventoryType(item) {
if (item.type !== 'inventory') {
throw new ServiceError(ERRORS.ITEM_SHOULD_BE_INVENTORY_TYPE);
}
}
}

View File

@@ -0,0 +1,67 @@
import { Inject } from '@nestjs/common';
import { Knex } from 'knex';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import {
IInventoryAdjustmentDeletingPayload,
IInventoryAdjustmentEventDeletedPayload,
} from '../types/InventoryAdjustments.types';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { InventoryAdjustmentEntry } from '../models/InventoryAdjustmentEntry';
import { InventoryAdjustment } from '../models/InventoryAdjustment';
export class DeleteInventoryAdjustmentService {
constructor(
private readonly eventEmitter: EventEmitter2,
private readonly uow: UnitOfWork,
@Inject(InventoryAdjustment.name)
private readonly inventoryAdjustmentModel: typeof InventoryAdjustment,
@Inject(InventoryAdjustmentEntry.name)
private readonly inventoryAdjustmentEntryModel: typeof InventoryAdjustmentEntry,
) {}
/**
* Deletes the inventory adjustment transaction.
* @param {number} inventoryAdjustmentId - Inventory adjustment id.
*/
public async deleteInventoryAdjustment(
inventoryAdjustmentId: number,
): Promise<void> {
// Retrieve the inventory adjustment or throw not found service error.
const oldInventoryAdjustment = await this.inventoryAdjustmentModel
.query()
.findById(inventoryAdjustmentId)
.throwIfNotFound();
// Deletes the inventory adjustment transaction and associated transactions
// under unit-of-work env.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onInventoryAdjustmentDeleting` event.
await this.eventEmitter.emitAsync(events.inventoryAdjustment.onDeleting, {
oldInventoryAdjustment,
trx,
} as IInventoryAdjustmentDeletingPayload);
// Deletes the inventory adjustment entries.
await this.inventoryAdjustmentEntryModel
.query(trx)
.where('adjustment_id', inventoryAdjustmentId)
.delete();
// Deletes the inventory adjustment transaction.
await this.inventoryAdjustmentModel
.query(trx)
.findById(inventoryAdjustmentId)
.delete();
// Triggers `onInventoryAdjustmentDeleted` event.
await this.eventEmitter.emitAsync(events.inventoryAdjustment.onDeleted, {
inventoryAdjustmentId,
oldInventoryAdjustment,
trx,
} as IInventoryAdjustmentEventDeletedPayload);
});
}
}

View File

@@ -0,0 +1,97 @@
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { Inject } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { InventoryAdjustment } from '../models/InventoryAdjustment';
import { InventoryAdjustmentEntry } from '../models/InventoryAdjustmentEntry';
import {
IInventoryAdjustmentEventPublishedPayload,
IInventoryAdjustmentPublishingPayload,
} from '../types/InventoryAdjustments.types';
import { events } from '@/common/events/events';
import { Knex } from 'knex';
import { ServiceError } from '@/modules/Items/ServiceError';
export class PublishInventoryAdjustmentService {
constructor(
private readonly eventEmitter: EventEmitter2,
private readonly uow: UnitOfWork,
@Inject(InventoryAdjustment.name)
private readonly inventoryAdjustmentModel: typeof InventoryAdjustment,
@Inject(InventoryAdjustmentEntry.name)
private readonly inventoryAdjustmentEntryModel: typeof InventoryAdjustmentEntry,
) {}
/**
* Publish the inventory adjustment transaction.
* @param {number} tenantId
* @param {number} inventoryAdjustmentId
*/
public async publishInventoryAdjustment(
inventoryAdjustmentId: number,
): Promise<void> {
// Retrieve the inventory adjustment or throw not found service error.
const oldInventoryAdjustment = await this.inventoryAdjustmentModel
.query()
.findById(inventoryAdjustmentId)
.throwIfNotFound();
// Validate adjustment not already published.
this.validateAdjustmentTransactionsNotPublished(oldInventoryAdjustment);
// Publishes inventory adjustment with associated inventory transactions
// under unit-of-work envirement.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
await this.eventEmitter.emitAsync(
events.inventoryAdjustment.onPublishing,
{
trx,
oldInventoryAdjustment,
} as IInventoryAdjustmentPublishingPayload,
);
// Publish the inventory adjustment transaction.
await InventoryAdjustment.query().findById(inventoryAdjustmentId).patch({
publishedAt: moment().toMySqlDateTime(),
});
// Retrieve the inventory adjustment after the modification.
const inventoryAdjustment = await InventoryAdjustment.query()
.findById(inventoryAdjustmentId)
.withGraphFetched('entries');
// Triggers `onInventoryAdjustmentDeleted` event.
await this.eventEmitter.emitAsync(
events.inventoryAdjustment.onPublished,
{
inventoryAdjustmentId,
inventoryAdjustment,
oldInventoryAdjustment,
trx,
} as IInventoryAdjustmentEventPublishedPayload,
);
});
}
/**
* Validate the adjustment transaction is exists.
* @param {IInventoryAdjustment} inventoryAdjustment
*/
private throwIfAdjustmentNotFound(inventoryAdjustment: InventoryAdjustment) {
if (!inventoryAdjustment) {
throw new ServiceError(ERRORS.INVENTORY_ADJUSTMENT_NOT_FOUND);
}
}
/**
* Validates the adjustment transaction is not already published.
* @param {IInventoryAdjustment} oldInventoryAdjustment
*/
private validateAdjustmentTransactionsNotPublished(
oldInventoryAdjustment: InventoryAdjustment,
) {
if (oldInventoryAdjustment.isPublished) {
throw new ServiceError(ERRORS.INVENTORY_ADJUSTMENT_ALREADY_PUBLISHED);
}
}
}