add server to monorepo.

This commit is contained in:
a.bouhuolia
2023-02-03 11:57:50 +02:00
parent 28e309981b
commit 80b97b5fdc
1303 changed files with 137049 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
import { Service, Inject } from 'typedi';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { ServiceError } from '@/exceptions';
import { ERRORS } from './constants';
@Service()
export class CRUDWarehouseTransfer {
@Inject()
tenancy: HasTenancyService;
throwIfTransferNotFound = (warehouseTransfer) => {
if (!warehouseTransfer) {
throw new ServiceError(ERRORS.WAREHOUSE_TRANSFER_NOT_FOUND);
}
}
public getWarehouseTransferOrThrowNotFound = async (
tenantId: number,
branchId: number
) => {
const { WarehouseTransfer } = this.tenancy.models(tenantId);
const foundTransfer = await WarehouseTransfer.query().findById(branchId);
if (!foundTransfer) {
throw new ServiceError(ERRORS.WAREHOUSE_TRANSFER_NOT_FOUND);
}
return foundTransfer;
};
}

View File

@@ -0,0 +1,79 @@
import { Inject } from 'typedi';
import {
ICreateWarehouseTransferDTO,
IEditWarehouseTransferDTO,
IItem,
} from '@/interfaces';
import { ServiceError } from '@/exceptions';
import { ERRORS } from './constants';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
import { CRUDWarehouseTransfer } from './CRUDWarehouseTransfer';
export class CommandWarehouseTransfer extends CRUDWarehouseTransfer {
@Inject()
tenancy: HasTenancyService;
@Inject()
itemsEntries: ItemsEntriesService;
/**
* Validate the from/to warehouses should not be the same.
* @param {ICreateWarehouseTransferDTO|IEditWarehouseTransferDTO} warehouseTransferDTO
*/
protected validateWarehouseFromToNotSame = (
warehouseTransferDTO:
| ICreateWarehouseTransferDTO
| IEditWarehouseTransferDTO
) => {
if (
warehouseTransferDTO.fromWarehouseId ===
warehouseTransferDTO.toWarehouseId
) {
throw new ServiceError(ERRORS.WAREHOUSES_TRANSFER_SHOULD_NOT_BE_SAME);
}
};
/**
* Validates entries items should be inventory.
* @param {IItem[]} items
* @returns {void}
*/
protected validateItemsShouldBeInventory = (items: IItem[]): void => {
const nonInventoryItems = items.filter((item) => item.type !== 'inventory');
if (nonInventoryItems.length > 0) {
throw new ServiceError(
ERRORS.WAREHOUSE_TRANSFER_ITEMS_SHOULD_BE_INVENTORY
);
}
};
protected getToWarehouseOrThrow = async (
tenantId: number,
fromWarehouseId: number
) => {
const { Warehouse } = this.tenancy.models(tenantId);
const warehouse = await Warehouse.query().findById(fromWarehouseId);
if (!warehouse) {
throw new ServiceError(ERRORS.TO_WAREHOUSE_NOT_FOUND);
}
return warehouse;
};
protected getFromWarehouseOrThrow = async (
tenantId: number,
fromWarehouseId: number
) => {
const { Warehouse } = this.tenancy.models(tenantId);
const warehouse = await Warehouse.query().findById(fromWarehouseId);
if (!warehouse) {
throw new ServiceError(ERRORS.FROM_WAREHOUSE_NOT_FOUND);
}
return warehouse;
};
}

View File

@@ -0,0 +1,204 @@
import { Knex } from 'knex';
import { omit, get, isNumber } from 'lodash';
import * as R from 'ramda';
import {
ICreateWarehouseTransferDTO,
IWarehouseTransfer,
IWarehouseTransferCreate,
IWarehouseTransferCreated,
IWarehouseTransferEntryDTO,
IInventoryItemCostMeta,
} from '@/interfaces';
import { Service, Inject } from 'typedi';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import UnitOfWork from '@/services/UnitOfWork';
import events from '@/subscribers/events';
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
import { CommandWarehouseTransfer } from './CommandWarehouseTransfer';
import { InventoryItemCostService } from '@/services/Inventory/InventoryCostsService';
import { WarehouseTransferAutoIncrement } from './WarehouseTransferAutoIncrement';
@Service()
export class CreateWarehouseTransfer extends CommandWarehouseTransfer {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private uow: UnitOfWork;
@Inject()
private eventPublisher: EventPublisher;
@Inject()
private itemsEntries: ItemsEntriesService;
@Inject()
private inventoryItemCost: InventoryItemCostService;
@Inject()
private autoIncrementOrders: WarehouseTransferAutoIncrement;
/**
* Transformes the givne new warehouse transfer DTO to model.
* @param {ICreateWarehouseTransferDTO} warehouseTransferDTO
* @returns {IWarehouseTransfer}
*/
private transformDTOToModel = async (
tenantId: number,
warehouseTransferDTO: ICreateWarehouseTransferDTO
): Promise<IWarehouseTransfer> => {
const entries = await this.transformEntries(
tenantId,
warehouseTransferDTO,
warehouseTransferDTO.entries
);
// Retrieves the auto-increment the warehouse transfer number.
const autoNextNumber =
this.autoIncrementOrders.getNextTransferNumber(tenantId);
// 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: IWarehouseTransferEntryDTO
): IWarehouseTransferEntryDTO => {
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 {number} tenantId
* @param {ICreateWarehouseTransferDTO} warehouseTransferDTO
* @param {IWarehouseTransferEntryDTO[]} entries
* @returns {Promise<IWarehouseTransferEntryDTO[]>}
*/
public transformEntries = async (
tenantId: number,
warehouseTransferDTO: ICreateWarehouseTransferDTO,
entries: IWarehouseTransferEntryDTO[]
): Promise<IWarehouseTransferEntryDTO[]> => {
const inventoryItemsIds = warehouseTransferDTO.entries.map((e) => e.itemId);
// Retrieves the inventory items valuation map.
const inventoryItemsCostMap =
await this.inventoryItemCost.getItemsInventoryValuation(
tenantId,
inventoryItemsIds,
warehouseTransferDTO.date
);
// Assoc average cost to the entry.
const assocAverageCost = this.transformEntryAssocAverageCost(
inventoryItemsCostMap
);
return R.map(assocAverageCost)(entries);
};
/**
* Authorize warehouse transfer before creating.
* @param {number} tenantId
* @param {ICreateWarehouseTransferDTO} warehouseTransferDTO
*/
public authorize = async (
tenantId: number,
warehouseTransferDTO: ICreateWarehouseTransferDTO
) => {
// Validate warehouse from and to should not be the same.
this.validateWarehouseFromToNotSame(warehouseTransferDTO);
// Retrieves the from warehouse or throw not found service error.
const fromWarehouse = await this.getFromWarehouseOrThrow(
tenantId,
warehouseTransferDTO.fromWarehouseId
);
// Retrieves the to warehouse or throw not found service error.
const toWarehouse = await this.getToWarehouseOrThrow(
tenantId,
warehouseTransferDTO.toWarehouseId
);
// Validates the not found entries items ids.
const items = await this.itemsEntries.validateItemsIdsExistance(
tenantId,
warehouseTransferDTO.entries
);
// Validate the items entries should be inventory type.
this.validateItemsShouldBeInventory(items);
};
/**
* Creates a new warehouse transfer transaction.
* @param {number} tenantId -
* @param {ICreateWarehouseTransferDTO} warehouseDTO -
* @returns {Promise<IWarehouseTransferCreate>}
*/
public createWarehouseTransfer = async (
tenantId: number,
warehouseTransferDTO: ICreateWarehouseTransferDTO
): Promise<IWarehouseTransfer> => {
const { WarehouseTransfer } = this.tenancy.models(tenantId);
// Authorize warehouse transfer before creating.
await this.authorize(tenantId, warehouseTransferDTO);
// Transformes the warehouse transfer DTO to model.
const warehouseTransferModel = await this.transformDTOToModel(
tenantId,
warehouseTransferDTO
);
// Create warehouse transfer under unit-of-work.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onWarehouseTransferCreate` event.
await this.eventPublisher.emitAsync(events.warehouseTransfer.onCreate, {
trx,
warehouseTransferDTO,
tenantId,
} as IWarehouseTransferCreate);
// Stores the warehouse transfer transaction graph to the storage.
const warehouseTransfer = await WarehouseTransfer.query(
trx
).upsertGraphAndFetch({
...warehouseTransferModel,
});
// Triggers `onWarehouseTransferCreated` event.
await this.eventPublisher.emitAsync(events.warehouseTransfer.onCreated, {
trx,
warehouseTransfer,
warehouseTransferDTO,
tenantId,
} as IWarehouseTransferCreated);
return warehouseTransfer;
});
};
}

View File

@@ -0,0 +1,67 @@
import { Service, Inject } from 'typedi';
import { Knex } from 'knex';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import UnitOfWork from '@/services/UnitOfWork';
import events from '@/subscribers/events';
import {
IWarehouseTransferDeletedPayload,
IWarehouseTransferDeletePayload,
} from '@/interfaces';
import { CRUDWarehouseTransfer } from './CRUDWarehouseTransfer';
@Service()
export class DeleteWarehouseTransfer extends CRUDWarehouseTransfer {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private uow: UnitOfWork;
@Inject()
private eventPublisher: EventPublisher;
/**
* Deletes warehouse transfer transaction.
* @param {number} tenantId
* @param {number} warehouseTransferId
* @returns {Promise<void>}
*/
public deleteWarehouseTransfer = async (
tenantId: number,
warehouseTransferId: number
): Promise<void> => {
const { WarehouseTransfer, WarehouseTransferEntry } =
this.tenancy.models(tenantId);
// Retrieve the old warehouse transfer or throw not found service error.
const oldWarehouseTransfer = await WarehouseTransfer.query()
.findById(warehouseTransferId)
.throwIfNotFound();
// Deletes the warehouse transfer under unit-of-work envirement.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onWarehouseTransferCreate` event.
await this.eventPublisher.emitAsync(events.warehouseTransfer.onDelete, {
tenantId,
oldWarehouseTransfer,
trx,
} as IWarehouseTransferDeletePayload);
// Delete warehouse transfer entries.
await WarehouseTransferEntry.query(trx)
.where('warehouseTransferId', warehouseTransferId)
.delete();
// Delete warehouse transfer.
await WarehouseTransfer.query(trx).findById(warehouseTransferId).delete();
// Triggers `onWarehouseTransferDeleted` event
await this.eventPublisher.emitAsync(events.warehouseTransfer.onDeleted, {
tenantId,
oldWarehouseTransfer,
trx,
} as IWarehouseTransferDeletedPayload);
});
};
}

View File

@@ -0,0 +1,95 @@
import { Service, Inject } from 'typedi';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import UnitOfWork from '@/services/UnitOfWork';
import { Knex } from 'knex';
import events from '@/subscribers/events';
import {
IEditWarehouseTransferDTO,
IWarehouseTransfer,
IWarehouseTransferEditPayload,
IWarehouseTransferEditedPayload,
} from '@/interfaces';
import { CommandWarehouseTransfer } from './CommandWarehouseTransfer';
@Service()
export class EditWarehouseTransfer extends CommandWarehouseTransfer {
@Inject()
tenancy: HasTenancyService;
@Inject()
uow: UnitOfWork;
@Inject()
eventPublisher: EventPublisher;
/**
* Edits warehouse transfer.
* @param {number} tenantId
* @param {number} warehouseTransferId
* @param {IEditWarehouseTransferDTO} editWarehouseDTO
* @returns {Promise<IWarehouseTransfer>}
*/
public editWarehouseTransfer = async (
tenantId: number,
warehouseTransferId: number,
editWarehouseDTO: IEditWarehouseTransferDTO
): Promise<IWarehouseTransfer> => {
const { WarehouseTransfer } = this.tenancy.models(tenantId);
// Retrieves the old warehouse transfer transaction.
const oldWarehouseTransfer = await WarehouseTransfer.query()
.findById(warehouseTransferId)
.throwIfNotFound();
// Validate warehouse from and to should not be the same.
this.validateWarehouseFromToNotSame(editWarehouseDTO);
// Retrieves the from warehouse or throw not found service error.
const fromWarehouse = await this.getFromWarehouseOrThrow(
tenantId,
editWarehouseDTO.fromWarehouseId
);
// Retrieves the to warehouse or throw not found service error.
const toWarehouse = await this.getToWarehouseOrThrow(
tenantId,
editWarehouseDTO.toWarehouseId
);
// Validates the not found entries items ids.
const items = await this.itemsEntries.validateItemsIdsExistance(
tenantId,
editWarehouseDTO.entries
);
// Validate the items entries should be inventory type.
this.validateItemsShouldBeInventory(items);
// Edits warehouse transfer transaction under unit-of-work envirement.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onWarehouseTransferEdit` event.
await this.eventPublisher.emitAsync(events.warehouseTransfer.onEdit, {
tenantId,
editWarehouseDTO,
oldWarehouseTransfer,
trx,
} as IWarehouseTransferEditPayload);
// Updates warehouse transfer graph on the storage.
const warehouseTransfer = await WarehouseTransfer.query(
trx
).upsertGraphAndFetch({
id: warehouseTransferId,
...editWarehouseDTO,
});
// Triggers `onWarehouseTransferEdit` event
await this.eventPublisher.emitAsync(events.warehouseTransfer.onEdited, {
tenantId,
editWarehouseDTO,
warehouseTransfer,
oldWarehouseTransfer,
trx,
} as IWarehouseTransferEditedPayload);
return warehouseTransfer;
});
};
}

View File

@@ -0,0 +1,45 @@
import { Service, Inject } from 'typedi';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { IWarehouseTransfer } from '@/interfaces';
import { CRUDWarehouseTransfer } from './CRUDWarehouseTransfer';
import { WarehouseTransferTransformer } from './WarehouseTransferTransfomer';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
@Service()
export class GetWarehouseTransfer extends CRUDWarehouseTransfer {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private transformer: TransformerInjectable;
/**
* Retrieves the specific warehouse transfer transaction.
* @param {number} tenantId
* @param {number} warehouseTransferId
* @param {IEditWarehouseTransferDTO} editWarehouseDTO
* @returns {Promise<IWarehouseTransfer>}
*/
public getWarehouseTransfer = async (
tenantId: number,
warehouseTransferId: number
): Promise<IWarehouseTransfer> => {
const { WarehouseTransfer } = this.tenancy.models(tenantId);
// Retrieves the old warehouse transfer transaction.
const warehouseTransfer = await WarehouseTransfer.query()
.findById(warehouseTransferId)
.withGraphFetched('entries.item')
.withGraphFetched('fromWarehouse')
.withGraphFetched('toWarehouse');
this.throwIfTransferNotFound(warehouseTransfer);
// Retrieves the transfromed warehouse transfers.
return this.transformer.transform(
tenantId,
warehouseTransfer,
new WarehouseTransferTransformer()
);
};
}

View File

@@ -0,0 +1,72 @@
import * as R from 'ramda';
import { Service, Inject } from 'typedi';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import { IGetWarehousesTransfersFilterDTO } from '@/interfaces';
import { WarehouseTransferTransformer } from './WarehouseTransferTransfomer';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
@Service()
export class GetWarehouseTransfers {
@Inject()
private dynamicListService: DynamicListingService;
@Inject()
private tenancy: HasTenancyService;
@Inject()
private transformer: TransformerInjectable;
/**
* 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 (
tenantId: number,
filterDTO: IGetWarehousesTransfersFilterDTO
) => {
const { WarehouseTransfer } = this.tenancy.models(tenantId);
// Parses stringified filter roles.
const filter = this.parseListFilterDTO(filterDTO);
// Dynamic list service.
const dynamicFilter = await this.dynamicListService.dynamicList(
tenantId,
WarehouseTransfer,
filter
);
const { results, pagination } = await WarehouseTransfer.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(
tenantId,
results,
new WarehouseTransferTransformer()
);
return {
warehousesTransfers,
pagination,
filter,
};
};
}

View File

@@ -0,0 +1,92 @@
import { Service, Inject } from 'typedi';
import { Knex } from 'knex';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import UnitOfWork from '@/services/UnitOfWork';
import events from '@/subscribers/events';
import {
IWarehouseTransfer,
IWarehouseTransferEditedPayload,
IWarehouseTransferInitiatePayload,
} from '@/interfaces';
import { CommandWarehouseTransfer } from './CommandWarehouseTransfer';
import { ServiceError } from '@/exceptions';
import { ERRORS } from './constants';
@Service()
export class InitiateWarehouseTransfer extends CommandWarehouseTransfer {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private uow: UnitOfWork;
@Inject()
private eventPublisher: EventPublisher;
/**
* Validate the given warehouse transfer not already initiated.
* @param {IWarehouseTransfer} warehouseTransfer
*/
private validateWarehouseTransferNotAlreadyInitiated = (
warehouseTransfer: IWarehouseTransfer
) => {
if (warehouseTransfer.transferInitiatedAt) {
throw new ServiceError(ERRORS.WAREHOUSE_TRANSFER_ALREADY_INITIATED);
}
};
/**
* Initiate warehouse transfer.
* @param {number} tenantId
* @param {number} warehouseTransferId
* @returns {Promise<IWarehouseTransfer>}
*/
public initiateWarehouseTransfer = async (
tenantId: number,
warehouseTransferId: number
): Promise<IWarehouseTransfer> => {
const { WarehouseTransfer } = this.tenancy.models(tenantId);
// Retrieves the old warehouse transfer transaction.
const oldWarehouseTransfer = await WarehouseTransfer.query()
.findById(warehouseTransferId)
.throwIfNotFound(warehouseTransferId);
// Validate the given warehouse transfer not already initiated.
this.validateWarehouseTransferNotAlreadyInitiated(oldWarehouseTransfer);
// Edits warehouse transfer transaction under unit-of-work envirement.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onWarehouseTransferInitiate` event.
await this.eventPublisher.emitAsync(events.warehouseTransfer.onInitiate, {
tenantId,
oldWarehouseTransfer,
trx,
} as IWarehouseTransferInitiatePayload);
// Updates warehouse transfer graph on the storage.
const warehouseTransferUpdated = await WarehouseTransfer.query(trx)
.findById(warehouseTransferId)
.patch({
transferInitiatedAt: new Date(),
});
// Fetches the warehouse transfer with entries.
const warehouseTransfer = await WarehouseTransfer.query(trx)
.findById(warehouseTransferId)
.withGraphFetched('entries');
// Triggers `onWarehouseTransferEdit` event
await this.eventPublisher.emitAsync(
events.warehouseTransfer.onInitiated,
{
tenantId,
warehouseTransfer,
oldWarehouseTransfer,
trx,
} as IWarehouseTransferEditedPayload
);
return warehouseTransfer;
});
};
}

View File

@@ -0,0 +1,107 @@
import { Service, Inject } from 'typedi';
import { Knex } from 'knex';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import UnitOfWork from '@/services/UnitOfWork';
import events from '@/subscribers/events';
import {
IWarehouseTransfer,
IWarehouseTransferTransferingPayload,
IWarehouseTransferTransferredPayload,
} from '@/interfaces';
import { CommandWarehouseTransfer } from './CommandWarehouseTransfer';
import { ERRORS } from './constants';
import { ServiceError } from '@/exceptions';
@Service()
export class TransferredWarehouseTransfer extends CommandWarehouseTransfer {
@Inject()
tenancy: HasTenancyService;
@Inject()
uow: UnitOfWork;
@Inject()
eventPublisher: EventPublisher;
/**
* Validate the warehouse transfer not already transferred.
* @param {IWarehouseTransfer} warehouseTransfer
*/
private validateWarehouseTransferNotTransferred = (
warehouseTransfer: IWarehouseTransfer
) => {
if (warehouseTransfer.transferDeliveredAt) {
throw new ServiceError(ERRORS.WAREHOUSE_TRANSFER_ALREAD_TRANSFERRED);
}
};
/**
* Validate the warehouse transfer should be initiated.
* @param {IWarehouseTransfer} warehouseTransfer
*/
private validateWarehouseTranbsferShouldInitiated = (
warehouseTransfer: IWarehouseTransfer
) => {
if (!warehouseTransfer.transferInitiatedAt) {
throw new ServiceError(ERRORS.WAREHOUSE_TRANSFER_NOT_INITIATED);
}
};
/**
* Transferred warehouse transfer.
* @param {number} tenantId
* @param {number} warehouseTransferId
* @returns {Promise<IWarehouseTransfer>}
*/
public transferredWarehouseTransfer = async (
tenantId: number,
warehouseTransferId: number
): Promise<IWarehouseTransfer> => {
const { WarehouseTransfer } = this.tenancy.models(tenantId);
// Retrieves the old warehouse transfer transaction.
const oldWarehouseTransfer = await WarehouseTransfer.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(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onWarehouseTransferInitiate` event.
await this.eventPublisher.emitAsync(events.warehouseTransfer.onTransfer, {
tenantId,
oldWarehouseTransfer,
trx,
} as IWarehouseTransferTransferingPayload);
// Updates warehouse transfer graph on the storage.
const warehouseTransferUpdated = await WarehouseTransfer.query(trx)
.findById(warehouseTransferId)
.patch({
transferDeliveredAt: new Date(),
});
// Fetches the warehouse transfer with entries.
const warehouseTransfer = await WarehouseTransfer.query(trx)
.findById(warehouseTransferId)
.withGraphFetched('entries');
// Triggers `onWarehouseTransferEdit` event
await this.eventPublisher.emitAsync(
events.warehouseTransfer.onTransferred,
{
tenantId,
warehouseTransfer,
oldWarehouseTransfer,
trx,
} as IWarehouseTransferTransferredPayload
);
return warehouseTransfer;
});
};
}

View File

@@ -0,0 +1,152 @@
import {
ICreateWarehouseTransferDTO,
IEditWarehouseTransferDTO,
IGetWarehousesTransfersFilterDTO,
IWarehouseTransfer,
} from '@/interfaces';
import { Service, Inject } from 'typedi';
import { CreateWarehouseTransfer } from './CreateWarehouseTransfer';
import { DeleteWarehouseTransfer } from './DeleteWarehouseTransfer';
import { EditWarehouseTransfer } from './EditWarehouseTransfer';
import { GetWarehouseTransfer } from './GetWarehouseTransfer';
import { GetWarehouseTransfers } from './GetWarehouseTransfers';
import { InitiateWarehouseTransfer } from './InitiateWarehouseTransfer';
import { TransferredWarehouseTransfer } from './TransferredWarehouseTransfer';
@Service()
export class WarehouseTransferApplication {
@Inject()
private createWarehouseTransferService: CreateWarehouseTransfer;
@Inject()
private editWarehouseTransferService: EditWarehouseTransfer;
@Inject()
private deleteWarehouseTransferService: DeleteWarehouseTransfer;
@Inject()
private getWarehouseTransferService: GetWarehouseTransfer;
@Inject()
private getWarehousesTransfersService: GetWarehouseTransfers;
@Inject()
private initiateWarehouseTransferService: InitiateWarehouseTransfer;
@Inject()
private transferredWarehouseTransferService: TransferredWarehouseTransfer;
/**
* Creates a warehouse transfer transaction.
* @param {number} tenantId
* @param {ICreateWarehouseTransferDTO} createWarehouseTransferDTO
* @returns {}
*/
public createWarehouseTransfer = (
tenantId: number,
createWarehouseTransferDTO: ICreateWarehouseTransferDTO
): Promise<IWarehouseTransfer> => {
return this.createWarehouseTransferService.createWarehouseTransfer(
tenantId,
createWarehouseTransferDTO
);
};
/**
* Edits warehouse transfer transaction.
* @param {number} tenantId -
* @param {number} warehouseTransferId - number
* @param {IEditWarehouseTransferDTO} editWarehouseTransferDTO
*/
public editWarehouseTransfer = (
tenantId: number,
warehouseTransferId: number,
editWarehouseTransferDTO: IEditWarehouseTransferDTO
): Promise<IWarehouseTransfer> => {
return this.editWarehouseTransferService.editWarehouseTransfer(
tenantId,
warehouseTransferId,
editWarehouseTransferDTO
);
};
/**
* Deletes warehouse transfer transaction.
* @param {number} tenantId
* @param {number} warehouseTransferId
* @returns {Promise<void>}
*/
public deleteWarehouseTransfer = (
tenantId: number,
warehouseTransferId: number
): Promise<void> => {
return this.deleteWarehouseTransferService.deleteWarehouseTransfer(
tenantId,
warehouseTransferId
);
};
/**
* Retrieves warehouse transfer transaction.
* @param {number} tenantId
* @param {number} warehouseTransferId
* @returns {Promise<IWarehouseTransfer>}
*/
public getWarehouseTransfer = (
tenantId: number,
warehouseTransferId: number
): Promise<IWarehouseTransfer> => {
return this.getWarehouseTransferService.getWarehouseTransfer(
tenantId,
warehouseTransferId
);
};
/**
* Retrieves warehouses trans
* @param {number} tenantId
* @param {IGetWarehousesTransfersFilterDTO} filterDTO
* @returns {Promise<IWarehouseTransfer>}
*/
public getWarehousesTransfers = (
tenantId: number,
filterDTO: IGetWarehousesTransfersFilterDTO
) => {
return this.getWarehousesTransfersService.getWarehouseTransfers(
tenantId,
filterDTO
);
};
/**
* Marks the warehouse transfer order as transfered.
* @param {number} tenantId
* @param {number} warehouseTransferId
* @returns {Promise<IWarehouseTransfer>}
*/
public transferredWarehouseTransfer = (
tenantId: number,
warehouseTransferId: number
): Promise<IWarehouseTransfer> => {
return this.transferredWarehouseTransferService.transferredWarehouseTransfer(
tenantId,
warehouseTransferId
);
};
/**
* Marks the warehouse transfer order as initiated.
* @param {number} tenantId
* @param {number} warehouseTransferId
* @returns {Promise<IWarehouseTransfer>}
*/
public initiateWarehouseTransfer = (
tenantId: number,
warehouseTransferId: number
): Promise<IWarehouseTransfer> => {
return this.initiateWarehouseTransferService.initiateWarehouseTransfer(
tenantId,
warehouseTransferId
);
};
}

View File

@@ -0,0 +1,31 @@
import { Inject, Service } from 'typedi';
import AutoIncrementOrdersService from '../../Sales/AutoIncrementOrdersService';
@Service()
export class WarehouseTransferAutoIncrement {
@Inject()
private autoIncrementOrdersService: AutoIncrementOrdersService;
/**
* Retrieve the next unique invoice number.
* @param {number} tenantId - Tenant id.
* @return {string}
*/
public getNextTransferNumber(tenantId: number): string {
return this.autoIncrementOrdersService.getNextTransactionNumber(
tenantId,
'warehouse_transfers'
);
}
/**
* Increment the invoice next number.
* @param {number} tenantId -
*/
public incrementNextTransferNumber(tenantId: number) {
return this.autoIncrementOrdersService.incrementSettingsNextNumber(
tenantId,
'warehouse_transfers'
);
}
}

View File

@@ -0,0 +1,33 @@
import { Service, Inject } from 'typedi';
import events from '@/subscribers/events';
import { IWarehouseTransferCreated } from '@/interfaces';
import { WarehouseTransferAutoIncrement } from './WarehouseTransferAutoIncrement';
@Service()
export class WarehouseTransferAutoIncrementSubscriber {
@Inject()
private warehouseTransferAutoIncrement: WarehouseTransferAutoIncrement;
/**
* Attaches events with handlers.
*/
public attach = (bus) => {
bus.subscribe(
events.warehouseTransfer.onCreated,
this.incrementTransferAutoIncrementOnCreated
);
return bus;
};
/**
* Writes inventory transactions once warehouse transfer created.
* @param {IInventoryTransactionsCreatedPayload} -
*/
private incrementTransferAutoIncrementOnCreated = async ({
tenantId,
}: IWarehouseTransferCreated) => {
await this.warehouseTransferAutoIncrement.incrementNextTransferNumber(
tenantId
);
};
}

View File

@@ -0,0 +1,155 @@
import { Service, Inject } from 'typedi';
import events from '@/subscribers/events';
import {
IWarehouseTransferEditedPayload,
IWarehouseTransferDeletedPayload,
IWarehouseTransferCreated,
IWarehouseTransferInitiatedPayload,
IWarehouseTransferTransferredPayload,
} from '@/interfaces';
import { WarehouseTransferInventoryTransactions } from './WriteInventoryTransactions';
@Service()
export class WarehouseTransferInventoryTransactionsSubscriber {
@Inject()
private warehouseTransferInventoryTransactions: WarehouseTransferInventoryTransactions;
/**
* Attaches events with handlers.
*/
public attach = (bus) => {
bus.subscribe(
events.warehouseTransfer.onCreated,
this.writeInventoryTransactionsOnWarehouseTransferCreated
);
bus.subscribe(
events.warehouseTransfer.onEdited,
this.rewriteInventoryTransactionsOnWarehouseTransferEdited
);
bus.subscribe(
events.warehouseTransfer.onDeleted,
this.revertInventoryTransactionsOnWarehouseTransferDeleted
);
bus.subscribe(
events.warehouseTransfer.onInitiated,
this.writeInventoryTransactionsOnTransferInitiated
);
bus.subscribe(
events.warehouseTransfer.onTransferred,
this.writeInventoryTransactionsOnTransferred
);
return bus;
};
/**
* Writes inventory transactions once warehouse transfer created.
* @param {IInventoryTransactionsCreatedPayload} -
*/
private writeInventoryTransactionsOnWarehouseTransferCreated = async ({
warehouseTransfer,
tenantId,
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(
tenantId,
warehouseTransfer,
false,
trx
);
// Write initiate inventory transaction if warehouse transfer initited and transferred yet.
} else if (warehouseTransfer.isInitiated) {
await this.warehouseTransferInventoryTransactions.writeInitiateInventoryTransactions(
tenantId,
warehouseTransfer,
false,
trx
);
}
};
/**
* Rewrite inventory transactions once warehouse transfer edited.
* @param {IWarehouseTransferEditedPayload} -
*/
private rewriteInventoryTransactionsOnWarehouseTransferEdited = async ({
tenantId,
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(
tenantId,
warehouseTransfer,
true,
trx
);
// Write initiate inventory transaction if warehouse transfer initited and transferred yet.
} else if (warehouseTransfer.isInitiated) {
await this.warehouseTransferInventoryTransactions.writeInitiateInventoryTransactions(
tenantId,
warehouseTransfer,
true,
trx
);
}
};
/**
* Reverts inventory transactions once warehouse transfer deleted.
* @parma {IWarehouseTransferDeletedPayload} -
*/
private revertInventoryTransactionsOnWarehouseTransferDeleted = async ({
tenantId,
oldWarehouseTransfer,
trx,
}: IWarehouseTransferDeletedPayload) => {
await this.warehouseTransferInventoryTransactions.revertInventoryTransactions(
tenantId,
oldWarehouseTransfer.id,
trx
);
};
/**
* Write inventory transactions of warehouse transfer once the transfer initiated.
* @param {IWarehouseTransferInitiatedPayload}
*/
private writeInventoryTransactionsOnTransferInitiated = async ({
trx,
warehouseTransfer,
tenantId,
}: IWarehouseTransferInitiatedPayload) => {
await this.warehouseTransferInventoryTransactions.writeInitiateInventoryTransactions(
tenantId,
warehouseTransfer,
false,
trx
);
};
/**
* Write inventory transactions of warehouse transfer once the transfer completed.
* @param {IWarehouseTransferTransferredPayload}
*/
private writeInventoryTransactionsOnTransferred = async ({
trx,
warehouseTransfer,
tenantId,
}: IWarehouseTransferTransferredPayload) => {
await this.warehouseTransferInventoryTransactions.writeTransferredInventoryTransactions(
tenantId,
warehouseTransfer,
false,
trx
);
};
}

View File

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

View File

@@ -0,0 +1,28 @@
import { Transformer } from '@/lib/Transformer/Transformer';
import { WarehouseTransferItemTransformer } from './WarehouseTransferItemTransformer';
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,176 @@
import { Knex } from 'knex';
import {
IWarehouseTransfer,
IInventoryTransaction,
IWarehouseTransferEntry,
} from '@/interfaces';
import { Inject, Service } from 'typedi';
import InventoryService from '@/services/Inventory/Inventory';
@Service()
export class WarehouseTransferInventoryTransactions {
@Inject()
private inventory: InventoryService;
/**
* Writes all (initiate and transfer) inventory transactions.
* @param {number} tenantId
* @param {IWarehouseTransfer} warehouseTransfer
* @param {Boolean} override
* @param {Knex.Transaction} trx - Knex transcation.
* @returns {Promise<void>}
*/
public writeAllInventoryTransactions = async (
tenantId: number,
warehouseTransfer: IWarehouseTransfer,
override?: boolean,
trx?: Knex.Transaction
): Promise<void> => {
const inventoryTransactions =
this.getWarehouseTransferInventoryTransactions(warehouseTransfer);
await this.inventory.recordInventoryTransactions(
tenantId,
inventoryTransactions,
override,
trx
);
};
/**
* Writes initiate inventory transactions of warehouse transfer transaction.
* @param {number} tenantId
* @param {IWarehouseTransfer} warehouseTransfer
* @param {boolean} override
* @param {Knex.Transaction} trx - Knex transaction.
* @returns {Promise<void>}
*/
public writeInitiateInventoryTransactions = async (
tenantId: number,
warehouseTransfer: IWarehouseTransfer,
override?: boolean,
trx?: Knex.Transaction
): Promise<void> => {
const inventoryTransactions =
this.getWarehouseFromTransferInventoryTransactions(warehouseTransfer);
await this.inventory.recordInventoryTransactions(
tenantId,
inventoryTransactions,
override,
trx
);
};
/**
* Writes transferred inventory transaction of warehouse transfer transaction.
* @param {number} tenantId
* @param {IWarehouseTransfer} warehouseTransfer
* @param {boolean} override
* @param {Knex.Transaction} trx - Knex transaction.
* @returns {Promise<void>}
*/
public writeTransferredInventoryTransactions = async (
tenantId: number,
warehouseTransfer: IWarehouseTransfer,
override?: boolean,
trx?: Knex.Transaction
): Promise<void> => {
const inventoryTransactions =
this.getWarehouseToTransferInventoryTransactions(warehouseTransfer);
await this.inventory.recordInventoryTransactions(
tenantId,
inventoryTransactions,
override,
trx
);
};
/**
* Reverts warehouse transfer inventory transactions.
* @param {number} tenatnId
* @param {number} warehouseTransferId
* @param {Knex.Transaction} trx
* @returns {Promise<void>}
*/
public revertInventoryTransactions = async (
tenantId: number,
warehouseTransferId: number,
trx?: Knex.Transaction
): Promise<void> => {
await this.inventory.deleteInventoryTransactions(
tenantId,
warehouseTransferId,
'WarehouseTransfer',
trx
);
};
/**
* Retrieves the inventory transactions of the given warehouse transfer.
* @param {IWarehouseTransfer} warehouseTransfer
* @returns {IInventoryTransaction[]}
*/
private getWarehouseFromTransferInventoryTransactions = (
warehouseTransfer: IWarehouseTransfer
): IInventoryTransaction[] => {
const commonEntry = {
date: warehouseTransfer.date,
transactionType: 'WarehouseTransfer',
transactionId: warehouseTransfer.id,
};
return warehouseTransfer.entries.map((entry: IWarehouseTransferEntry) => ({
...commonEntry,
entryId: entry.id,
itemId: entry.itemId,
quantity: entry.quantity,
rate: entry.cost,
direction: 'OUT',
warehouseId: warehouseTransfer.fromWarehouseId,
}));
};
/**
*
* @param {IWarehouseTransfer} warehouseTransfer
* @returns {IInventoryTransaction[]}
*/
private getWarehouseToTransferInventoryTransactions = (
warehouseTransfer: IWarehouseTransfer
): IInventoryTransaction[] => {
const commonEntry = {
date: warehouseTransfer.date,
transactionType: 'WarehouseTransfer',
transactionId: warehouseTransfer.id,
};
return warehouseTransfer.entries.map((entry: IWarehouseTransferEntry) => ({
...commonEntry,
entryId: entry.id,
itemId: entry.itemId,
quantity: entry.quantity,
rate: entry.cost,
direction: 'IN',
warehouseId: warehouseTransfer.toWarehouseId,
}));
};
/**
*
* @param {IWarehouseTransfer} warehouseTransfer
* @returns {IInventoryTransaction[]}
*/
private getWarehouseTransferInventoryTransactions = (
warehouseTransfer: IWarehouseTransfer
): IInventoryTransaction[] => {
// 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_ALREAD_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: [],
},
];