feat(nestjs): migrate to NestJS

This commit is contained in:
Ahmed Bouhuolia
2025-04-07 11:51:24 +02:00
parent f068218a16
commit 55fcc908ef
3779 changed files with 631 additions and 195332 deletions

View File

@@ -0,0 +1,119 @@
import { Injectable } from '@nestjs/common';
import { ModelObject } from 'objection';
import { IGetWarehousesTransfersFilterDTO } from '@/modules/Warehouses/Warehouse.types';
import { CreateWarehouseTransfer } from './commands/CreateWarehouseTransfer';
import { DeleteWarehouseTransfer } from './commands/DeleteWarehouseTransfer';
import { EditWarehouseTransfer } from './commands/EditWarehouseTransfer';
import { GetWarehouseTransfer } from './queries/GetWarehouseTransfer';
import { GetWarehouseTransfers } from './queries/GetWarehouseTransfers';
import { InitiateWarehouseTransfer } from './commands/InitiateWarehouseTransfer';
import { TransferredWarehouseTransfer } from './commands/TransferredWarehouseTransfer';
import { WarehouseTransfer } from './models/WarehouseTransfer';
import {
CreateWarehouseTransferDto,
EditWarehouseTransferDto,
} from './dtos/WarehouseTransfer.dto';
@Injectable()
export class WarehouseTransferApplication {
constructor(
private readonly createWarehouseTransferService: CreateWarehouseTransfer,
private readonly editWarehouseTransferService: EditWarehouseTransfer,
private readonly deleteWarehouseTransferService: DeleteWarehouseTransfer,
private readonly getWarehouseTransferService: GetWarehouseTransfer,
private readonly getWarehousesTransfersService: GetWarehouseTransfers,
private readonly initiateWarehouseTransferService: InitiateWarehouseTransfer,
private readonly transferredWarehouseTransferService: TransferredWarehouseTransfer,
) {}
/**
* Creates a warehouse transfer transaction.
* @param {ICreateWarehouseTransferDTO} createWarehouseTransferDTO
* @returns {Promise<ModelObject<WarehouseTransfer>>}
*/
public createWarehouseTransfer = (
createWarehouseTransferDTO: CreateWarehouseTransferDto,
): Promise<ModelObject<WarehouseTransfer>> => {
return this.createWarehouseTransferService.createWarehouseTransfer(
createWarehouseTransferDTO,
);
};
/**
* Edits warehouse transfer transaction.
* @param {number} warehouseTransferId - number
* @param {IEditWarehouseTransferDTO} editWarehouseTransferDTO
*/
public editWarehouseTransfer = (
warehouseTransferId: number,
editWarehouseTransferDTO: EditWarehouseTransferDto,
): Promise<ModelObject<WarehouseTransfer>> => {
return this.editWarehouseTransferService.editWarehouseTransfer(
warehouseTransferId,
editWarehouseTransferDTO,
);
};
/**
* Deletes warehouse transfer transaction.
* @param {number} warehouseTransferId
* @returns {Promise<void>}
*/
public deleteWarehouseTransfer = (
warehouseTransferId: number,
): Promise<void> => {
return this.deleteWarehouseTransferService.deleteWarehouseTransfer(
warehouseTransferId,
);
};
/**
* Retrieves warehouse transfer transaction.
* @param {number} warehouseTransferId - Warehouse transfer id.
* @returns {Promise<ModelObject<WarehouseTransfer>>}
*/
public getWarehouseTransfer = (
warehouseTransferId: number,
): Promise<ModelObject<WarehouseTransfer>> => {
return this.getWarehouseTransferService.getWarehouseTransfer(
warehouseTransferId,
);
};
/**
* Retrieves warehouses trans
* @param {IGetWarehousesTransfersFilterDTO} filterDTO
* @returns {Promise<IWarehouseTransfer>}
*/
public getWarehousesTransfers = (
filterDTO: IGetWarehousesTransfersFilterDTO,
) => {
return this.getWarehousesTransfersService.getWarehouseTransfers(filterDTO);
};
/**
* Marks the warehouse transfer order as transfered.
* @param {number} warehouseTransferId
* @returns {Promise<IWarehouseTransfer>}
*/
public transferredWarehouseTransfer = (
warehouseTransferId: number,
): Promise<ModelObject<WarehouseTransfer>> => {
return this.transferredWarehouseTransferService.transferredWarehouseTransfer(
warehouseTransferId,
);
};
/**
* Marks the warehouse transfer order as initiated.
* @param {number} warehouseTransferId
* @returns {Promise<ModelObject<WarehouseTransfer>>}
*/
public initiateWarehouseTransfer = (
warehouseTransferId: number,
): Promise<ModelObject<WarehouseTransfer>> => {
return this.initiateWarehouseTransferService.initiateWarehouseTransfer(
warehouseTransferId,
);
};
}

View File

@@ -0,0 +1,178 @@
import {
Controller,
Post,
Put,
Get,
Delete,
Body,
Param,
Query,
Inject,
} from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { WarehouseTransferApplication } from './WarehouseTransferApplication';
import {
CreateWarehouseTransferDto,
EditWarehouseTransferDto,
} from './dtos/WarehouseTransfer.dto';
@Controller('warehouse-transfers')
@ApiTags('warehouse-transfers')
export class WarehouseTransfersController {
/**
* @param {WarehouseTransferApplication} warehouseTransferApplication - Warehouse transfer application.
*/
constructor(
@Inject(WarehouseTransferApplication)
private readonly warehouseTransferApplication: WarehouseTransferApplication,
) {}
/**
* Creates a new warehouse transfer transaction.
*/
@Post()
@ApiOperation({ summary: 'Create a new warehouse transfer transaction.' })
@ApiResponse({
status: 200,
description:
'The warehouse transfer transaction has been created successfully.',
})
async createWarehouseTransfer(
@Body() createWarehouseTransferDTO: CreateWarehouseTransferDto,
) {
const warehouse =
await this.warehouseTransferApplication.createWarehouseTransfer(
createWarehouseTransferDTO,
);
return {
id: warehouse.id,
message:
'The warehouse transfer transaction has been created successfully.',
};
}
/**
* Edits warehouse transfer transaction.
*/
@Post(':id')
@ApiOperation({ summary: 'Edit the given warehouse transfer transaction.' })
@ApiResponse({
status: 200,
description:
'The warehouse transfer transaction has been edited successfully.',
})
async editWarehouseTransfer(
@Param('id') id: number,
@Body() editWarehouseTransferDTO: EditWarehouseTransferDto,
) {
const warehouseTransfer =
await this.warehouseTransferApplication.editWarehouseTransfer(
id,
editWarehouseTransferDTO,
);
return {
id: warehouseTransfer.id,
message:
'The warehouse transfer transaction has been edited successfully.',
};
}
/**
* Initiates the warehouse transfer.
*/
@Put(':id/initiate')
@ApiOperation({ summary: 'Initiate the given warehouse transfer.' })
@ApiResponse({
status: 200,
description: 'The warehouse transfer has been initiated successfully.',
})
async initiateTransfer(@Param('id') id: number) {
await this.warehouseTransferApplication.initiateWarehouseTransfer(id);
return {
id,
message: 'The given warehouse transfer has been initialized.',
};
}
/**
* Marks the given warehouse transfer as transferred.
*/
@Put(':id/transferred')
@ApiOperation({
summary: 'Mark the given warehouse transfer as transferred.',
})
@ApiResponse({
status: 200,
description:
'The warehouse transfer has been marked as transferred successfully.',
})
async deliverTransfer(@Param('id') id: number) {
await this.warehouseTransferApplication.transferredWarehouseTransfer(id);
return {
id,
message: 'The given warehouse transfer has been delivered.',
};
}
/**
* Retrieves warehouse transfer transactions with pagination.
*/
@Get()
@ApiOperation({
summary: 'Retrieve warehouse transfer transactions with pagination.',
})
@ApiResponse({
status: 200,
description:
'The warehouse transfer transactions have been retrieved successfully.',
})
async getWarehousesTransfers(@Query() query: any) {
const { warehousesTransfers, pagination, filter } =
await this.warehouseTransferApplication.getWarehousesTransfers(query);
return {
data: warehousesTransfers,
pagination,
filter,
};
}
/**
* Retrieves warehouse transfer transaction details.
*/
@Get(':id')
@ApiOperation({ summary: 'Retrieve warehouse transfer transaction details.' })
@ApiResponse({
status: 200,
description:
'The warehouse transfer transaction details have been retrieved successfully.',
})
async getWarehouseTransfer(@Param('id') id: number) {
const warehouseTransfer =
await this.warehouseTransferApplication.getWarehouseTransfer(id);
return { data: warehouseTransfer };
}
/**
* Deletes the given warehouse transfer transaction.
*/
@Delete(':id')
@ApiOperation({ summary: 'Delete the given warehouse transfer transaction.' })
@ApiResponse({
status: 200,
description:
'The warehouse transfer transaction has been deleted successfully.',
})
async deleteWarehouseTransfer(@Param('id') id: number) {
await this.warehouseTransferApplication.deleteWarehouseTransfer(id);
return {
message:
'The warehouse transfer transaction has been deleted successfully.',
};
}
}

View File

@@ -0,0 +1,55 @@
import { Module } from '@nestjs/common';
import { CreateWarehouseTransfer } from './commands/CreateWarehouseTransfer';
import { EditWarehouseTransfer } from './commands/EditWarehouseTransfer';
import { DeleteWarehouseTransfer } from './commands/DeleteWarehouseTransfer';
import { GetWarehouseTransfer } from './queries/GetWarehouseTransfer';
import { GetWarehouseTransfers } from './queries/GetWarehouseTransfers';
import { WarehouseTransferApplication } from './WarehouseTransferApplication';
import { WarehouseTransfersController } from './WarehouseTransfers.controller';
import { WarehouseTransferInventoryTransactions } from './commands/WarehouseTransferWriteInventoryTransactions';
import { WarehouseTransferAutoIncrement } from './commands/WarehouseTransferAutoIncrement';
import { WarehouseTransferAutoIncrementSubscriber } from './susbcribers/WarehouseTransferAutoIncrementSubscriber';
import { WarehouseTransferInventoryTransactionsSubscriber } from './susbcribers/WarehouseTransferInventoryTransactionsSubscriber';
import { InitiateWarehouseTransfer } from './commands/InitiateWarehouseTransfer';
import { TransferredWarehouseTransfer } from './commands/TransferredWarehouseTransfer';
import { CommandWarehouseTransfer } from './commands/CommandWarehouseTransfer';
import { ItemsModule } from '../Items/items.module';
import { InventoryCostModule } from '../InventoryCost/InventoryCost.module';
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
import { WarehouseTransfer } from './models/WarehouseTransfer';
import { WarehouseTransferEntry } from './models/WarehouseTransferEntry';
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
import { AutoIncrementOrdersModule } from '../AutoIncrementOrders/AutoIncrementOrders.module';
const models = [
RegisterTenancyModel(WarehouseTransfer),
RegisterTenancyModel(WarehouseTransferEntry),
];
@Module({
imports: [
ItemsModule,
InventoryCostModule,
DynamicListModule,
AutoIncrementOrdersModule,
...models,
],
providers: [
WarehouseTransferApplication,
CreateWarehouseTransfer,
EditWarehouseTransfer,
DeleteWarehouseTransfer,
GetWarehouseTransfer,
GetWarehouseTransfers,
WarehouseTransferInventoryTransactions,
WarehouseTransferAutoIncrement,
WarehouseTransferAutoIncrementSubscriber,
WarehouseTransferInventoryTransactionsSubscriber,
TransferredWarehouseTransfer,
InitiateWarehouseTransfer,
CommandWarehouseTransfer,
],
exports: [...models],
controllers: [WarehouseTransfersController],
})
export class WarehousesTransfersModule {}

View File

@@ -0,0 +1,113 @@
import { ModelObject } from 'objection';
import { Inject, Injectable } from '@nestjs/common';
import { ERRORS } from '../constants';
import { WarehouseTransfer } from '../models/WarehouseTransfer';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { ServiceError } from '@/modules/Items/ServiceError';
import { Item } from '@/modules/Items/models/Item';
import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model';
import {
CreateWarehouseTransferDto,
EditWarehouseTransferDto,
} from '../dtos/WarehouseTransfer.dto';
@Injectable()
export class CommandWarehouseTransfer {
constructor(
@Inject(Warehouse.name)
private readonly warehouseModel: TenantModelProxy<typeof Warehouse>,
@Inject(WarehouseTransfer.name)
private readonly warehouseTransferModel: TenantModelProxy<
typeof WarehouseTransfer
>,
) {}
/**
* Throws error if warehouse transfer not found.
* @param {WarehouseTransfer} warehouseTransfer
*/
throwIfTransferNotFound = (warehouseTransfer: WarehouseTransfer) => {
if (!warehouseTransfer) {
throw new ServiceError(ERRORS.WAREHOUSE_TRANSFER_NOT_FOUND);
}
};
/**
* Retrieves the warehouse transfer or throw not found service error.
* @param {number} branchId
* @returns {Promise<WarehouseTransfer>}
*/
async getWarehouseTransferOrThrowNotFound(branchId: number) {
const foundTransfer = await this.warehouseTransferModel()
.query()
.findById(branchId);
if (!foundTransfer) {
throw new ServiceError(ERRORS.WAREHOUSE_TRANSFER_NOT_FOUND);
}
return foundTransfer;
}
/**
* Validate the from/to warehouses should not be the same.
* @param {ICreateWarehouseTransferDTO|IEditWarehouseTransferDTO} warehouseTransferDTO
*/
validateWarehouseFromToNotSame(
warehouseTransferDTO: CreateWarehouseTransferDto | EditWarehouseTransferDto,
) {
if (
warehouseTransferDTO.fromWarehouseId ===
warehouseTransferDTO.toWarehouseId
) {
throw new ServiceError(ERRORS.WAREHOUSES_TRANSFER_SHOULD_NOT_BE_SAME);
}
}
/**
* Validates entries items should be inventory.
* @param {ModelObject<Item>[]} items - Items.
* @returns {void}
*/
validateItemsShouldBeInventory = (items: ModelObject<Item>[]): void => {
const nonInventoryItems = items.filter((item) => item.type !== 'inventory');
if (nonInventoryItems.length > 0) {
throw new ServiceError(
ERRORS.WAREHOUSE_TRANSFER_ITEMS_SHOULD_BE_INVENTORY,
);
}
};
/**
* Retrieves the to warehouse or throw not found service error.
* @param {number} fromWarehouseId - From warehouse id.
* @returns {Promise<Warehouse>}
*/
getToWarehouseOrThrow = async (fromWarehouseId: number) => {
const warehouse = await this.warehouseModel()
.query()
.findById(fromWarehouseId);
if (!warehouse) {
throw new ServiceError(ERRORS.TO_WAREHOUSE_NOT_FOUND);
}
return warehouse;
};
/**
*
* @param {number} fromWarehouseId
* @returns
*/
getFromWarehouseOrThrow = async (fromWarehouseId: number) => {
const warehouse = await this.warehouseModel()
.query()
.findById(fromWarehouseId);
if (!warehouse) {
throw new ServiceError(ERRORS.FROM_WAREHOUSE_NOT_FOUND);
}
return warehouse;
};
}

View File

@@ -0,0 +1,200 @@
import { Knex } from 'knex';
import { omit, get, isNumber } from 'lodash';
import * as R from 'ramda';
import {
ICreateWarehouseTransferDTO,
IWarehouseTransferCreate,
IWarehouseTransferCreated,
IWarehouseTransferEntryDTO,
} from '@/modules/Warehouses/Warehouse.types';
import { CommandWarehouseTransfer } from './CommandWarehouseTransfer';
import { WarehouseTransferAutoIncrement } from './WarehouseTransferAutoIncrement';
import { WarehouseTransfer } from '../models/WarehouseTransfer';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
import { InventoryItemCostService } from '@/modules/InventoryCost/commands/InventoryCosts.service';
import { Inject, Injectable } from '@nestjs/common';
import { events } from '@/common/events/events';
import { IInventoryItemCostMeta } from '@/modules/InventoryCost/types/InventoryCost.types';
import { ModelObject } from 'objection';
import { WarehouseTransferEntry } from '../models/WarehouseTransferEntry';
import {
CreateWarehouseTransferDto,
WarehouseTransferEntryDto,
} from '../dtos/WarehouseTransfer.dto';
@Injectable()
export class CreateWarehouseTransfer {
/**
* @param {UnitOfWork} uow - Unit of work.
* @param {EventEmitter2} eventPublisher - Event publisher.
* @param {ItemsEntriesService} itemsEntries - Items entries service.
* @param {InventoryItemCostService} inventoryItemCost - Inventory item cost service.
* @param {WarehouseTransferAutoIncrement} autoIncrementOrders - Warehouse transfer auto increment.
* @param {CommandWarehouseTransfer} commandWarehouseTransfer - Command warehouse transfer.
* @param {TenantModelProxy<typeof WarehouseTransfer>} warehouseTransferModel - Warehouse transfer model.
*/
constructor(
private readonly uow: UnitOfWork,
private readonly eventPublisher: EventEmitter2,
private readonly itemsEntries: ItemsEntriesService,
private readonly inventoryItemCost: InventoryItemCostService,
private readonly autoIncrementOrders: WarehouseTransferAutoIncrement,
private readonly commandWarehouseTransfer: CommandWarehouseTransfer,
@Inject(WarehouseTransfer.name)
private readonly warehouseTransferModel: TenantModelProxy<
typeof WarehouseTransfer
>,
) {}
/**
* Transformes the givne new warehouse transfer DTO to model.
* @param {ICreateWarehouseTransferDTO} warehouseTransferDTO
* @returns {IWarehouseTransfer}
*/
private transformDTOToModel = async (
warehouseTransferDTO: CreateWarehouseTransferDto,
): Promise<ModelObject<WarehouseTransfer>> => {
const entries = await this.transformEntries(
warehouseTransferDTO,
warehouseTransferDTO.entries,
);
// Retrieves the auto-increment the warehouse transfer number.
const autoNextNumber = this.autoIncrementOrders.getNextTransferNumber();
// Warehouse transfer order transaction number.
const transactionNumber =
warehouseTransferDTO.transactionNumber || autoNextNumber;
return {
...omit(warehouseTransferDTO, ['transferDelivered', 'transferInitiated']),
transactionNumber,
...(warehouseTransferDTO.transferDelivered
? {
transferDeliveredAt: new Date(),
}
: {}),
...(warehouseTransferDTO.transferDelivered ||
warehouseTransferDTO.transferInitiated
? {
transferInitiatedAt: new Date(),
}
: {}),
entries,
};
};
/**
* Assoc average cost to the entry that has no cost.
* @param {Promise<Map<number, IInventoryItemCostMeta>} inventoryItemsCostMap -
* @param {IWarehouseTransferEntryDTO} entry -
*/
private transformEntryAssocAverageCost = R.curry(
(
inventoryItemsCostMap: Map<number, IInventoryItemCostMeta>,
entry: WarehouseTransferEntryDto,
): WarehouseTransferEntryDto => {
const itemValuation = inventoryItemsCostMap.get(entry.itemId);
const itemCost = get(itemValuation, 'average', 0);
return isNumber(entry.cost) ? entry : R.assoc('cost', itemCost, entry);
},
);
/**
* Transformes warehouse transfer entries.
* @param {ICreateWarehouseTransferDTO} warehouseTransferDTO
* @param {IWarehouseTransferEntryDTO[]} entries
* @returns {Promise<IWarehouseTransferEntryDTO[]>}
*/
public transformEntries = async (
warehouseTransferDTO: CreateWarehouseTransferDto,
entries: WarehouseTransferEntryDto[],
): Promise<ModelObject<WarehouseTransferEntry>[]> => {
const inventoryItemsIds = warehouseTransferDTO.entries.map((e) => e.itemId);
// Retrieves the inventory items valuation map.
const inventoryItemsCostMap =
await this.inventoryItemCost.getItemsInventoryValuation(
inventoryItemsIds,
warehouseTransferDTO.date,
);
// Assoc average cost to the entry.
const assocAverageCost = this.transformEntryAssocAverageCost(
inventoryItemsCostMap,
);
return entries.map((entry) => assocAverageCost(entry));
};
/**
* Authorize warehouse transfer before creating.
* @param {CreateWarehouseTransferDto} warehouseTransferDTO - Warehouse transfer DTO.
*/
public authorize = async (
warehouseTransferDTO: CreateWarehouseTransferDto,
) => {
// Validate warehouse from and to should not be the same.
this.commandWarehouseTransfer.validateWarehouseFromToNotSame(
warehouseTransferDTO,
);
// Retrieves the from warehouse or throw not found service error.
const fromWarehouse =
await this.commandWarehouseTransfer.getFromWarehouseOrThrow(
warehouseTransferDTO.fromWarehouseId,
);
// Retrieves the to warehouse or throw not found service error.
const toWarehouse =
await this.commandWarehouseTransfer.getToWarehouseOrThrow(
warehouseTransferDTO.toWarehouseId,
);
// Validates the not found entries items ids.
const items = await this.itemsEntries.validateItemsIdsExistance(
warehouseTransferDTO.entries,
);
// Validate the items entries should be inventory type.
this.commandWarehouseTransfer.validateItemsShouldBeInventory(items);
};
/**
* Creates a new warehouse transfer transaction.
* @param {ICreateWarehouseTransferDTO} warehouseDTO -
* @returns {Promise<ModelObject<WarehouseTransfer>>}
*/
public createWarehouseTransfer = async (
warehouseTransferDTO: CreateWarehouseTransferDto,
): Promise<ModelObject<WarehouseTransfer>> => {
// Authorize warehouse transfer before creating.
await this.authorize(warehouseTransferDTO);
// Transformes the warehouse transfer DTO to model.
const warehouseTransferModel =
await this.transformDTOToModel(warehouseTransferDTO);
// Create warehouse transfer under unit-of-work.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onWarehouseTransferCreate` event.
await this.eventPublisher.emitAsync(events.warehouseTransfer.onCreate, {
trx,
warehouseTransferDTO,
} as IWarehouseTransferCreate);
// Stores the warehouse transfer transaction graph to the storage.
const warehouseTransfer = await this.warehouseTransferModel()
.query(trx)
.upsertGraphAndFetch({
...warehouseTransferModel,
});
// Triggers `onWarehouseTransferCreated` event.
await this.eventPublisher.emitAsync(events.warehouseTransfer.onCreated, {
trx,
warehouseTransfer,
warehouseTransferDTO,
} as IWarehouseTransferCreated);
return warehouseTransfer;
});
};
}

View File

@@ -0,0 +1,77 @@
import { Knex } from 'knex';
import {
IWarehouseTransferDeletedPayload,
IWarehouseTransferDeletePayload,
} from '@/modules/Warehouses/Warehouse.types';
import { Inject, Injectable } from '@nestjs/common';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { events } from '@/common/events/events';
import { WarehouseTransfer } from '../models/WarehouseTransfer';
import { WarehouseTransferEntry } from '../models/WarehouseTransferEntry';
@Injectable()
export class DeleteWarehouseTransfer {
/**
* @param {UnitOfWork} uow - Unit of work service.
* @param {EventEmitter2} eventPublisher - Event emitter service.
* @param {TenantModelProxy<WarehouseTransfer>} warehouseTransferModel - Warehouse transfer model.
* @param {TenantModelProxy<WarehouseTransferEntry>} warehouseTransferEntryModel - Warehouse transfer entry model.
*/
constructor(
private readonly uow: UnitOfWork,
private readonly eventPublisher: EventEmitter2,
@Inject(WarehouseTransfer.name)
private readonly warehouseTransferModel: TenantModelProxy<
typeof WarehouseTransfer
>,
@Inject(WarehouseTransferEntry.name)
private readonly warehouseTransferEntryModel: TenantModelProxy<
typeof WarehouseTransferEntry
>,
) {}
/**
* Deletes warehouse transfer transaction.
* @param {number} warehouseTransferId
* @returns {Promise<void>}
*/
public deleteWarehouseTransfer = async (
warehouseTransferId: number,
): Promise<void> => {
// Retrieve the old warehouse transfer or throw not found service error.
const oldWarehouseTransfer = await this.warehouseTransferModel()
.query()
.findById(warehouseTransferId)
.throwIfNotFound();
// Deletes the warehouse transfer under unit-of-work envirement.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onWarehouseTransferCreate` event.
await this.eventPublisher.emitAsync(events.warehouseTransfer.onDelete, {
oldWarehouseTransfer,
trx,
} as IWarehouseTransferDeletePayload);
// Delete warehouse transfer entries.
await this.warehouseTransferEntryModel()
.query(trx)
.where('warehouseTransferId', warehouseTransferId)
.delete();
// Delete warehouse transfer.
await this.warehouseTransferModel()
.query(trx)
.findById(warehouseTransferId)
.delete();
// Triggers `onWarehouseTransferDeleted` event
await this.eventPublisher.emitAsync(events.warehouseTransfer.onDeleted, {
oldWarehouseTransfer,
trx,
} as IWarehouseTransferDeletedPayload);
});
};
}

View File

@@ -0,0 +1,103 @@
import { Knex } from 'knex';
import {
IEditWarehouseTransferDTO,
IWarehouseTransferEditPayload,
IWarehouseTransferEditedPayload,
} from '@/modules/Warehouses/Warehouse.types';
import { CommandWarehouseTransfer } from './CommandWarehouseTransfer';
import { Inject, Injectable } from '@nestjs/common';
import { TenantModelProxy } from '../../System/models/TenantBaseModel';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { WarehouseTransfer } from '../models/WarehouseTransfer';
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
import { ModelObject } from 'objection';
import { EditWarehouseTransferDto } from '../dtos/WarehouseTransfer.dto';
@Injectable()
export class EditWarehouseTransfer {
/**
* @param {UnitOfWork} uow - Unit of work service.
* @param {EventEmitter2} eventPublisher - Event emitter service.
* @param {CommandWarehouseTransfer} commandWarehouseTransfer - Command warehouse transfer service.
* @param {ItemsEntriesService} itemsEntries - Items entries service.
* @param {TenantModelProxy<WarehouseTransfer>} warehouseTransferModel - Warehouse transfer model.
*/
constructor(
private readonly uow: UnitOfWork,
private readonly eventPublisher: EventEmitter2,
private readonly commandWarehouseTransfer: CommandWarehouseTransfer,
private readonly itemsEntries: ItemsEntriesService,
@Inject(WarehouseTransfer.name)
private readonly warehouseTransferModel: TenantModelProxy<
typeof WarehouseTransfer
>,
) {}
/**
* Edits warehouse transfer.
* @param {number} warehouseTransferId - Warehouse transfer id.
* @param {IEditWarehouseTransferDTO} editWarehouseDTO -
* @returns {Promise<ModelObject<WarehouseTransfer>>}
*/
public editWarehouseTransfer = async (
warehouseTransferId: number,
editWarehouseDTO: EditWarehouseTransferDto,
): Promise<ModelObject<WarehouseTransfer>> => {
// Retrieves the old warehouse transfer transaction.
const oldWarehouseTransfer = await this.warehouseTransferModel()
.query()
.findById(warehouseTransferId)
.throwIfNotFound();
// Validate warehouse from and to should not be the same.
this.commandWarehouseTransfer.validateWarehouseFromToNotSame(
editWarehouseDTO,
);
// Retrieves the from warehouse or throw not found service error.
const fromWarehouse =
await this.commandWarehouseTransfer.getFromWarehouseOrThrow(
editWarehouseDTO.fromWarehouseId,
);
// Retrieves the to warehouse or throw not found service error.
const toWarehouse =
await this.commandWarehouseTransfer.getToWarehouseOrThrow(
editWarehouseDTO.toWarehouseId,
);
// Validates the not found entries items ids.
const items = await this.itemsEntries.validateItemsIdsExistance(
editWarehouseDTO.entries,
);
// Validate the items entries should be inventory type.
this.commandWarehouseTransfer.validateItemsShouldBeInventory(items);
// Edits warehouse transfer transaction under unit-of-work envirement.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onWarehouseTransferEdit` event.
await this.eventPublisher.emitAsync(events.warehouseTransfer.onEdit, {
editWarehouseDTO,
oldWarehouseTransfer,
trx,
} as IWarehouseTransferEditPayload);
// Updates warehouse transfer graph on the storage.
const warehouseTransfer = await this.warehouseTransferModel()
.query(trx)
.upsertGraphAndFetch({
id: warehouseTransferId,
...editWarehouseDTO,
});
// Triggers `onWarehouseTransferEdit` event
await this.eventPublisher.emitAsync(events.warehouseTransfer.onEdited, {
editWarehouseDTO,
warehouseTransfer,
oldWarehouseTransfer,
trx,
} as IWarehouseTransferEditedPayload);
return warehouseTransfer;
});
};
}

View File

@@ -0,0 +1,92 @@
import { Knex } from 'knex';
import {
IWarehouseTransferEditedPayload,
IWarehouseTransferInitiatedPayload,
IWarehouseTransferInitiatePayload,
} from '@/modules/Warehouses/Warehouse.types';
import { ERRORS } from '../constants';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { WarehouseTransfer } from '../models/WarehouseTransfer';
import { Inject } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { ServiceError } from '@/modules/Items/ServiceError';
import { events } from '@/common/events/events';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { ModelObject } from 'objection';
@Injectable()
export class InitiateWarehouseTransfer {
constructor(
private readonly uow: UnitOfWork,
private readonly eventPublisher: EventEmitter2,
@Inject(WarehouseTransfer.name)
private readonly warehouseTransferModel: TenantModelProxy<
typeof WarehouseTransfer
>,
) {}
/**
* Validate the given warehouse transfer not already initiated.
* @param {IWarehouseTransfer} warehouseTransfer
*/
private validateWarehouseTransferNotAlreadyInitiated = (
warehouseTransfer: ModelObject<WarehouseTransfer>,
) => {
if (warehouseTransfer.transferInitiatedAt) {
throw new ServiceError(ERRORS.WAREHOUSE_TRANSFER_ALREADY_INITIATED);
}
};
/**
* Initiate warehouse transfer.
* @param {number} warehouseTransferId
* @returns {Promise<IWarehouseTransfer>}
*/
public initiateWarehouseTransfer = async (
warehouseTransferId: number,
): Promise<ModelObject<WarehouseTransfer>> => {
// Retrieves the old warehouse transfer transaction.
const oldWarehouseTransfer = await this.warehouseTransferModel()
.query()
.findById(warehouseTransferId)
.throwIfNotFound();
// Validate the given warehouse transfer not already initiated.
this.validateWarehouseTransferNotAlreadyInitiated(oldWarehouseTransfer);
// Edits warehouse transfer transaction under unit-of-work envirement.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onWarehouseTransferInitiate` event.
await this.eventPublisher.emitAsync(events.warehouseTransfer.onInitiate, {
oldWarehouseTransfer,
trx,
} as IWarehouseTransferInitiatePayload);
// Updates warehouse transfer graph on the storage.
const warehouseTransferUpdated = await this.warehouseTransferModel()
.query(trx)
.findById(warehouseTransferId)
.patch({
transferInitiatedAt: new Date(),
});
// Fetches the warehouse transfer with entries.
const warehouseTransfer = await this.warehouseTransferModel()
.query(trx)
.findById(warehouseTransferId)
.withGraphFetched('entries');
// Triggers `onWarehouseTransferEdit` event
await this.eventPublisher.emitAsync(
events.warehouseTransfer.onInitiated,
{
warehouseTransfer,
oldWarehouseTransfer,
trx,
} as IWarehouseTransferInitiatedPayload,
);
return warehouseTransfer;
});
};
}

View File

@@ -0,0 +1,106 @@
import { Knex } from 'knex';
import {
IWarehouseTransferTransferingPayload,
IWarehouseTransferTransferredPayload,
} from '@/modules/Warehouses/Warehouse.types';
import { CommandWarehouseTransfer } from './CommandWarehouseTransfer';
import { ERRORS } from '../constants';
import { Inject, Injectable } from '@nestjs/common';
import { UnitOfWork } from '../../Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { TenantModelProxy } from '../../System/models/TenantBaseModel';
import { WarehouseTransfer } from '../models/WarehouseTransfer';
import { ServiceError } from '../../Items/ServiceError';
import { events } from '@/common/events/events';
import { ModelObject } from 'objection';
@Injectable()
export class TransferredWarehouseTransfer {
constructor(
private readonly uow: UnitOfWork,
private readonly eventPublisher: EventEmitter2,
@Inject(WarehouseTransfer.name)
private readonly warehouseTransferModel: TenantModelProxy<
typeof WarehouseTransfer
>,
) {}
/**
* Validate the warehouse transfer not already transferred.
* @param {IWarehouseTransfer} warehouseTransfer
*/
private validateWarehouseTransferNotTransferred = (
warehouseTransfer: ModelObject<WarehouseTransfer>,
) => {
if (warehouseTransfer.transferDeliveredAt) {
throw new ServiceError(ERRORS.WAREHOUSE_TRANSFER_ALREADY_TRANSFERRED);
}
};
/**
* Validate the warehouse transfer should be initiated.
* @param {IWarehouseTransfer} warehouseTransfer
*/
private validateWarehouseTranbsferShouldInitiated = (
warehouseTransfer: ModelObject<WarehouseTransfer>,
) => {
if (!warehouseTransfer.transferInitiatedAt) {
throw new ServiceError(ERRORS.WAREHOUSE_TRANSFER_NOT_INITIATED);
}
};
/**
* Transferred warehouse transfer.
* @param {number} warehouseTransferId
* @returns {Promise<IWarehouseTransfer>}
*/
public transferredWarehouseTransfer = async (
warehouseTransferId: number,
): Promise<ModelObject<WarehouseTransfer>> => {
// Retrieves the old warehouse transfer transaction.
const oldWarehouseTransfer = await this.warehouseTransferModel()
.query()
.findById(warehouseTransferId)
.throwIfNotFound();
// Validate the warehouse transfer not already transferred.
this.validateWarehouseTransferNotTransferred(oldWarehouseTransfer);
// Validate the warehouse transfer should be initiated.
this.validateWarehouseTranbsferShouldInitiated(oldWarehouseTransfer);
// Edits warehouse transfer transaction under unit-of-work envirement.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onWarehouseTransferInitiate` event.
await this.eventPublisher.emitAsync(events.warehouseTransfer.onTransfer, {
oldWarehouseTransfer,
trx,
} as IWarehouseTransferTransferingPayload);
// Updates warehouse transfer graph on the storage.
const warehouseTransferUpdated = await this.warehouseTransferModel()
.query(trx)
.findById(warehouseTransferId)
.patch({
transferDeliveredAt: new Date(),
});
// Fetches the warehouse transfer with entries.
const warehouseTransfer = await this.warehouseTransferModel()
.query(trx)
.findById(warehouseTransferId)
.withGraphFetched('entries');
// Triggers `onWarehouseTransferEdit` event
await this.eventPublisher.emitAsync(
events.warehouseTransfer.onTransferred,
{
warehouseTransfer,
oldWarehouseTransfer,
trx,
} as IWarehouseTransferTransferredPayload,
);
return warehouseTransfer;
});
};
}

View File

@@ -0,0 +1,28 @@
import { AutoIncrementOrdersService } from '@/modules/AutoIncrementOrders/AutoIncrementOrders.service';
import { Injectable } from '@nestjs/common';
@Injectable()
export class WarehouseTransferAutoIncrement {
constructor(
private readonly autoIncrementOrdersService: AutoIncrementOrdersService,
) {}
/**
* Retrieve the next unique invoice number.
* @return {Promise<string>}
*/
public getNextTransferNumber(): Promise<string> {
return this.autoIncrementOrdersService.getNextTransactionNumber(
'warehouse_transfers',
);
}
/**
* Increment the invoice next number.
*/
public incrementNextTransferNumber() {
return this.autoIncrementOrdersService.incrementSettingsNextNumber(
'warehouse_transfers',
);
}
}

View File

@@ -0,0 +1,167 @@
import { Knex } from 'knex';
import { IWarehouseTransferEntry } from '@/modules/Warehouses/Warehouse.types';
import { Injectable } from '@nestjs/common';
import { InventoryTransactionsService } from '../../InventoryCost/commands/InventoryTransactions.service';
import { ModelObject } from 'objection';
import { WarehouseTransfer } from '../models/WarehouseTransfer';
import { InventoryTransaction } from '../../InventoryCost/models/InventoryTransaction';
import { WarehouseTransferEntry } from '../models/WarehouseTransferEntry';
@Injectable()
export class WarehouseTransferInventoryTransactions {
constructor(private readonly inventory: InventoryTransactionsService) {}
/**
* Writes all (initiate and transfer) inventory transactions.
* @param {ModelObject<WarehouseTransfer>} warehouseTransfer - Warehouse transfer.
* @param {Boolean} override - Override the inventory transactions.
* @param {Knex.Transaction} trx - Knex transcation.
* @returns {Promise<void>}
*/
public writeAllInventoryTransactions = async (
warehouseTransfer: ModelObject<WarehouseTransfer>,
override?: boolean,
trx?: Knex.Transaction,
): Promise<void> => {
const inventoryTransactions =
this.getWarehouseTransferInventoryTransactions(warehouseTransfer);
await this.inventory.recordInventoryTransactions(
inventoryTransactions,
override,
trx,
);
};
/**
* Writes initiate inventory transactions of warehouse transfer transaction.
* @param {ModelObject<WarehouseTransfer>} warehouseTransfer - Warehouse transfer.
* @param {boolean} override - Override the inventory transactions.
* @param {Knex.Transaction} trx - Knex transaction.
* @returns {Promise<void>}
*/
public writeInitiateInventoryTransactions = async (
warehouseTransfer: ModelObject<WarehouseTransfer>,
override?: boolean,
trx?: Knex.Transaction,
): Promise<void> => {
const inventoryTransactions =
this.getWarehouseFromTransferInventoryTransactions(warehouseTransfer);
await this.inventory.recordInventoryTransactions(
inventoryTransactions,
override,
trx,
);
};
/**
* Writes transferred inventory transaction of warehouse transfer transaction.
* @param {ModelObject<WarehouseTransfer>} warehouseTransfer - Warehouse transfer.
* @param {boolean} override - Override the inventory transactions.
* @param {Knex.Transaction} trx - Knex transaction.
* @returns {Promise<void>}
*/
public writeTransferredInventoryTransactions = async (
warehouseTransfer: ModelObject<WarehouseTransfer>,
override?: boolean,
trx?: Knex.Transaction,
): Promise<void> => {
const inventoryTransactions =
this.getWarehouseToTransferInventoryTransactions(warehouseTransfer);
await this.inventory.recordInventoryTransactions(
inventoryTransactions,
override,
trx,
);
};
/**
* Reverts warehouse transfer inventory transactions.
* @param {number} warehouseTransferId - Warehouse transfer id.
* @param {Knex.Transaction} trx - Knex transaction.
* @returns {Promise<void>}
*/
public revertInventoryTransactions = async (
warehouseTransferId: number,
trx?: Knex.Transaction,
): Promise<void> => {
await this.inventory.deleteInventoryTransactions(
warehouseTransferId,
'WarehouseTransfer',
trx,
);
};
/**
* Retrieves the inventory transactions of the given warehouse transfer.
* @param {IWarehouseTransfer} warehouseTransfer
* @returns {IInventoryTransaction[]}
*/
private getWarehouseFromTransferInventoryTransactions = (
warehouseTransfer: ModelObject<WarehouseTransfer>,
) => {
const commonEntry = {
date: warehouseTransfer.date,
transactionType: 'WarehouseTransfer',
transactionId: warehouseTransfer.id,
};
return warehouseTransfer.entries.map(
(entry: ModelObject<WarehouseTransferEntry>) => ({
...commonEntry,
entryId: entry.id,
itemId: entry.itemId,
quantity: entry.quantity,
rate: entry.cost,
direction: 'OUT',
warehouseId: warehouseTransfer.fromWarehouseId,
}),
);
};
/**
* Retrieves the inventory transactions of the given warehouse transfer.
* @param {ModelObject<WarehouseTransfer>} warehouseTransfer - Warehouse transfer.
* @returns {IInventoryTransaction[]}
*/
private getWarehouseToTransferInventoryTransactions = (
warehouseTransfer: ModelObject<WarehouseTransfer>,
) => {
const commonEntry = {
date: warehouseTransfer.date,
transactionType: 'WarehouseTransfer',
transactionId: warehouseTransfer.id,
};
return warehouseTransfer.entries.map(
(entry: ModelObject<WarehouseTransferEntry>) => ({
...commonEntry,
entryId: entry.id,
itemId: entry.itemId,
quantity: entry.quantity,
rate: entry.cost,
direction: 'IN',
warehouseId: warehouseTransfer.toWarehouseId,
}),
);
};
/**
* Retrieves the inventory transactions of the given warehouse transfer.
* @param {ModelObject<WarehouseTransfer>} warehouseTransfer - Warehouse transfer.
* @returns {IInventoryTransaction[]}
*/
private getWarehouseTransferInventoryTransactions = (
warehouseTransfer: ModelObject<WarehouseTransfer>,
) => {
// Retrieve the to inventory transactions of warehouse transfer.
const toTransactions =
this.getWarehouseToTransferInventoryTransactions(warehouseTransfer);
// Retrieve the from inventory transactions of warehouse transfer.
const fromTransactions =
this.getWarehouseFromTransferInventoryTransactions(warehouseTransfer);
return [...toTransactions, ...fromTransactions];
};
}

View File

@@ -0,0 +1,57 @@
export const ERRORS = {
WAREHOUSE_TRANSFER_NOT_FOUND: 'WAREHOUSE_TRANSFER_NOT_FOUND',
WAREHOUSES_TRANSFER_SHOULD_NOT_BE_SAME:
'WAREHOUSES_TRANSFER_SHOULD_NOT_BE_SAME',
FROM_WAREHOUSE_NOT_FOUND: 'FROM_WAREHOUSE_NOT_FOUND',
TO_WAREHOUSE_NOT_FOUND: 'TO_WAREHOUSE_NOT_FOUND',
WAREHOUSE_TRANSFER_ITEMS_SHOULD_BE_INVENTORY:
'WAREHOUSE_TRANSFER_ITEMS_SHOULD_BE_INVENTORY',
WAREHOUSE_TRANSFER_ALREADY_TRANSFERRED:
'WAREHOUSE_TRANSFER_ALREADY_TRANSFERRED',
WAREHOUSE_TRANSFER_ALREADY_INITIATED: 'WAREHOUSE_TRANSFER_ALREADY_INITIATED',
WAREHOUSE_TRANSFER_NOT_INITIATED: 'WAREHOUSE_TRANSFER_NOT_INITIATED',
};
// Warehouse transfers default views.
export const DEFAULT_VIEWS = [
{
name: 'warehouse_transfer.view.draft.name',
slug: 'draft',
rolesLogicExpression: '1',
roles: [
{ index: 1, fieldKey: 'status', comparator: 'equals', value: 'draft' },
],
columns: [],
},
{
name: 'warehouse_transfer.view.in_transit.name',
slug: 'in-transit',
rolesLogicExpression: '1',
roles: [
{
index: 1,
fieldKey: 'status',
comparator: 'equals',
value: 'in-transit',
},
],
columns: [],
},
{
name: 'warehouse_transfer.view.transferred.name',
slug: 'transferred',
rolesLogicExpression: '1',
roles: [
{
index: 1,
fieldKey: 'status',
comparator: 'equals',
value: 'tansferred',
},
],
columns: [],
},
];

View File

@@ -0,0 +1,109 @@
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
IsArray,
IsBoolean,
IsDate,
IsDecimal,
IsInt,
IsNotEmpty,
IsNumber,
IsOptional,
IsPositive,
IsString,
ValidateNested,
ArrayMinSize,
} from 'class-validator';
export class WarehouseTransferEntryDto {
@IsNotEmpty()
index: number;
@IsNotEmpty()
@IsInt()
itemId: number;
@IsOptional()
@IsString()
description?: string;
@IsNotEmpty()
@IsInt()
@IsPositive()
quantity: number;
@IsOptional()
@IsDecimal()
cost?: number;
}
export class CommandWarehouseTransferDto {
@IsNotEmpty()
@IsInt()
@ApiProperty({
description: 'The id of the warehouse to transfer from',
example: 1,
})
fromWarehouseId: number;
@IsNotEmpty()
@IsInt()
@ApiProperty({
description: 'The id of the warehouse to transfer to',
example: 2,
})
toWarehouseId: number;
@IsNotEmpty()
@IsDate()
@ApiProperty({
description: 'The date of the warehouse transfer',
example: '2021-01-01',
})
date: Date;
@IsOptional()
@IsString()
@ApiProperty({
description: 'The transaction number of the warehouse transfer',
example: '123456',
})
transactionNumber?: string;
@IsBoolean()
@IsOptional()
@ApiProperty({
description: 'Whether the warehouse transfer has been initiated',
example: false,
})
transferInitiated: boolean = false;
@IsBoolean()
@IsOptional()
@ApiProperty({
description: 'Whether the warehouse transfer has been delivered',
example: false,
})
transferDelivered: boolean = false;
@IsArray()
@ArrayMinSize(1)
@ValidateNested({ each: true })
@Type(() => WarehouseTransferEntryDto)
@ApiProperty({
description: 'The entries of the warehouse transfer',
example: [
{
index: 1,
itemId: 1,
description: 'This is a description',
quantity: 100,
cost: 100,
},
],
})
entries: WarehouseTransferEntryDto[];
}
export class CreateWarehouseTransferDto extends CommandWarehouseTransferDto {}
export class EditWarehouseTransferDto extends CommandWarehouseTransferDto {}

View File

@@ -0,0 +1,152 @@
import { Model, mixin } from 'objection';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model';
import { WarehouseTransferEntry } from './WarehouseTransferEntry';
export class WarehouseTransfer extends TenantBaseModel {
public date!: Date;
public transferInitiatedAt!: Date;
public transferDeliveredAt!: Date;
public fromWarehouseId!: number;
public toWarehouseId!: number;
public entries!: WarehouseTransferEntry[];
public fromWarehouse!: Warehouse;
public toWarehouse!: Warehouse;
/**
* Table name.
*/
static get tableName() {
return 'warehouses_transfers';
}
/**
* Timestamps columns.
*/
get timestamps() {
return ['created_at', 'updated_at'];
}
/**
* Virtual attributes.
*/
static get virtualAttributes() {
return ['isInitiated', 'isTransferred'];
}
/**
* Detarmines whether the warehouse transfer initiated.
* @retruns {boolean}
*/
get isInitiated() {
return !!this.transferInitiatedAt;
}
/**
* Detarmines whether the warehouse transfer transferred.
* @returns {boolean}
*/
get isTransferred() {
return !!this.transferDeliveredAt;
}
/**
* Model modifiers.
*/
static get modifiers() {
return {
filterByDraft(query) {
query.whereNull('transferInitiatedAt');
query.whereNull('transferDeliveredAt');
},
filterByInTransit(query) {
query.whereNotNull('transferInitiatedAt');
query.whereNull('transferDeliveredAt');
},
filterByTransferred(query) {
query.whereNotNull('transferInitiatedAt');
query.whereNotNull('transferDeliveredAt');
},
filterByStatus(query, status) {
switch (status) {
case 'draft':
default:
return query.modify('filterByDraft');
case 'in-transit':
return query.modify('filterByInTransit');
case 'transferred':
return query.modify('filterByTransferred');
}
},
};
}
/**
* Relationship mapping.
*/
static get relationMappings() {
const { WarehouseTransferEntry } = require('./WarehouseTransferEntry');
const { Warehouse } = require('../../Warehouses/models/Warehouse.model');
return {
/**
* View model may has many columns.
*/
entries: {
relation: Model.HasManyRelation,
modelClass: WarehouseTransferEntry,
join: {
from: 'warehouses_transfers.id',
to: 'warehouses_transfers_entries.warehouseTransferId',
},
},
/**
*
*/
fromWarehouse: {
relation: Model.BelongsToOneRelation,
modelClass: Warehouse,
join: {
from: 'warehouses_transfers.fromWarehouseId',
to: 'warehouses.id',
},
},
toWarehouse: {
relation: Model.BelongsToOneRelation,
modelClass: Warehouse,
join: {
from: 'warehouses_transfers.toWarehouseId',
to: 'warehouses.id',
},
},
};
}
/**
* Model settings.
*/
// static get meta() {
// return WarehouseTransferSettings;
// }
// /**
// * Retrieve the default custom views, roles and columns.
// */
// static get defaultViews() {
// return DEFAULT_VIEWS;
// }
/**
* Model search roles.
*/
static get searchRoles() {
return [
// { fieldKey: 'name', comparator: 'contains' },
// { condition: 'or', fieldKey: 'code', comparator: 'like' },
];
}
}

View File

@@ -0,0 +1,54 @@
import { Model } from 'objection';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { WarehouseTransfer } from './WarehouseTransfer';
import { Item } from '@/modules/Items/models/Item';
export class WarehouseTransferEntry extends TenantBaseModel {
public warehouseTransferId!: number;
public itemId!: number;
public quantity!: number;
public cost!: number;
public warehouseTransfer!: WarehouseTransfer;
public item!: Item;
/**
* Table name.
*/
static get tableName() {
return 'warehouses_transfers_entries';
}
/**
* Virtual attributes.
*/
static get virtualAttributes() {
return ['total'];
}
/**
* Invoice amount in local currency.
* @returns {number}
*/
get total() {
return this.cost * this.quantity;
}
/**
* Relationship mapping.
*/
static get relationMappings() {
const { Item } = require('../../Items/models/Item');
return {
item: {
relation: Model.BelongsToOneRelation,
modelClass: Item,
join: {
from: 'warehouses_transfers_entries.itemId',
to: 'items.id',
},
},
};
}
}

View File

@@ -0,0 +1,43 @@
import { WarehouseTransferTransformer } from './WarehouseTransferTransfomer';
import { Inject, Injectable } from '@nestjs/common';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { WarehouseTransfer } from '../models/WarehouseTransfer';
import { ModelObject } from 'objection';
@Injectable()
export class GetWarehouseTransfer {
constructor(
private readonly transformer: TransformerInjectable,
@Inject(WarehouseTransfer.name)
private readonly warehouseTransferModel: TenantModelProxy<
typeof WarehouseTransfer
>,
) {}
/**
* Retrieves the specific warehouse transfer transaction.
* @param {number} warehouseTransferId
* @param {IEditWarehouseTransferDTO} editWarehouseDTO
* @returns {Promise<IWarehouseTransfer>}
*/
public getWarehouseTransfer = async (
warehouseTransferId: number,
): Promise<ModelObject<WarehouseTransfer>> => {
// Retrieves the old warehouse transfer transaction.
const warehouseTransfer = await this.warehouseTransferModel()
.query()
.findById(warehouseTransferId)
.withGraphFetched('entries.item')
.withGraphFetched('fromWarehouse')
.withGraphFetched('toWarehouse')
.throwIfNotFound();
// Retrieves the transfromed warehouse transfers.
return this.transformer.transform(
warehouseTransfer,
new WarehouseTransferTransformer(),
);
};
}

View File

@@ -0,0 +1,70 @@
import * as R from 'ramda';
import { Inject, Injectable } from '@nestjs/common';
import { WarehouseTransferTransformer } from './WarehouseTransferTransfomer';
import { IGetWarehousesTransfersFilterDTO } from '../../Warehouses/Warehouse.types';
import { TransformerInjectable } from '../../Transformer/TransformerInjectable.service';
import { DynamicListService } from '../../DynamicListing/DynamicList.service';
import { TenantModelProxy } from '../../System/models/TenantBaseModel';
import { WarehouseTransfer } from '../models/WarehouseTransfer';
@Injectable()
export class GetWarehouseTransfers {
constructor(
private readonly dynamicListService: DynamicListService,
private readonly transformer: TransformerInjectable,
@Inject(WarehouseTransfer.name)
private readonly warehouseTransferModel: TenantModelProxy<
typeof WarehouseTransfer
>,
) {}
/**
* Parses the sale invoice list filter DTO.
* @param filterDTO
* @returns
*/
private parseListFilterDTO(filterDTO) {
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
}
/**
* Retrieves warehouse transfers paginated list.
* @param {number} tenantId
* @param {IGetWarehousesTransfersFilterDTO} filterDTO
* @returns {}
*/
public getWarehouseTransfers = async (
filterDTO: IGetWarehousesTransfersFilterDTO,
) => {
// Parses stringified filter roles.
const filter = this.parseListFilterDTO(filterDTO);
// Dynamic list service.
const dynamicFilter = await this.dynamicListService.dynamicList(
this.warehouseTransferModel(),
filter,
);
const { results, pagination } = await this.warehouseTransferModel()
.query()
.onBuild((query) => {
query.withGraphFetched('entries.item');
query.withGraphFetched('fromWarehouse');
query.withGraphFetched('toWarehouse');
dynamicFilter.buildQuery()(query);
})
.pagination(filter.page - 1, filter.pageSize);
// Retrieves the transformed warehouse transfers
const warehousesTransfers = await this.transformer.transform(
results,
new WarehouseTransferTransformer(),
);
return {
warehousesTransfers,
pagination,
filter,
};
};
}

View File

@@ -0,0 +1,38 @@
import { Transformer } from "../../Transformer/Transformer";
export class WarehouseTransferItemTransformer extends Transformer {
/**
* Include these attributes to sale invoice object.
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return ['formattedQuantity', 'formattedCost', 'formattedTotal'];
};
/**
* Formats the total.
* @param {IWarehouseTransferEntry} entry
* @returns {string}
*/
public formattedTotal = (entry) => {
return this.formatMoney(entry.total);
};
/**
* Formats the quantity.
* @param {IWarehouseTransferEntry} entry
* @returns {string}
*/
public formattedQuantity = (entry) => {
return this.formatNumber(entry.quantity);
};
/**
* Formats the cost.
* @param {IWarehouseTransferEntry} entry
* @returns {string}
*/
public formattedCost = (entry) => {
return this.formatMoney(entry.cost);
};
}

View File

@@ -0,0 +1,27 @@
import { WarehouseTransferItemTransformer } from './WarehouseTransferItemTransformer';
import { Transformer } from '@/modules/Transformer/Transformer';
export class WarehouseTransferTransformer extends Transformer {
/**
* Include these attributes to sale invoice object.
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return ['formattedDate', 'entries'];
};
/**
*
* @param transfer
* @returns
*/
protected formattedDate = (transfer) => {
return this.formatDate(transfer.date);
};
/**
*
*/
protected entries = (transfer) => {
return this.item(transfer.entries, new WarehouseTransferItemTransformer());
};
}

View File

@@ -0,0 +1,21 @@
import { Inject, Injectable } from '@nestjs/common';
import { events } from '@/common/events/events';
import { IWarehouseTransferCreated } from '../../Warehouses/Warehouse.types';
import { WarehouseTransferAutoIncrement } from '../commands/WarehouseTransferAutoIncrement';
import { OnEvent } from '@nestjs/event-emitter';
@Injectable()
export class WarehouseTransferAutoIncrementSubscriber {
constructor(
private readonly warehouseTransferAutoIncrement: WarehouseTransferAutoIncrement,
) {}
/**
* Writes inventory transactions once warehouse transfer created.
* @param {IInventoryTransactionsCreatedPayload} -
*/
@OnEvent(events.warehouseTransfer.onCreated)
async incrementTransferAutoIncrementOnCreated({}: IWarehouseTransferCreated) {
await this.warehouseTransferAutoIncrement.incrementNextTransferNumber();
}
}

View File

@@ -0,0 +1,123 @@
import {
IWarehouseTransferEditedPayload,
IWarehouseTransferDeletedPayload,
IWarehouseTransferCreated,
IWarehouseTransferInitiatedPayload,
IWarehouseTransferTransferredPayload,
} from '@/modules/Warehouses/Warehouse.types';
import { WarehouseTransferInventoryTransactions } from '../commands/WarehouseTransferWriteInventoryTransactions';
import { OnEvent } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { Injectable } from '@nestjs/common';
@Injectable()
export class WarehouseTransferInventoryTransactionsSubscriber {
constructor(
private readonly warehouseTransferInventoryTransactions: WarehouseTransferInventoryTransactions,
) {}
/**
* Writes inventory transactions once warehouse transfer created.
* @param {IInventoryTransactionsCreatedPayload} -
*/
@OnEvent(events.warehouseTransfer.onCreated)
async writeInventoryTransactionsOnWarehouseTransferCreated({
warehouseTransfer,
trx,
}: IWarehouseTransferCreated) {
// Can't continue if the warehouse transfer is not initiated yet.
if (!warehouseTransfer.isInitiated) return;
// Write all inventory transaction if warehouse transfer initiated and transferred.
if (warehouseTransfer.isInitiated && warehouseTransfer.isTransferred) {
await this.warehouseTransferInventoryTransactions.writeAllInventoryTransactions(
warehouseTransfer,
false,
trx,
);
// Write initiate inventory transaction if warehouse transfer initited and transferred yet.
} else if (warehouseTransfer.isInitiated) {
await this.warehouseTransferInventoryTransactions.writeInitiateInventoryTransactions(
warehouseTransfer,
false,
trx,
);
}
}
/**
* Rewrite inventory transactions once warehouse transfer edited.
* @param {IWarehouseTransferEditedPayload} -
*/
@OnEvent(events.warehouseTransfer.onEdited)
async rewriteInventoryTransactionsOnWarehouseTransferEdited({
warehouseTransfer,
trx,
}: IWarehouseTransferEditedPayload) {
// Can't continue if the warehouse transfer is not initiated yet.
if (!warehouseTransfer.isInitiated) return;
// Write all inventory transaction if warehouse transfer initiated and transferred.
if (warehouseTransfer.isInitiated && warehouseTransfer.isTransferred) {
await this.warehouseTransferInventoryTransactions.writeAllInventoryTransactions(
warehouseTransfer,
true,
trx,
);
// Write initiate inventory transaction if warehouse transfer initited and transferred yet.
} else if (warehouseTransfer.isInitiated) {
await this.warehouseTransferInventoryTransactions.writeInitiateInventoryTransactions(
warehouseTransfer,
true,
trx,
);
}
}
/**
* Reverts inventory transactions once warehouse transfer deleted.
* @parma {IWarehouseTransferDeletedPayload} -
*/
@OnEvent(events.warehouseTransfer.onDeleted)
async revertInventoryTransactionsOnWarehouseTransferDeleted({
oldWarehouseTransfer,
trx,
}: IWarehouseTransferDeletedPayload) {
await this.warehouseTransferInventoryTransactions.revertInventoryTransactions(
oldWarehouseTransfer.id,
trx,
);
}
/**
* Write inventory transactions of warehouse transfer once the transfer initiated.
* @param {IWarehouseTransferInitiatedPayload}
*/
@OnEvent(events.warehouseTransfer.onInitiated)
async writeInventoryTransactionsOnTransferInitiated({
trx,
warehouseTransfer,
}: IWarehouseTransferInitiatedPayload) {
await this.warehouseTransferInventoryTransactions.writeInitiateInventoryTransactions(
warehouseTransfer,
false,
trx,
);
}
/**
* Write inventory transactions of warehouse transfer once the transfer completed.
* @param {IWarehouseTransferTransferredPayload}
*/
@OnEvent(events.warehouseTransfer.onTransferred)
async writeInventoryTransactionsOnTransferred({
trx,
warehouseTransfer,
}: IWarehouseTransferTransferredPayload) {
await this.warehouseTransferInventoryTransactions.writeTransferredInventoryTransactions(
warehouseTransfer,
false,
trx,
);
}
}