mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-27 18:19:48 +00:00
feat(nestjs): migrate to NestJS
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import { Transformer } from "../Transformer/Transformer";
|
||||
import { InventoryAdjustment } from "./models/InventoryAdjustment";
|
||||
|
||||
export class InventoryAdjustmentTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale invoice object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return ['formattedType'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted and localized adjustment type.
|
||||
* @param {IInventoryAdjustment} inventoryAdjustment
|
||||
* @returns {string}
|
||||
*/
|
||||
formattedType(inventoryAdjustment: InventoryAdjustment) {
|
||||
const types = {
|
||||
increment: 'inventory_adjustment.type.increment',
|
||||
decrement: 'inventory_adjustment.type.decrement',
|
||||
};
|
||||
return this.context.i18n.t(types[inventoryAdjustment.type] || '');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { InventoryAdjustmentsApplicationService } from './InventoryAdjustmentsApplication.service';
|
||||
import { IInventoryAdjustmentsFilter } from './types/InventoryAdjustments.types';
|
||||
import { InventoryAdjustment } from './models/InventoryAdjustment';
|
||||
import { IPaginationMeta } from '@/interfaces/Model';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { CreateQuickInventoryAdjustmentDto } from './dtos/CreateQuickInventoryAdjustment.dto';
|
||||
|
||||
@Controller('inventory-adjustments')
|
||||
@ApiTags('inventory-adjustments')
|
||||
export class InventoryAdjustmentsController {
|
||||
constructor(
|
||||
private readonly inventoryAdjustmentsApplicationService: InventoryAdjustmentsApplicationService,
|
||||
) {}
|
||||
|
||||
@Post('quick')
|
||||
@ApiOperation({ summary: 'Create a quick inventory adjustment.' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'The inventory adjustment has been successfully created.',
|
||||
})
|
||||
public async createQuickInventoryAdjustment(
|
||||
@Body() quickAdjustmentDTO: CreateQuickInventoryAdjustmentDto,
|
||||
): Promise<InventoryAdjustment> {
|
||||
return this.inventoryAdjustmentsApplicationService.createQuickInventoryAdjustment(
|
||||
quickAdjustmentDTO,
|
||||
);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@ApiOperation({ summary: 'Delete the given inventory adjustment.' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'The inventory adjustment has been successfully deleted.',
|
||||
})
|
||||
public async deleteInventoryAdjustment(
|
||||
@Param('id') inventoryAdjustmentId: number,
|
||||
): Promise<void> {
|
||||
return this.inventoryAdjustmentsApplicationService.deleteInventoryAdjustment(
|
||||
inventoryAdjustmentId,
|
||||
);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Retrieves the inventory adjustments.' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'The inventory adjustments have been successfully retrieved.',
|
||||
})
|
||||
public async getInventoryAdjustments(
|
||||
@Query() filterDTO: IInventoryAdjustmentsFilter,
|
||||
): Promise<{
|
||||
inventoryAdjustments: InventoryAdjustment[];
|
||||
pagination: IPaginationMeta;
|
||||
}> {
|
||||
return this.inventoryAdjustmentsApplicationService.getInventoryAdjustments(
|
||||
filterDTO,
|
||||
);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: 'Retrieves the inventory adjustment details.' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description:
|
||||
'The inventory adjustment details have been successfully retrieved.',
|
||||
})
|
||||
public async getInventoryAdjustment(
|
||||
@Param('id') inventoryAdjustmentId: number,
|
||||
): Promise<InventoryAdjustment> {
|
||||
return this.inventoryAdjustmentsApplicationService.getInventoryAdjustment(
|
||||
inventoryAdjustmentId,
|
||||
);
|
||||
}
|
||||
|
||||
@Put(':id/publish')
|
||||
@ApiOperation({ summary: 'Publish the given inventory adjustment.' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'The inventory adjustment has been successfully published.',
|
||||
})
|
||||
public async publishInventoryAdjustment(
|
||||
@Param('id') inventoryAdjustmentId: number,
|
||||
): Promise<void> {
|
||||
return this.inventoryAdjustmentsApplicationService.publishInventoryAdjustment(
|
||||
inventoryAdjustmentId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
|
||||
import { InventoryAdjustment } from './models/InventoryAdjustment';
|
||||
import { InventoryAdjustmentEntry } from './models/InventoryAdjustmentEntry';
|
||||
import { CreateQuickInventoryAdjustmentService } from './commands/CreateQuickInventoryAdjustment.service';
|
||||
import { PublishInventoryAdjustmentService } from './commands/PublishInventoryAdjustment.service';
|
||||
import { GetInventoryAdjustmentService } from './queries/GetInventoryAdjustment.service';
|
||||
import { GetInventoryAdjustmentsService } from './queries/GetInventoryAdjustments.service';
|
||||
import { DeleteInventoryAdjustmentService } from './commands/DeleteInventoryAdjustment.service';
|
||||
import { InventoryAdjustmentsApplicationService } from './InventoryAdjustmentsApplication.service';
|
||||
import { InventoryAdjustmentsController } from './InventoryAdjustments.controller';
|
||||
import { BranchesModule } from '../Branches/Branches.module';
|
||||
import { WarehousesModule } from '../Warehouses/Warehouses.module';
|
||||
import { InventoryAdjustmentsGLSubscriber } from './subscribers/InventoryAdjustmentGL.subscriber';
|
||||
import { InventoryAdjustmentsGLEntries } from './commands/ledger/InventoryAdjustmentsGLEntries';
|
||||
import { InventoryAdjustmentInventoryTransactionsSubscriber } from './inventory/InventoryAdjustmentInventoryTransactionsSubscriber';
|
||||
import { InventoryAdjustmentInventoryTransactions } from './inventory/InventoryAdjustmentInventoryTransactions';
|
||||
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
|
||||
import { LedgerModule } from '../Ledger/Ledger.module';
|
||||
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||
import { InventoryCostModule } from '../InventoryCost/InventoryCost.module';
|
||||
|
||||
const models = [
|
||||
RegisterTenancyModel(InventoryAdjustment),
|
||||
RegisterTenancyModel(InventoryAdjustmentEntry),
|
||||
];
|
||||
@Module({
|
||||
imports: [
|
||||
BranchesModule,
|
||||
WarehousesModule,
|
||||
LedgerModule,
|
||||
DynamicListModule,
|
||||
InventoryCostModule,
|
||||
...models,
|
||||
],
|
||||
controllers: [InventoryAdjustmentsController],
|
||||
providers: [
|
||||
CreateQuickInventoryAdjustmentService,
|
||||
PublishInventoryAdjustmentService,
|
||||
GetInventoryAdjustmentsService,
|
||||
GetInventoryAdjustmentService,
|
||||
DeleteInventoryAdjustmentService,
|
||||
InventoryAdjustmentsApplicationService,
|
||||
InventoryAdjustmentsGLSubscriber,
|
||||
InventoryAdjustmentsGLEntries,
|
||||
TenancyContext,
|
||||
InventoryAdjustmentInventoryTransactionsSubscriber,
|
||||
InventoryAdjustmentInventoryTransactions,
|
||||
],
|
||||
exports: [...models],
|
||||
})
|
||||
export class InventoryAdjustmentsModule {}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DeleteInventoryAdjustmentService } from './commands/DeleteInventoryAdjustment.service';
|
||||
import { PublishInventoryAdjustmentService } from './commands/PublishInventoryAdjustment.service';
|
||||
import { CreateQuickInventoryAdjustmentService } from './commands/CreateQuickInventoryAdjustment.service';
|
||||
import {
|
||||
IInventoryAdjustmentsFilter,
|
||||
IQuickInventoryAdjustmentDTO,
|
||||
} from './types/InventoryAdjustments.types';
|
||||
import { InventoryAdjustment } from './models/InventoryAdjustment';
|
||||
import { GetInventoryAdjustmentService } from './queries/GetInventoryAdjustment.service';
|
||||
import { GetInventoryAdjustmentsService } from './queries/GetInventoryAdjustments.service';
|
||||
import { IPaginationMeta } from '@/interfaces/Model';
|
||||
import { CreateQuickInventoryAdjustmentDto } from './dtos/CreateQuickInventoryAdjustment.dto';
|
||||
|
||||
@Injectable()
|
||||
export class InventoryAdjustmentsApplicationService {
|
||||
constructor(
|
||||
private readonly createQuickInventoryAdjustmentService: CreateQuickInventoryAdjustmentService,
|
||||
private readonly deleteInventoryAdjustmentService: DeleteInventoryAdjustmentService,
|
||||
private readonly publishInventoryAdjustmentService: PublishInventoryAdjustmentService,
|
||||
private readonly getInventoryAdjustmentService: GetInventoryAdjustmentService,
|
||||
private readonly getInventoryAdjustmentsService: GetInventoryAdjustmentsService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves the inventory adjustment transaction.
|
||||
* @param {number} inventoryAdjustmentId - Inventory adjustment id.
|
||||
* @returns {Promise<InventoryAdjustment>}
|
||||
*/
|
||||
public async getInventoryAdjustment(
|
||||
inventoryAdjustmentId: number,
|
||||
): Promise<InventoryAdjustment> {
|
||||
return this.getInventoryAdjustmentService.getInventoryAdjustment(
|
||||
inventoryAdjustmentId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a quick inventory adjustment transaction.
|
||||
* @param {IQuickInventoryAdjustmentDTO} quickAdjustmentDTO - Quick inventory adjustment DTO.
|
||||
*/
|
||||
public async createQuickInventoryAdjustment(
|
||||
quickAdjustmentDTO: CreateQuickInventoryAdjustmentDto,
|
||||
): Promise<InventoryAdjustment> {
|
||||
return this.createQuickInventoryAdjustmentService.createQuickAdjustment(
|
||||
quickAdjustmentDTO,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the inventory adjustment transaction.
|
||||
* @param {number} inventoryAdjustmentId - Inventory adjustment id.
|
||||
*/
|
||||
public async deleteInventoryAdjustment(
|
||||
inventoryAdjustmentId: number,
|
||||
): Promise<void> {
|
||||
return this.deleteInventoryAdjustmentService.deleteInventoryAdjustment(
|
||||
inventoryAdjustmentId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes the inventory adjustment transaction.
|
||||
* @param {number} inventoryAdjustmentId - Inventory adjustment id.
|
||||
*/
|
||||
public async publishInventoryAdjustment(
|
||||
inventoryAdjustmentId: number,
|
||||
): Promise<void> {
|
||||
return this.publishInventoryAdjustmentService.publishInventoryAdjustment(
|
||||
inventoryAdjustmentId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the inventory adjustments paginated list.
|
||||
* @param {IInventoryAdjustmentsFilter} adjustmentsFilter - Inventory adjustments filter.
|
||||
*/
|
||||
public async getInventoryAdjustments(
|
||||
filterDTO: IInventoryAdjustmentsFilter,
|
||||
): Promise<{
|
||||
inventoryAdjustments: InventoryAdjustment[];
|
||||
pagination: IPaginationMeta;
|
||||
}> {
|
||||
return this.getInventoryAdjustmentsService.getInventoryAdjustments(
|
||||
filterDTO,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as moment from 'moment';
|
||||
import * as composeAsync from 'async/compose';
|
||||
import { omit } from 'lodash';
|
||||
import { events } from '@/common/events/events';
|
||||
import { InventoryAdjustment } from '../models/InventoryAdjustment';
|
||||
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';
|
||||
import { CreateQuickInventoryAdjustmentDto } from '../dtos/CreateQuickInventoryAdjustment.dto';
|
||||
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||
import { ERRORS } from '../constants/InventoryAdjustments.constants';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class CreateQuickInventoryAdjustmentService {
|
||||
constructor(
|
||||
@Inject(InventoryAdjustment.name)
|
||||
private readonly inventoryAdjustmentModel: TenantModelProxy<
|
||||
typeof InventoryAdjustment
|
||||
>,
|
||||
|
||||
@Inject(Item.name)
|
||||
private readonly itemModel: TenantModelProxy<typeof Item>,
|
||||
|
||||
@Inject(Account.name)
|
||||
private readonly accountModel: TenantModelProxy<typeof Account>,
|
||||
|
||||
private readonly tenancyContext: TenancyContext,
|
||||
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 async transformQuickAdjToModel(
|
||||
adjustmentDTO: IQuickInventoryAdjustmentDTO,
|
||||
): Promise<InventoryAdjustment> {
|
||||
const authorizedUser = await this.tenancyContext.getSystemUser();
|
||||
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 composeAsync(
|
||||
this.warehouseDTOTransform.transformDTO<InventoryAdjustment>,
|
||||
this.branchDTOTransform.transformDTO<InventoryAdjustment>,
|
||||
)(initialDTO) as InventoryAdjustment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a quick inventory adjustment for specific item.
|
||||
* @param {IQuickInventoryAdjustmentDTO} quickAdjustmentDTO - qucik adjustment DTO.
|
||||
*/
|
||||
public async createQuickAdjustment(
|
||||
quickAdjustmentDTO: CreateQuickInventoryAdjustmentDto,
|
||||
): 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 =
|
||||
await 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)
|
||||
.upsertGraphAndFetch({
|
||||
...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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Inject, Injectable } 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';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteInventoryAdjustmentService {
|
||||
constructor(
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly uow: UnitOfWork,
|
||||
|
||||
@Inject(InventoryAdjustment.name)
|
||||
private readonly inventoryAdjustmentModel: TenantModelProxy<
|
||||
typeof InventoryAdjustment
|
||||
>,
|
||||
|
||||
@Inject(InventoryAdjustmentEntry.name)
|
||||
private readonly inventoryAdjustmentEntryModel: TenantModelProxy<
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import * as moment from 'moment';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { InventoryAdjustment } from '../models/InventoryAdjustment';
|
||||
import {
|
||||
IInventoryAdjustmentEventPublishedPayload,
|
||||
IInventoryAdjustmentPublishingPayload,
|
||||
} from '../types/InventoryAdjustments.types';
|
||||
import { events } from '@/common/events/events';
|
||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
import { ERRORS } from '../constants/InventoryAdjustments.constants';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class PublishInventoryAdjustmentService {
|
||||
constructor(
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly uow: UnitOfWork,
|
||||
|
||||
@Inject(InventoryAdjustment.name)
|
||||
private readonly inventoryAdjustmentModel: TenantModelProxy<
|
||||
typeof InventoryAdjustment
|
||||
>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Publish the inventory adjustment transaction.
|
||||
* @param {number} inventoryAdjustmentId - Inventory adjustment ID.
|
||||
*/
|
||||
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 this.inventoryAdjustmentModel()
|
||||
.query()
|
||||
.findById(inventoryAdjustmentId)
|
||||
.patch({
|
||||
publishedAt: moment().toMySqlDateTime(),
|
||||
});
|
||||
// Retrieve the inventory adjustment after the modification.
|
||||
const inventoryAdjustment = await this.inventoryAdjustmentModel()
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import * as R from 'ramda';
|
||||
import { InventoryAdjustment } from '../../models/InventoryAdjustment';
|
||||
import { InventoryAdjustmentEntry } from '../../models/InventoryAdjustmentEntry';
|
||||
import { ILedgerEntry } from '../../../Ledger/types/Ledger.types';
|
||||
import { AccountNormal } from '@/interfaces/Account';
|
||||
import { Ledger } from '../../../Ledger/Ledger';
|
||||
|
||||
export class InventoryAdjustmentsGL {
|
||||
private inventoryAdjustment: InventoryAdjustment;
|
||||
private baseCurrency: string;
|
||||
|
||||
constructor(inventoryAdjustmentModel: InventoryAdjustment) {
|
||||
this.inventoryAdjustment = inventoryAdjustmentModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the base currency.
|
||||
* @param {string} baseCurrency - Base currency.
|
||||
* @returns {InventoryAdjustmentsGL}
|
||||
*/
|
||||
public setBaseCurrency(baseCurrency: string) {
|
||||
this.baseCurrency = baseCurrency;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the inventory adjustment common GL entry.
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private get adjustmentGLCommonEntry() {
|
||||
return {
|
||||
currencyCode: this.baseCurrency,
|
||||
exchangeRate: 1,
|
||||
|
||||
transactionId: this.inventoryAdjustment.id,
|
||||
transactionType: 'InventoryAdjustment',
|
||||
referenceNumber: this.inventoryAdjustment.referenceNo,
|
||||
|
||||
date: this.inventoryAdjustment.date,
|
||||
|
||||
userId: this.inventoryAdjustment.userId,
|
||||
branchId: this.inventoryAdjustment.branchId,
|
||||
|
||||
createdAt: this.inventoryAdjustment.createdAt,
|
||||
|
||||
credit: 0,
|
||||
debit: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the inventory adjustment inventory GL entry.
|
||||
* @param {InventoryAdjustmentEntry} entry - Inventory adjustment entry.
|
||||
* @param {number} index - Entry index.
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getAdjustmentGLInventoryEntry = R.curry(
|
||||
(entry: InventoryAdjustmentEntry, index: number): ILedgerEntry => {
|
||||
const commonEntry = this.adjustmentGLCommonEntry;
|
||||
const amount = entry.cost * entry.quantity;
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
debit: amount,
|
||||
accountId: entry.item.inventoryAccountId,
|
||||
accountNormal: AccountNormal.DEBIT,
|
||||
index,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Retrieves the inventory adjustment
|
||||
* @param {IInventoryAdjustment} inventoryAdjustment
|
||||
* @param {IInventoryAdjustmentEntry} entry
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getAdjustmentGLCostEntry(
|
||||
entry: InventoryAdjustmentEntry,
|
||||
index: number,
|
||||
): ILedgerEntry {
|
||||
const commonEntry = this.adjustmentGLCommonEntry;
|
||||
const amount = entry.cost * entry.quantity;
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
accountId: this.inventoryAdjustment.adjustmentAccountId,
|
||||
accountNormal: AccountNormal.DEBIT,
|
||||
credit: amount,
|
||||
index: index + 2,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the inventory adjustment GL item entry.
|
||||
* @param {InventoryAdjustmentEntry} entry - Inventory adjustment entry.
|
||||
* @param {number} index - Entry index.
|
||||
* @returns {ILedgerEntry[]}
|
||||
*/
|
||||
private getAdjustmentGLItemEntry(
|
||||
entry: InventoryAdjustmentEntry,
|
||||
index: number,
|
||||
): ILedgerEntry[] {
|
||||
const getInventoryEntry = this.getAdjustmentGLInventoryEntry();
|
||||
const inventoryEntry = getInventoryEntry(entry, index);
|
||||
const costEntry = this.getAdjustmentGLCostEntry(entry, index);
|
||||
|
||||
return [inventoryEntry, costEntry];
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes increment inventroy adjustment GL entries.
|
||||
* @param {InventoryAdjustment} inventoryAdjustment -
|
||||
* @param {JournalPoster} jorunal -
|
||||
* @returns {ILedgerEntry[]}
|
||||
*/
|
||||
public getIncrementAdjustmentGLEntries(): ILedgerEntry[] {
|
||||
return this.inventoryAdjustment.entries
|
||||
.map((entry, index) => this.getAdjustmentGLItemEntry(entry, index))
|
||||
.flat();
|
||||
}
|
||||
|
||||
public getAdjustmentGL(): Ledger {
|
||||
const entries = this.getIncrementAdjustmentGLEntries();
|
||||
|
||||
return new Ledger(entries);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { LedgerStorageService } from '../../../Ledger/LedgerStorage.service';
|
||||
import { InventoryAdjustment } from '../../models/InventoryAdjustment';
|
||||
import { TenancyContext } from '../../../Tenancy/TenancyContext.service';
|
||||
import { InventoryAdjustmentsGL } from './InventoryAdjustmentGL';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class InventoryAdjustmentsGLEntries {
|
||||
constructor(
|
||||
private readonly ledgerStorage: LedgerStorageService,
|
||||
private readonly tenancyContext: TenancyContext,
|
||||
|
||||
@Inject(InventoryAdjustment.name)
|
||||
private readonly inventoryAdjustment: TenantModelProxy<
|
||||
typeof InventoryAdjustment
|
||||
>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Writes inventory increment adjustment GL entries.
|
||||
* @param {number} inventoryAdjustmentId - Inventory adjustment ID.
|
||||
* @param {Knex.Transaction} trx - Knex transaction.
|
||||
*/
|
||||
public writeAdjustmentGLEntries = async (
|
||||
inventoryAdjustmentId: number,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> => {
|
||||
// Retrieves the inventory adjustment with associated entries.
|
||||
const adjustment = await this.inventoryAdjustment()
|
||||
.query(trx)
|
||||
.findById(inventoryAdjustmentId)
|
||||
.withGraphFetched('entries.item');
|
||||
|
||||
const tenantMeta = await this.tenancyContext.getTenantMetadata();
|
||||
|
||||
// Retrieves the inventory adjustment GL entries.
|
||||
const ledger = new InventoryAdjustmentsGL(adjustment)
|
||||
.setBaseCurrency(tenantMeta.baseCurrency)
|
||||
.getAdjustmentGL();
|
||||
|
||||
// Commits the ledger entries to the storage.
|
||||
await this.ledgerStorage.commit(ledger, trx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reverts the adjustment transactions GL entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} inventoryAdjustmentId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public revertAdjustmentGLEntries = (
|
||||
inventoryAdjustmentId: number,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> => {
|
||||
return this.ledgerStorage.deleteByReference(
|
||||
inventoryAdjustmentId,
|
||||
'InventoryAdjustment',
|
||||
trx,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Rewrite inventory adjustment GL entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} inventoryAdjustmentId
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public rewriteAdjustmentGLEntries = async (
|
||||
inventoryAdjustmentId: number,
|
||||
trx?: Knex.Transaction,
|
||||
) => {
|
||||
// Reverts GL entries of the given inventory adjustment.
|
||||
await this.revertAdjustmentGLEntries(inventoryAdjustmentId, trx);
|
||||
|
||||
// Writes GL entries of th egiven inventory adjustment.
|
||||
await this.writeAdjustmentGLEntries(inventoryAdjustmentId, trx);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export const ERRORS = {
|
||||
INVENTORY_ADJUSTMENT_NOT_FOUND: 'INVENTORY_ADJUSTMENT_NOT_FOUND',
|
||||
ITEM_SHOULD_BE_INVENTORY_TYPE: 'ITEM_SHOULD_BE_INVENTORY_TYPE',
|
||||
INVENTORY_ADJUSTMENT_ALREADY_PUBLISHED:
|
||||
'INVENTORY_ADJUSTMENT_ALREADY_PUBLISHED',
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsDate,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsPositive,
|
||||
IsString,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
enum IAdjustmentTypes {
|
||||
INCREMENT = 'increment',
|
||||
DECREMENT = 'decrement',
|
||||
}
|
||||
|
||||
export class CreateQuickInventoryAdjustmentDto {
|
||||
@ApiProperty({ description: 'Date of the inventory adjustment' })
|
||||
@IsNotEmpty()
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
date: Date;
|
||||
|
||||
@ApiProperty({ description: 'Type of adjustment', enum: IAdjustmentTypes })
|
||||
@IsNotEmpty()
|
||||
@IsEnum(IAdjustmentTypes)
|
||||
type: 'increment' | 'decrement';
|
||||
|
||||
@ApiProperty({ description: 'ID of the adjustment account' })
|
||||
@IsNotEmpty()
|
||||
@IsNumber()
|
||||
@IsPositive()
|
||||
adjustmentAccountId: number;
|
||||
|
||||
@ApiProperty({ description: 'Reason for the adjustment' })
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
reason: string;
|
||||
|
||||
@ApiProperty({ description: 'Description of the adjustment' })
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
description: string;
|
||||
|
||||
@ApiProperty({ description: 'Reference number' })
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
referenceNo: string;
|
||||
|
||||
@ApiProperty({ description: 'ID of the item being adjusted' })
|
||||
@IsNotEmpty()
|
||||
@IsNumber()
|
||||
@IsPositive()
|
||||
itemId: number;
|
||||
|
||||
@ApiProperty({ description: 'Quantity to adjust' })
|
||||
@IsNotEmpty()
|
||||
@IsNumber()
|
||||
@IsPositive()
|
||||
quantity: number;
|
||||
|
||||
@ApiProperty({ description: 'Cost of the item' })
|
||||
@IsNotEmpty()
|
||||
@IsNumber()
|
||||
@IsPositive()
|
||||
cost: number;
|
||||
|
||||
@ApiProperty({ description: 'Whether to publish the adjustment immediately' })
|
||||
@IsNotEmpty()
|
||||
@IsBoolean()
|
||||
publish: boolean;
|
||||
|
||||
@ApiPropertyOptional({ description: 'ID of the warehouse (optional)' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@IsPositive()
|
||||
warehouseId?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'ID of the branch (optional)' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@IsPositive()
|
||||
branchId?: number;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { Knex } from "knex";
|
||||
import { InventoryAdjustment } from "../models/InventoryAdjustment";
|
||||
import { InventoryTransaction } from "@/modules/InventoryCost/models/InventoryTransaction";
|
||||
import { InventoryTransactionsService } from "@/modules/InventoryCost/commands/InventoryTransactions.service";
|
||||
|
||||
@Injectable()
|
||||
export class InventoryAdjustmentInventoryTransactions {
|
||||
constructor(
|
||||
private readonly inventoryService: InventoryTransactionsService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Writes the inventory transactions from the inventory adjustment transaction.
|
||||
* @param {number} tenantId -
|
||||
* @param {IInventoryAdjustment} inventoryAdjustment -
|
||||
* @param {boolean} override -
|
||||
* @param {Knex.Transaction} trx -
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async writeInventoryTransactions(
|
||||
inventoryAdjustment: InventoryAdjustment,
|
||||
override: boolean = false,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> {
|
||||
const commonTransaction = {
|
||||
direction: inventoryAdjustment.inventoryDirection,
|
||||
date: inventoryAdjustment.date,
|
||||
transactionType: 'InventoryAdjustment',
|
||||
transactionId: inventoryAdjustment.id,
|
||||
createdAt: inventoryAdjustment.createdAt,
|
||||
costAccountId: inventoryAdjustment.adjustmentAccountId,
|
||||
|
||||
branchId: inventoryAdjustment.branchId,
|
||||
warehouseId: inventoryAdjustment.warehouseId,
|
||||
};
|
||||
const inventoryTransactions = [];
|
||||
|
||||
inventoryAdjustment.entries.forEach((entry) => {
|
||||
inventoryTransactions.push({
|
||||
...commonTransaction,
|
||||
itemId: entry.itemId,
|
||||
quantity: entry.quantity,
|
||||
rate: entry.cost,
|
||||
});
|
||||
});
|
||||
// Saves the given inventory transactions to the storage.
|
||||
await this.inventoryService.recordInventoryTransactions(
|
||||
inventoryTransactions,
|
||||
override,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts the inventory transactions from the inventory adjustment transaction.
|
||||
* @param {number} inventoryAdjustmentId
|
||||
*/
|
||||
async revertInventoryTransactions(
|
||||
inventoryAdjustmentId: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<{ oldInventoryTransactions: InventoryTransaction[] }> {
|
||||
return this.inventoryService.deleteInventoryTransactions(
|
||||
inventoryAdjustmentId,
|
||||
'InventoryAdjustment',
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
IInventoryAdjustmentEventCreatedPayload,
|
||||
IInventoryAdjustmentEventPublishedPayload,
|
||||
} from '../types/InventoryAdjustments.types';
|
||||
import { IInventoryAdjustmentEventDeletedPayload } from '../types/InventoryAdjustments.types';
|
||||
import { InventoryAdjustmentInventoryTransactions } from './InventoryAdjustmentInventoryTransactions';
|
||||
import { events } from '@/common/events/events';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
|
||||
@Injectable()
|
||||
export class InventoryAdjustmentInventoryTransactionsSubscriber {
|
||||
constructor(
|
||||
private readonly inventoryTransactions: InventoryAdjustmentInventoryTransactions,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handles writing inventory transactions once the quick adjustment created.
|
||||
* @param {IInventoryAdjustmentEventPublishedPayload} payload
|
||||
* @param {IInventoryAdjustmentEventCreatedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.inventoryAdjustment.onQuickCreated)
|
||||
public async handleWriteInventoryTransactionsOncePublished({
|
||||
inventoryAdjustment,
|
||||
trx,
|
||||
}:
|
||||
| IInventoryAdjustmentEventPublishedPayload
|
||||
| IInventoryAdjustmentEventCreatedPayload) {
|
||||
await this.inventoryTransactions.writeInventoryTransactions(
|
||||
inventoryAdjustment,
|
||||
false,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles reverting invetory transactions once the inventory adjustment deleted.
|
||||
* @param {IInventoryAdjustmentEventDeletedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.inventoryAdjustment.onDeleted)
|
||||
public async handleRevertInventoryTransactionsOnceDeleted({
|
||||
inventoryAdjustmentId,
|
||||
oldInventoryAdjustment,
|
||||
trx,
|
||||
}: IInventoryAdjustmentEventDeletedPayload) {
|
||||
// Can't continue if the inventory adjustment is not published.
|
||||
if (!oldInventoryAdjustment.isPublished) {
|
||||
return;
|
||||
}
|
||||
// Reverts the inventory transactions of adjustment transaction.
|
||||
await this.inventoryTransactions.revertInventoryTransactions(
|
||||
inventoryAdjustmentId,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import { Model } from 'objection';
|
||||
import { InventoryAdjustmentEntry } from './InventoryAdjustmentEntry';
|
||||
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
export class InventoryAdjustment extends TenantBaseModel {
|
||||
public readonly date!: string;
|
||||
public readonly type!: string;
|
||||
public readonly adjustmentAccountId!: number;
|
||||
public readonly reason?: string;
|
||||
public readonly referenceNo!: string;
|
||||
public readonly description?: string;
|
||||
public readonly userId!: number;
|
||||
public readonly publishedAt?: string;
|
||||
|
||||
public readonly branchId!: number;
|
||||
public readonly warehouseId!: number;
|
||||
|
||||
public readonly createdAt!: Date | string;
|
||||
|
||||
public readonly entries: InventoryAdjustmentEntry[];
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'inventory_adjustments';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps(): Array<string> {
|
||||
return ['created_at'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes(): Array<string> {
|
||||
return ['formattedType', 'inventoryDirection', 'isPublished'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve formatted adjustment type.
|
||||
*/
|
||||
get formattedType(): string {
|
||||
return InventoryAdjustment.getFormattedType(this.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve formatted reference type.
|
||||
*/
|
||||
get inventoryDirection(): string {
|
||||
return InventoryAdjustment.getInventoryDirection(this.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the adjustment is published.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isPublished(): boolean {
|
||||
return !!this.publishedAt;
|
||||
}
|
||||
|
||||
static getInventoryDirection(type) {
|
||||
const directions = {
|
||||
increment: 'IN',
|
||||
decrement: 'OUT',
|
||||
};
|
||||
return directions[type] || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the formatted adjustment type of the given type.
|
||||
* @param {string} type
|
||||
* @returns {string}
|
||||
*/
|
||||
static getFormattedType(type) {
|
||||
const types = {
|
||||
increment: 'inventory_adjustment.type.increment',
|
||||
decrement: 'inventory_adjustment.type.decrement',
|
||||
};
|
||||
return types[type];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const { InventoryAdjustmentEntry } = require('./InventoryAdjustmentEntry');
|
||||
const { Account } = require('../../Accounts/models/Account.model');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Adjustment entries.
|
||||
*/
|
||||
entries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: InventoryAdjustmentEntry,
|
||||
join: {
|
||||
from: 'inventory_adjustments.id',
|
||||
to: 'inventory_adjustments_entries.adjustmentId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Inventory adjustment account.
|
||||
*/
|
||||
adjustmentAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account,
|
||||
join: {
|
||||
from: 'inventory_adjustments.adjustmentAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Model settings.
|
||||
*/
|
||||
// static get meta() {
|
||||
// return InventoryAdjustmentSettings;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Model } from 'objection';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { Item } from '@/modules/Items/models/Item';
|
||||
// import TenantModel from 'models/TenantModel';
|
||||
|
||||
export class InventoryAdjustmentEntry extends BaseModel {
|
||||
adjustmentId!: number;
|
||||
index!: number;
|
||||
itemId!: number;
|
||||
quantity!: number;
|
||||
cost!: number;
|
||||
value!: number;
|
||||
|
||||
item!: Item;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'inventory_adjustments_entries';
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const { InventoryAdjustment } = require('./InventoryAdjustment');
|
||||
const { Item } = require('../../Items/models/Item');
|
||||
|
||||
return {
|
||||
inventoryAdjustment: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: InventoryAdjustment,
|
||||
join: {
|
||||
from: 'inventory_adjustments_entries.adjustmentId',
|
||||
to: 'inventory_adjustments.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Entry item.
|
||||
*/
|
||||
item: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Item,
|
||||
join: {
|
||||
from: 'inventory_adjustments_entries.itemId',
|
||||
to: 'items.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
import { InventoryAdjustment } from '../models/InventoryAdjustment';
|
||||
import { InventoryAdjustmentTransformer } from '../InventoryAdjustmentTransformer';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class GetInventoryAdjustmentService {
|
||||
constructor(
|
||||
private readonly transformer: TransformerInjectable,
|
||||
|
||||
@Inject(InventoryAdjustment.name)
|
||||
private readonly inventoryAdjustmentModel: TenantModelProxy<
|
||||
typeof InventoryAdjustment
|
||||
>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve specific inventory adjustment transaction details.
|
||||
* @param {number} inventoryAdjustmentId - Inventory adjustment id.
|
||||
*/
|
||||
async getInventoryAdjustment(inventoryAdjustmentId: number) {
|
||||
// Retrieve inventory adjustment transation with associated models.
|
||||
const inventoryAdjustment = await this.inventoryAdjustmentModel()
|
||||
.query()
|
||||
.findById(inventoryAdjustmentId)
|
||||
.withGraphFetched('entries.item')
|
||||
.withGraphFetched('adjustmentAccount')
|
||||
.throwIfNotFound();
|
||||
|
||||
return this.transformer.transform(
|
||||
inventoryAdjustment,
|
||||
new InventoryAdjustmentTransformer(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as R from 'ramda';
|
||||
import { IPaginationMeta } from '@/interfaces/Model';
|
||||
import { InventoryAdjustmentTransformer } from '../InventoryAdjustmentTransformer';
|
||||
import { InventoryAdjustment } from '../models/InventoryAdjustment';
|
||||
import { IInventoryAdjustmentsFilter } from '../types/InventoryAdjustments.types';
|
||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class GetInventoryAdjustmentsService {
|
||||
constructor(
|
||||
private readonly transformer: TransformerInjectable,
|
||||
private readonly dynamicListService: DynamicListService,
|
||||
|
||||
@Inject(InventoryAdjustment.name)
|
||||
private readonly inventoryAdjustmentModel: TenantModelProxy<
|
||||
typeof InventoryAdjustment
|
||||
>,
|
||||
) {}
|
||||
/**
|
||||
* Retrieve the inventory adjustments paginated list.
|
||||
* @param {number} tenantId
|
||||
* @param {IInventoryAdjustmentsFilter} adjustmentsFilter
|
||||
*/
|
||||
public async getInventoryAdjustments(
|
||||
filterDTO: IInventoryAdjustmentsFilter,
|
||||
): Promise<{
|
||||
inventoryAdjustments: InventoryAdjustment[];
|
||||
pagination: IPaginationMeta;
|
||||
}> {
|
||||
// Parses inventory adjustments list filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
this.inventoryAdjustmentModel(),
|
||||
filter,
|
||||
);
|
||||
const { results, pagination } = await this.inventoryAdjustmentModel()
|
||||
.query()
|
||||
.onBuild((query) => {
|
||||
query.withGraphFetched('entries.item');
|
||||
query.withGraphFetched('adjustmentAccount');
|
||||
|
||||
dynamicFilter.buildQuery()(query);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// Retrieves the transformed inventory adjustments.
|
||||
const inventoryAdjustments = await this.transformer.transform(
|
||||
results,
|
||||
new InventoryAdjustmentTransformer(),
|
||||
);
|
||||
return {
|
||||
inventoryAdjustments,
|
||||
pagination,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses inventory adjustments list filter DTO.
|
||||
* @param filterDTO -
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { InventoryAdjustmentsGLEntries } from '../commands/ledger/InventoryAdjustmentsGLEntries';
|
||||
import { IInventoryAdjustmentEventDeletedPayload } from '../types/InventoryAdjustments.types';
|
||||
import { IInventoryAdjustmentEventCreatedPayload } from '../types/InventoryAdjustments.types';
|
||||
import { events } from '@/common/events/events';
|
||||
|
||||
@Injectable()
|
||||
export class InventoryAdjustmentsGLSubscriber {
|
||||
constructor(
|
||||
private readonly inventoryAdjustmentGL: InventoryAdjustmentsGLEntries,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handles writing increment inventory adjustment GL entries.
|
||||
*/
|
||||
@OnEvent(events.inventoryAdjustment.onQuickCreated)
|
||||
@OnEvent(events.inventoryAdjustment.onPublished)
|
||||
public async handleGLEntriesOnceIncrementAdjustmentCreated({
|
||||
inventoryAdjustmentId,
|
||||
inventoryAdjustment,
|
||||
trx,
|
||||
}: IInventoryAdjustmentEventCreatedPayload) {
|
||||
// Can't continue if the inventory adjustment is not published.
|
||||
if (!inventoryAdjustment.isPublished) {
|
||||
return;
|
||||
}
|
||||
// Can't continue if the inventory adjustment direction is not `IN`.
|
||||
if (inventoryAdjustment.type !== 'increment') {
|
||||
return;
|
||||
}
|
||||
await this.inventoryAdjustmentGL.writeAdjustmentGLEntries(
|
||||
inventoryAdjustmentId,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts the inventory adjustment GL entries once the transaction deleted.
|
||||
* @param {IInventoryAdjustmentEventDeletedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.inventoryAdjustment.onDeleted)
|
||||
public async revertAdjustmentGLEntriesOnceDeleted({
|
||||
inventoryAdjustmentId,
|
||||
oldInventoryAdjustment,
|
||||
}: IInventoryAdjustmentEventDeletedPayload) {
|
||||
// Can't continue if the inventory adjustment is not published.
|
||||
if (!oldInventoryAdjustment.isPublished) {
|
||||
return;
|
||||
}
|
||||
await this.inventoryAdjustmentGL.revertAdjustmentGLEntries(
|
||||
inventoryAdjustmentId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles writing inventory transactions once the quick adjustment created.
|
||||
* @param {IInventoryAdjustmentEventPublishedPayload} payload
|
||||
* @param {IInventoryAdjustmentEventCreatedPayload} payload -
|
||||
*/
|
||||
// private handleWriteInventoryTransactionsOncePublished = async ({
|
||||
// inventoryAdjustment,
|
||||
// trx,
|
||||
// }:
|
||||
// | IInventoryAdjustmentEventPublishedPayload
|
||||
// | IInventoryAdjustmentEventCreatedPayload) => {
|
||||
// await this.inventoryAdjustment.writeInventoryTransactions(
|
||||
// tenantId,
|
||||
// inventoryAdjustment,
|
||||
// false,
|
||||
// trx
|
||||
// );
|
||||
// };
|
||||
|
||||
/**
|
||||
* Handles reverting invetory transactions once the inventory adjustment deleted.
|
||||
* @param {IInventoryAdjustmentEventDeletedPayload} payload -
|
||||
*/
|
||||
// private handleRevertInventoryTransactionsOnceDeleted = async ({
|
||||
// tenantId,
|
||||
// inventoryAdjustmentId,
|
||||
// oldInventoryAdjustment,
|
||||
// trx,
|
||||
// }: IInventoryAdjustmentEventDeletedPayload) => {
|
||||
// // Can't continue if the inventory adjustment is not published.
|
||||
// if (!oldInventoryAdjustment.isPublished) {
|
||||
// return;
|
||||
// }
|
||||
// // Reverts the inventory transactions of adjustment transaction.
|
||||
// await this.inventoryAdjustment.revertInventoryTransactions(
|
||||
// tenantId,
|
||||
// inventoryAdjustmentId,
|
||||
// trx
|
||||
// );
|
||||
// };
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Knex } from 'knex';
|
||||
import { InventoryAdjustment } from '../models/InventoryAdjustment';
|
||||
import { CreateQuickInventoryAdjustmentDto } from '../dtos/CreateQuickInventoryAdjustment.dto';
|
||||
|
||||
type IAdjustmentTypes = 'increment' | 'decrement';
|
||||
|
||||
export interface IQuickInventoryAdjustmentDTO {
|
||||
date: Date;
|
||||
type: IAdjustmentTypes;
|
||||
adjustmentAccountId: number;
|
||||
reason: string;
|
||||
description: string;
|
||||
referenceNo: string;
|
||||
itemId: number;
|
||||
quantity: number;
|
||||
cost: number;
|
||||
publish: boolean;
|
||||
|
||||
warehouseId?: number;
|
||||
branchId?: number;
|
||||
}
|
||||
export interface IInventoryAdjustmentsFilter {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
export interface IInventoryAdjustmentEventCreatedPayload {
|
||||
inventoryAdjustment: InventoryAdjustment;
|
||||
inventoryAdjustmentId: number;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
export interface IInventoryAdjustmentCreatingPayload {
|
||||
quickAdjustmentDTO: CreateQuickInventoryAdjustmentDto;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IInventoryAdjustmentEventPublishedPayload {
|
||||
inventoryAdjustmentId: number;
|
||||
inventoryAdjustment: InventoryAdjustment;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IInventoryAdjustmentPublishingPayload {
|
||||
trx: Knex.Transaction;
|
||||
oldInventoryAdjustment: InventoryAdjustment;
|
||||
}
|
||||
export interface IInventoryAdjustmentEventDeletedPayload {
|
||||
inventoryAdjustmentId: number;
|
||||
oldInventoryAdjustment: InventoryAdjustment;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IInventoryAdjustmentDeletingPayload {
|
||||
oldInventoryAdjustment: InventoryAdjustment;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export enum InventoryAdjustmentAction {
|
||||
CREATE = 'Create',
|
||||
EDIT = 'Edit',
|
||||
DELETE = 'Delete',
|
||||
VIEW = 'View',
|
||||
}
|
||||
Reference in New Issue
Block a user