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,30 @@
import { Service, Inject } from 'typedi';
import { IWarehouse } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class BillActivateWarehouses {
@Inject()
tenancy: HasTenancyService;
/**
* Updates all credit note transactions with the primary warehouse.
* @param {number} tenantId
* @param {number} primaryWarehouse
* @returns {Promise<void>}
*/
public updateBillsWithWarehouse = async (
tenantId: number,
primaryWarehouse: IWarehouse
): Promise<void> => {
const { Bill, ItemEntry } = this.tenancy.models(tenantId);
// Updates the sale estimates with primary warehouse.
await Bill.query().update({ warehouseId: primaryWarehouse.id });
// Update the sale estimates entries with primary warehouse.
await ItemEntry.query().where('referenceType', 'Bill').update({
warehouseId: primaryWarehouse.id,
});
};
}

View File

@@ -0,0 +1,30 @@
import { Service, Inject } from 'typedi';
import { IWarehouse } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class CreditNotesActivateWarehouses {
@Inject()
tenancy: HasTenancyService;
/**
* Updates all credit note transactions with the primary warehouse.
* @param {number} tenantId
* @param {number} primaryWarehouse
* @returns {Promise<void>}
*/
public updateCreditsWithWarehouse = async (
tenantId: number,
primaryWarehouse: IWarehouse
): Promise<void> => {
const { CreditNote, ItemEntry } = this.tenancy.models(tenantId);
// Updates the sale estimates with primary warehouse.
await CreditNote.query().update({ warehouseId: primaryWarehouse.id });
// Update the sale estimates entries with primary warehouse.
await ItemEntry.query().where('referenceType', 'CreditNote').update({
warehouseId: primaryWarehouse.id,
});
};
}

View File

@@ -0,0 +1,30 @@
import { Service, Inject } from 'typedi';
import { IWarehouse } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class EstimatesActivateWarehouses {
@Inject()
tenancy: HasTenancyService;
/**
* Updates all inventory transactions with the primary warehouse.
* @param {number} tenantId
* @param {number} primaryWarehouse
* @returns {Promise<void>}
*/
public updateEstimatesWithWarehouse = async (
tenantId: number,
primaryWarehouse: IWarehouse
): Promise<void> => {
const { SaleEstimate, ItemEntry } = this.tenancy.models(tenantId);
// Updates the sale estimates with primary warehouse.
await SaleEstimate.query().update({ warehouseId: primaryWarehouse.id });
// Update the sale estimates entries with primary warehouse.
await ItemEntry.query().where('referenceType', 'SaleEstimate').update({
warehouseId: primaryWarehouse.id,
});
};
}

View File

@@ -0,0 +1,31 @@
import { Service, Inject } from 'typedi';
import { IWarehouse } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class InventoryActivateWarehouses {
@Inject()
tenancy: HasTenancyService;
/**
* Updates all inventory transactions with the primary warehouse.
* @param {number} tenantId
* @param {number} primaryWarehouse
* @returns {Promise<void>}
*/
public updateInventoryTransactionsWithWarehouse = async (
tenantId: number,
primaryWarehouse: IWarehouse
): Promise<void> => {
const { InventoryTransaction, InventoryCostLotTracker } =
this.tenancy.models(tenantId);
// Updates the inventory transactions with primary warehouse.
await InventoryTransaction.query().update({
warehouseId: primaryWarehouse.id,
});
await InventoryCostLotTracker.query().update({
warehouseId: primaryWarehouse.id,
});
};
}

View File

@@ -0,0 +1,30 @@
import { Service, Inject } from 'typedi';
import { IWarehouse } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class InvoicesActivateWarehouses {
@Inject()
tenancy: HasTenancyService;
/**
* Updates all inventory transactions with the primary warehouse.
* @param {number} tenantId
* @param {number} primaryWarehouse
* @returns {Promise<void>}
*/
public updateInvoicesWithWarehouse = async (
tenantId: number,
primaryWarehouse: IWarehouse
): Promise<void> => {
const { SaleInvoice, ItemEntry } = this.tenancy.models(tenantId);
// Updates the sale invoices with primary warehouse.
await SaleInvoice.query().update({ warehouseId: primaryWarehouse.id });
// Update the sale invoices entries with primary warehouse.
await ItemEntry.query().where('referenceType', 'SaleInvoice').update({
warehouseId: primaryWarehouse.id,
});
};
}

View File

@@ -0,0 +1,30 @@
import { Service, Inject } from 'typedi';
import { IWarehouse } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class ReceiptActivateWarehouses {
@Inject()
tenancy: HasTenancyService;
/**
* Updates all sale receipts transactions with the primary warehouse.
* @param {number} tenantId
* @param {number} primaryWarehouse
* @returns {Promise<void>}
*/
public updateReceiptsWithWarehouse = async (
tenantId: number,
primaryWarehouse: IWarehouse
): Promise<void> => {
const { SaleReceipt, ItemEntry } = this.tenancy.models(tenantId);
// Updates the vendor credits transactions with primary warehouse.
await SaleReceipt.query().update({ warehouseId: primaryWarehouse.id });
// Update the sale invoices entries with primary warehouse.
await ItemEntry.query().where('referenceType', 'SaleReceipt').update({
warehouseId: primaryWarehouse.id,
});
};
}

View File

@@ -0,0 +1,30 @@
import { Service, Inject } from 'typedi';
import { IWarehouse } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class VendorCreditActivateWarehouses {
@Inject()
tenancy: HasTenancyService;
/**
* Updates all vendor credits transactions with the primary warehouse.
* @param {number} tenantId
* @param {number} primaryWarehouse
* @returns {Promise<void>}
*/
public updateCreditsWithWarehouse = async (
tenantId: number,
primaryWarehouse: IWarehouse
): Promise<void> => {
const { VendorCredit, ItemEntry } = this.tenancy.models(tenantId);
// Updates the vendor credits transactions with primary warehouse.
await VendorCredit.query().update({ warehouseId: primaryWarehouse.id });
// Update the sale invoices entries with primary warehouse.
await ItemEntry.query().where('referenceType', 'VendorCredit').update({
warehouseId: primaryWarehouse.id,
});
};
}

View File

@@ -0,0 +1,76 @@
import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import { ServiceError } from '@/exceptions';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import UnitOfWork from '@/services/UnitOfWork';
import { CreateInitialWarehouse } from './CreateInitialWarehouse';
import { WarehousesSettings } from './WarehousesSettings';
import events from '@/subscribers/events';
import { ERRORS } from './contants';
@Service()
export class ActivateWarehouses {
@Inject()
uow: UnitOfWork;
@Inject()
eventPublisher: EventPublisher;
@Inject()
createInitialWarehouse: CreateInitialWarehouse;
@Inject()
settings: WarehousesSettings;
/**
* Throws error if the multi-warehouses is already activated.
* @param {boolean} isActivated
*/
private throwIfWarehousesActivated = (isActivated: boolean) => {
if (isActivated) {
throw new ServiceError(ERRORS.MUTLI_WAREHOUSES_ALREADY_ACTIVATED);
}
};
/**
* Activates the multi-warehouses.
*
* - Creates a new warehouses and mark it as primary.
* - Seed warehouses items quantity.
* - Mutate inventory transactions with the primary warehouse.
* --------
* @param {number} tenantId
* @returns {Promise<void>}
*/
public activateWarehouses = (tenantId: number): Promise<void> => {
// Retrieve whether the multi-warehouses is active.
const isActivated = this.settings.isMultiWarehousesActive(tenantId);
// Throw error if the warehouses is already activated.
this.throwIfWarehousesActivated(isActivated);
// Activates multi-warehouses on unit-of-work envirement.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onWarehouseActivate` event.
await this.eventPublisher.emitAsync(events.warehouse.onActivate, {
tenantId,
trx,
});
// Creates a primary warehouse on the storage..
const primaryWarehouse =
await this.createInitialWarehouse.createInitialWarehouse(tenantId);
// Marks the multi-warehouses is activated.
this.settings.markMutliwarehoussAsActivated(tenantId);
// Triggers `onWarehouseActivated` event.
await this.eventPublisher.emitAsync(events.warehouse.onActivated, {
tenantId,
primaryWarehouse,
trx,
});
});
};
}

View File

@@ -0,0 +1,58 @@
import { Service, Inject } from 'typedi';
import events from '@/subscribers/events';
import { IWarehousesActivatedPayload } from '@/interfaces';
import { UpdateInventoryTransactionsWithWarehouse } from './UpdateInventoryTransactionsWithWarehouse';
import { CreateInitialWarehousesItemsQuantity } from './CreateInitialWarehousesitemsQuantity';
@Service()
export class ActivateWarehousesSubscriber {
@Inject()
private updateInventoryTransactionsWithWarehouse: UpdateInventoryTransactionsWithWarehouse;
@Inject()
private createInitialWarehousesItemsQuantity: CreateInitialWarehousesItemsQuantity;
/**
* Attaches events with handlers.
*/
attach(bus) {
bus.subscribe(
events.warehouse.onActivated,
this.updateInventoryTransactionsWithWarehouseOnActivating
);
bus.subscribe(
events.warehouse.onActivated,
this.createInitialWarehousesItemsQuantityOnActivating
);
return bus;
}
/**
* Updates inventory transactiont to primary warehouse once
* multi-warehouses activated.
* @param {IWarehousesActivatedPayload}
*/
private updateInventoryTransactionsWithWarehouseOnActivating = async ({
tenantId,
primaryWarehouse,
}: IWarehousesActivatedPayload) => {
await this.updateInventoryTransactionsWithWarehouse.run(
tenantId,
primaryWarehouse.id
);
};
/**
* Creates initial warehouses items quantity once the multi-warehouses activated.
* @param {IWarehousesActivatedPayload}
*/
private createInitialWarehousesItemsQuantityOnActivating = async ({
tenantId,
primaryWarehouse,
}: IWarehousesActivatedPayload) => {
await this.createInitialWarehousesItemsQuantity.run(
tenantId,
primaryWarehouse.id
);
};
}

View File

@@ -0,0 +1,26 @@
import { Inject, Service } from 'typedi';
import { ServiceError } from '@/exceptions';
import { ERRORS } from './contants';
import HasTenancyService from '@/services/Tenancy/TenancyService';
export class CRUDWarehouse {
@Inject()
tenancy: HasTenancyService;
getWarehouseOrThrowNotFound = async (tenantId: number, warehouseId: number) => {
const { Warehouse } = this.tenancy.models(tenantId);
const foundWarehouse = await Warehouse.query().findById(warehouseId);
if (!foundWarehouse) {
throw new ServiceError(ERRORS.WAREHOUSE_NOT_FOUND);
}
return foundWarehouse;
};
throwIfWarehouseNotFound = (warehouse) => {
if (!warehouse) {
throw new ServiceError(ERRORS.WAREHOUSE_NOT_FOUND);
}
}
}

View File

@@ -0,0 +1,26 @@
import { Service, Inject } from 'typedi';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { CreateWarehouse } from './CreateWarehouse';
@Service()
export class CreateInitialWarehouse {
@Inject()
private createWarehouse: CreateWarehouse;
@Inject()
private tenancy: HasTenancyService;
/**
* Creates a initial warehouse.
* @param {number} tenantId
*/
public createInitialWarehouse = async (tenantId: number) => {
const { __ } = this.tenancy.i18n(tenantId);
return this.createWarehouse.createWarehouse(tenantId, {
name: __('warehouses.primary_warehouse'),
code: '10001',
primary: true,
});
};
}

View File

@@ -0,0 +1,57 @@
import { Service, Inject } from 'typedi';
import { Knex } from 'knex';
import { IItem, IItemWarehouseQuantityChange } from '@/interfaces';
import { WarehousesItemsQuantitySync } from './Integrations/WarehousesItemsQuantitySync';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class CreateInitialWarehousesItemsQuantity {
@Inject()
private warehousesItemsQuantitySync: WarehousesItemsQuantitySync;
@Inject()
private tenancy: HasTenancyService;
/**
* Retrieves items warehouses quantity changes of the given inventory items.
* @param {IItem[]} items
* @param {IWarehouse} primaryWarehouse
* @returns {IItemWarehouseQuantityChange[]}
*/
private getWarehousesItemsChanges = (
items: IItem[],
primaryWarehouseId: number
): IItemWarehouseQuantityChange[] => {
return items
.filter((item: IItem) => item.quantityOnHand)
.map((item: IItem) => ({
itemId: item.id,
warehouseId: primaryWarehouseId,
amount: item.quantityOnHand,
}));
};
/**
* Creates initial warehouses items quantity.
* @param {number} tenantId
*/
public run = async (
tenantId: number,
primaryWarehouseId: number,
trx?: Knex.Transaction
): Promise<void> => {
const { Item } = this.tenancy.models(tenantId);
const items = await Item.query(trx).where('type', 'Inventory');
const warehousesChanges = this.getWarehousesItemsChanges(
items,
primaryWarehouseId
);
await this.warehousesItemsQuantitySync.mutateWarehousesItemsQuantity(
tenantId,
warehousesChanges,
trx
);
};
}

View File

@@ -0,0 +1,83 @@
import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import UnitOfWork from '@/services/UnitOfWork';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import {
ICreateWarehouseDTO,
IWarehouse,
IWarehouseCreatedPayload,
IWarehouseCreatePayload,
} from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import events from '@/subscribers/events';
import { WarehouseValidator } from './WarehouseValidator';
@Service()
export class CreateWarehouse {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private uow: UnitOfWork;
@Inject()
private eventPublisher: EventPublisher;
@Inject()
private validator: WarehouseValidator;
/**
* Authorize the warehouse before deleting.
* @param {number} tenantId -
* @param {ICreateWarehouseDTO} warehouseDTO -
*/
public authorize = async (
tenantId: number,
warehouseDTO: ICreateWarehouseDTO
) => {
if (warehouseDTO.code) {
await this.validator.validateWarehouseCodeUnique(
tenantId,
warehouseDTO.code
);
}
};
/**
* Creates a new warehouse on the system.
* @param {number} tenantId
* @param {ICreateWarehouseDTO} warehouseDTO
*/
public createWarehouse = async (
tenantId: number,
warehouseDTO: ICreateWarehouseDTO
): Promise<IWarehouse> => {
const { Warehouse } = this.tenancy.models(tenantId);
// Authorize warehouse before creating.
await this.authorize(tenantId, warehouseDTO);
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onWarehouseCreate` event.
await this.eventPublisher.emitAsync(events.warehouse.onEdit, {
tenantId,
warehouseDTO,
trx,
} as IWarehouseCreatePayload);
// Creates a new warehouse on the storage.
const warehouse = await Warehouse.query(trx).insertAndFetch({
...warehouseDTO,
});
// Triggers `onWarehouseCreated` event.
await this.eventPublisher.emitAsync(events.warehouse.onCreated, {
tenantId,
warehouseDTO,
warehouse,
trx,
} as IWarehouseCreatedPayload);
return warehouse;
});
};
}

View File

@@ -0,0 +1,25 @@
import { Service, Inject } from 'typedi';
import { Knex } from 'knex';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class DeleteItemWarehousesQuantity {
@Inject()
private tenancy: HasTenancyService;
/**
* Deletes the given item warehouses quantities.
* @param {number} tenantId
* @param {number} itemId
* @param {Knex.Transaction} trx -
*/
public deleteItemWarehousesQuantity = async (
tenantId: number,
itemId: number,
trx?: Knex.Transaction
): Promise<void> => {
const { ItemWarehouseQuantity } = this.tenancy.models(tenantId);
await ItemWarehouseQuantity.query(trx).where('itemId', itemId).delete();
};
}

View File

@@ -0,0 +1,86 @@
import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import UnitOfWork from '@/services/UnitOfWork';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import events from '@/subscribers/events';
import { IWarehouseDeletedPayload, IWarehouseDeletePayload } from '@/interfaces';
import { CRUDWarehouse } from './CRUDWarehouse';
import { WarehouseValidator } from './WarehouseValidator';
import { ERRORS } from './contants';
@Service()
export class DeleteWarehouse extends CRUDWarehouse {
@Inject()
tenancy: HasTenancyService;
@Inject()
uow: UnitOfWork;
@Inject()
eventPublisher: EventPublisher;
@Inject()
validator: WarehouseValidator;
/**
* Validates the given warehouse before deleting.
* @param {number} tenantId
* @param {number} warehouseId
* @returns {Promise<void>}
*/
public authorize = async (tenantId: number, warehouseId: number) => {
await this.validator.validateWarehouseNotOnlyWarehouse(
tenantId,
warehouseId
);
};
/**
* Deletes specific warehouse.
* @param {number} tenantId
* @param {number} warehouseId
* @returns {Promise<void>}
*/
public deleteWarehouse = async (
tenantId: number,
warehouseId: number
): Promise<void> => {
const { Warehouse } = this.tenancy.models(tenantId);
// Retrieves the old warehouse or throw not found service error.
const oldWarehouse = await Warehouse.query()
.findById(warehouseId)
.throwIfNotFound()
.queryAndThrowIfHasRelations({
type: ERRORS.WAREHOUSE_HAS_ASSOCIATED_TRANSACTIONS,
});
// Validates the given warehouse before deleting.
await this.authorize(tenantId, warehouseId);
// Creates a new warehouse under unit-of-work.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
const eventPayload = {
tenantId,
warehouseId,
oldWarehouse,
trx,
} as IWarehouseDeletePayload | IWarehouseDeletedPayload;
// Triggers `onWarehouseCreate`.
await this.eventPublisher.emitAsync(
events.warehouse.onDelete,
eventPayload
);
// Delets the given warehouse from the storage.
await Warehouse.query().findById(warehouseId).delete();
// Triggers `onWarehouseCreated`.
await this.eventPublisher.emitAsync(
events.warehouse.onDeleted,
eventPayload as IWarehouseDeletedPayload
);
});
};
}

View File

@@ -0,0 +1,82 @@
import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import UnitOfWork from '@/services/UnitOfWork';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import events from '@/subscribers/events';
import { IEditWarehouseDTO, IWarehouse } from '@/interfaces';
import { WarehouseValidator } from './WarehouseValidator';
@Service()
export class EditWarehouse {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private uow: UnitOfWork;
@Inject()
private eventPublisher: EventPublisher;
@Inject()
private validator: WarehouseValidator;
/**
* Authorize the warehouse before deleting.
* @param {number} tenantId -
* @param {ICreateWarehouseDTO} warehouseDTO -
*/
public authorize = async (
tenantId: number,
warehouseDTO: IEditWarehouseDTO,
warehouseId: number
) => {
if (warehouseDTO.code) {
await this.validator.validateWarehouseCodeUnique(
tenantId,
warehouseDTO.code,
warehouseId
);
}
};
/**
* Edits a new warehouse on the system.
* @param {number} tenantId
* @param {ICreateWarehouseDTO} warehouseDTO
* @returns {Promise<IWarehouse>}
*/
public editWarehouse = async (
tenantId: number,
warehouseId: number,
warehouseDTO: IEditWarehouseDTO
): Promise<IWarehouse> => {
const { Warehouse } = this.tenancy.models(tenantId);
// Authorize the warehouse DTO before editing.
await this.authorize(tenantId, warehouseDTO, warehouseId);
// Edits warehouse under unit-of-work.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onWarehouseEdit` event.
await this.eventPublisher.emitAsync(events.warehouse.onEdit, {
tenantId,
warehouseId,
warehouseDTO,
trx,
});
// Updates the given branch on the storage.
const warehouse = await Warehouse.query().patchAndFetchById(warehouseId, {
...warehouseDTO,
});
// Triggers `onWarehouseEdited` event.
await this.eventPublisher.emitAsync(events.warehouse.onEdited, {
tenantId,
warehouse,
warehouseDTO,
trx,
});
return warehouse;
});
};
}

View File

@@ -0,0 +1,39 @@
import {
BillsActivateWarehousesSubscriber,
CreditsActivateWarehousesSubscriber,
InvoicesActivateWarehousesSubscriber,
ReceiptsActivateWarehousesSubscriber,
EstimatesActivateWarehousesSubscriber,
InventoryActivateWarehousesSubscriber,
VendorCreditsActivateWarehousesSubscriber,
} from './Subscribers/Activate';
import {
BillWarehousesValidateSubscriber,
CreditNoteWarehousesValidateSubscriber,
SaleReceiptWarehousesValidateSubscriber,
SaleEstimateWarehousesValidateSubscriber,
SaleInvoicesWarehousesValidateSubscriber,
VendorCreditWarehousesValidateSubscriber,
InventoryAdjustmentWarehouseValidatorSubscriber,
} from './Subscribers/Validators';
import { DeleteItemWarehousesQuantitySubscriber } from './Subscribers/DeleteItemWarehousesQuantitySubscriber';
export default () => [
BillsActivateWarehousesSubscriber,
CreditsActivateWarehousesSubscriber,
InvoicesActivateWarehousesSubscriber,
ReceiptsActivateWarehousesSubscriber,
EstimatesActivateWarehousesSubscriber,
InventoryActivateWarehousesSubscriber,
VendorCreditsActivateWarehousesSubscriber,
BillWarehousesValidateSubscriber,
CreditNoteWarehousesValidateSubscriber,
SaleReceiptWarehousesValidateSubscriber,
SaleEstimateWarehousesValidateSubscriber,
SaleInvoicesWarehousesValidateSubscriber,
VendorCreditWarehousesValidateSubscriber,
InventoryAdjustmentWarehouseValidatorSubscriber,
DeleteItemWarehousesQuantitySubscriber,
];

View File

@@ -0,0 +1,24 @@
import { Inject, Service } from 'typedi';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { CRUDWarehouse } from './CRUDWarehouse';
@Service()
export class GetWarehouse extends CRUDWarehouse {
@Inject()
tenancy: HasTenancyService;
/**
* Retrieves warehouse details.
* @param {number} tenantId
* @returns
*/
public getWarehouse = async (tenantId: number, warehouseId: number) => {
const { Warehouse } = this.tenancy.models(tenantId);
const warehouse = await Warehouse.query().findById(warehouseId);
this.throwIfWarehouseNotFound(warehouse);
return warehouse;
};
}

View File

@@ -0,0 +1,21 @@
import { Inject, Service } from 'typedi';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class GetWarehouses {
@Inject()
tenancy: HasTenancyService;
/**
* Retrieves warehouses list.
* @param {number} tenantId
* @returns
*/
public getWarehouses = async (tenantId: number) => {
const { Warehouse } = this.tenancy.models(tenantId);
const warehouses = await Warehouse.query().orderBy('name', 'DESC');
return warehouses;
};
}

View File

@@ -0,0 +1,79 @@
import { Inject, Service } from 'typedi';
import { chain, difference } from 'lodash';
import { ServiceError } from '@/exceptions';
import { ERRORS } from './constants';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class ValidateWarehouseExistance {
@Inject()
tenancy: HasTenancyService;
/**
* Validate transaction warehouse id existance.
* @param transDTO
* @param entries
*/
public validateWarehouseIdExistance = (
transDTO: { warehouseId?: number },
entries: { warehouseId?: number }[] = []
) => {
const notAssignedWarehouseEntries = entries.filter((e) => !e.warehouseId);
if (notAssignedWarehouseEntries.length > 0 && !transDTO.warehouseId) {
throw new ServiceError(ERRORS.WAREHOUSE_ID_NOT_FOUND);
}
if (entries.length === 0 && !transDTO.warehouseId) {
throw new ServiceError(ERRORS.WAREHOUSE_ID_NOT_FOUND);
}
};
/**
* Validate warehouse existance.
* @param {number} tenantId
* @param {number} warehouseId
*/
public validateWarehouseExistance = (
tenantId: number,
warehouseId: number
) => {
const { Warehouse } = this.tenancy.models(tenantId);
const warehouse = Warehouse.query().findById(warehouseId);
if (!warehouse) {
throw new ServiceError(ERRORS.WAREHOUSE_ID_NOT_FOUND);
}
};
/**
*
* @param {number} tenantId
* @param {{ warehouseId?: number }[]} entries
*/
public validateItemEntriesWarehousesExistance = async (
tenantId: number,
entries: { warehouseId?: number }[]
) => {
const { Warehouse } = this.tenancy.models(tenantId);
const entriesWarehousesIds = chain(entries)
.filter((e) => !!e.warehouseId)
.map((e) => e.warehouseId)
.uniq()
.value();
const warehouses = await Warehouse.query().whereIn(
'id',
entriesWarehousesIds
);
const warehousesIds = warehouses.map((e) => e.id);
const notFoundWarehousesIds = difference(
entriesWarehousesIds,
warehousesIds
);
if (notFoundWarehousesIds.length > 0) {
throw new ServiceError(ERRORS.WAREHOUSE_ID_NOT_FOUND);
}
};
}

View File

@@ -0,0 +1,38 @@
import { Service, Inject } from 'typedi';
import { omit } from 'lodash';
import * as R from 'ramda';
import { WarehousesSettings } from '../WarehousesSettings';
@Service()
export class WarehouseTransactionDTOTransform {
@Inject()
private warehousesSettings: WarehousesSettings;
/**
* Excludes DTO warehouse id when mutli-warehouses feature is inactive.
* @param {number} tenantId
* @returns {Promise<Omit<T, 'warehouseId'> | T>}
*/
private excludeDTOWarehouseIdWhenInactive = <
T extends { warehouseId?: number }
>(
tenantId: number,
DTO: T
): Omit<T, 'warehouseId'> | T => {
const isActive = this.warehousesSettings.isMultiWarehousesActive(tenantId);
return !isActive ? omit(DTO, ['warehouseId']) : DTO;
};
/**
*
* @param {number} tenantId
* @param {T} DTO -
* @returns {Omit<T, 'warehouseId'> | T}
*/
public transformDTO =
<T extends { warehouseId?: number }>(tenantId: number) =>
(DTO: T): Omit<T, 'warehouseId'> | T => {
return this.excludeDTOWarehouseIdWhenInactive<T>(tenantId, DTO);
};
}

View File

@@ -0,0 +1,66 @@
import { Service, Inject } from 'typedi';
import { isEmpty } from 'lodash';
import { ValidateWarehouseExistance } from './ValidateWarehouseExistance';
import { WarehousesSettings } from '../WarehousesSettings';
interface IWarehouseTransactionDTO {
warehouseId?: number|null;
entries?: { warehouseId?: number|null }[];
}
@Service()
export class WarehousesDTOValidators {
@Inject()
private validateWarehouseExistanceService: ValidateWarehouseExistance;
@Inject()
private warehousesSettings: WarehousesSettings;
/**
* Validates the warehouse existance of sale invoice transaction.
* @param {number} tenantId
* @param {ISaleInvoiceCreateDTO | ISaleInvoiceEditDTO} saleInvoiceDTO
*/
public validateDTOWarehouseExistance = async (
tenantId: number,
DTO: IWarehouseTransactionDTO
) => {
// Validates the sale invoice warehouse id existance.
this.validateWarehouseExistanceService.validateWarehouseIdExistance(
DTO,
DTO.entries
);
// Validate the sale invoice warehouse existance on the storage.
if (DTO.warehouseId) {
this.validateWarehouseExistanceService.validateWarehouseExistance(
tenantId,
DTO.warehouseId
);
}
// Validate the sale invoice entries warehouses existance on the storage.
if (!isEmpty(DTO.entries)) {
await this.validateWarehouseExistanceService.validateItemEntriesWarehousesExistance(
tenantId,
DTO.entries
);
}
};
/**
* Validate the warehouse existance of
* @param {number} tenantId
* @param {IWarehouseTransactionDTO} saleInvoiceDTO
* @returns
*/
public validateDTOWarehouseWhenActive = async (
tenantId: number,
DTO: IWarehouseTransactionDTO
): Promise<void> => {
const isActive = this.warehousesSettings.isMultiWarehousesActive(tenantId);
// Can't continue if the multi-warehouses feature is inactive.
if (!isActive) return;
return this.validateDTOWarehouseExistance(tenantId, DTO);
};
}

View File

@@ -0,0 +1,117 @@
import {
IInventoryTransaction,
IItemWarehouseQuantityChange,
} from '@/interfaces';
import { set, get, chain, toPairs } from 'lodash';
export class WarehousesItemsQuantity {
balanceMap: { [warehouseId: number]: { [itemId: number]: number } } = {};
/**
*
* @param {number} warehouseId
* @param {number} itemId
* @returns {number}
*/
public get = (warehouseId: number, itemId: number): number => {
return get(this.balanceMap, `${warehouseId}.${itemId}`, 0);
};
/**
*
* @param {number} warehouseId
* @param {number} itemId
* @param {number} amount
* @returns {WarehousesItemsQuantity}
*/
public set = (warehouseId: number, itemId: number, amount: number) => {
if (!get(this.balanceMap, warehouseId)) {
set(this.balanceMap, warehouseId, {});
}
set(this.balanceMap, `${warehouseId}.${itemId}`, amount);
return this;
};
/**
*
* @param {number} warehouseId
* @param {number} itemId
* @param {number} amount
* @returns {WarehousesItemsQuantity}
*/
public increment = (warehouseId: number, itemId: number, amount: number) => {
const oldAmount = this.get(warehouseId, itemId);
return this.set(warehouseId, itemId, oldAmount + amount);
};
/**
*
* @param {number} warehouseId
* @param {number} itemId
* @param {number} amount
* @returns {WarehousesItemsQuantity}
*/
public decrement = (warehouseId: number, itemId: number, amount: number) => {
const oldAmount = this.get(warehouseId, itemId);
return this.set(warehouseId, itemId, oldAmount - amount);
};
/**
*
* @returns {WarehousesItemsQuantity}
*/
public reverse = () => {
const collection = this.toArray();
collection.forEach((change) => {
this.set(change.warehouseId, change.itemId, change.amount * -1);
});
return this;
};
/**
*
* @returns {IItemWarehouseQuantityChange[]}
*/
public toArray = (): IItemWarehouseQuantityChange[] => {
return chain(this.balanceMap)
.toPairs()
.map(([warehouseId, item]) => {
const pairs = toPairs(item);
return pairs.map(([itemId, amount]) => ({
itemId: parseInt(itemId),
warehouseId: parseInt(warehouseId),
amount,
}));
})
.flatten()
.value();
};
/**
*
* @param {IInventoryTransaction[]} inventoryTransactions
* @returns {WarehousesItemsQuantity}
*/
static fromInventoryTransaction = (
inventoryTransactions: IInventoryTransaction[]
): WarehousesItemsQuantity => {
const warehouseTransactions = inventoryTransactions.filter(
(transaction) => transaction.warehouseId
);
const warehouseItemsQuantity = new WarehousesItemsQuantity();
warehouseTransactions.forEach((transaction: IInventoryTransaction) => {
const change =
transaction.direction === 'IN'
? warehouseItemsQuantity.increment
: warehouseItemsQuantity.decrement;
change(transaction.warehouseId, transaction.itemId, transaction.quantity);
});
return warehouseItemsQuantity;
};
}

View File

@@ -0,0 +1,74 @@
import events from '@/subscribers/events';
import { Service, Inject } from 'typedi';
import { WarehousesItemsQuantitySync } from './WarehousesItemsQuantitySync';
import {
IInventoryTransactionsCreatedPayload,
IInventoryTransactionsDeletedPayload,
} from '@/interfaces';
import { WarehousesSettings } from '../WarehousesSettings';
@Service()
export class WarehousesItemsQuantitySyncSubscriber {
@Inject()
private warehousesItemsQuantitySync: WarehousesItemsQuantitySync;
@Inject()
private warehousesSettings: WarehousesSettings;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.inventory.onInventoryTransactionsCreated,
this.syncWarehousesItemsQuantityOnInventoryTransCreated
);
bus.subscribe(
events.inventory.onInventoryTransactionsDeleted,
this.syncWarehousesItemsQuantityOnInventoryTransDeleted
);
return bus;
}
/**
* Syncs warehouses items quantity once inventory transactions created.
* @param {IInventoryTransactionsCreatedPayload}
*/
private syncWarehousesItemsQuantityOnInventoryTransCreated = async ({
tenantId,
inventoryTransactions,
trx,
}: IInventoryTransactionsCreatedPayload) => {
const isActive = this.warehousesSettings.isMultiWarehousesActive(tenantId);
// Can't continue if the warehouses features is not active.
if (!isActive) return;
await this.warehousesItemsQuantitySync.mutateWarehousesItemsQuantityFromTransactions(
tenantId,
inventoryTransactions,
trx
);
};
/**
* Syncs warehouses items quantity once inventory transactions deleted.
* @param {IInventoryTransactionsDeletedPayload}
*/
private syncWarehousesItemsQuantityOnInventoryTransDeleted = async ({
tenantId,
oldInventoryTransactions,
trx,
}: IInventoryTransactionsDeletedPayload) => {
const isActive = this.warehousesSettings.isMultiWarehousesActive(tenantId);
// Can't continue if the warehouses feature is not active yet.
if (!isActive) return;
await this.warehousesItemsQuantitySync.reverseWarehousesItemsQuantityFromTransactions(
tenantId,
oldInventoryTransactions,
trx
);
};
}

View File

@@ -0,0 +1,131 @@
import { Knex } from 'knex';
import { Service, Inject } from 'typedi';
import { omit } from 'lodash';
import {
IInventoryTransaction,
IItemWarehouseQuantityChange,
} from '@/interfaces';
import { WarehousesItemsQuantity } from './WarehousesItemsQuantity';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class WarehousesItemsQuantitySync {
@Inject()
tenancy: HasTenancyService;
/**
* Retrieves the reversed warehouses items quantity changes.
* @param {IInventoryTransaction[]} inventoryTransactions
* @returns {IItemWarehouseQuantityChange[]}
*/
public getReverseWarehousesItemsQuantityChanges = (
inventoryTransactions: IInventoryTransaction[]
): IItemWarehouseQuantityChange[] => {
const warehouseItemsQuantity =
WarehousesItemsQuantity.fromInventoryTransaction(inventoryTransactions);
return warehouseItemsQuantity.reverse().toArray();
};
/**
* Retrieves the warehouses items changes from the given inventory tranasctions.
* @param {IInventoryTransaction[]} inventoryTransactions
* @returns {IItemWarehouseQuantityChange[]}
*/
public getWarehousesItemsQuantityChange = (
inventoryTransactions: IInventoryTransaction[]
): IItemWarehouseQuantityChange[] => {
const warehouseItemsQuantity =
WarehousesItemsQuantity.fromInventoryTransaction(inventoryTransactions);
return warehouseItemsQuantity.toArray();
};
/**
* Mutates warehouses items quantity on hand on the storage.
* @param {number} tenantId
* @param {IItemWarehouseQuantityChange[]} warehousesItemsQuantity
* @param {Knex.Transaction} trx
*/
public mutateWarehousesItemsQuantity = async (
tenantId: number,
warehousesItemsQuantity: IItemWarehouseQuantityChange[],
trx?: Knex.Transaction
): Promise<void> => {
const mutationsOpers = warehousesItemsQuantity.map(
(change: IItemWarehouseQuantityChange) =>
this.mutateWarehouseItemQuantity(tenantId, change, trx)
);
await Promise.all(mutationsOpers);
};
/**
* Mutates the warehouse item quantity.
* @param {number} tenantId
* @param {number} warehouseItemQuantity
* @param {Knex.Transaction} trx
*/
public mutateWarehouseItemQuantity = async (
tenantId: number,
warehouseItemQuantity: IItemWarehouseQuantityChange,
trx: Knex.Transaction
): Promise<void> => {
const { ItemWarehouseQuantity } = this.tenancy.models(tenantId);
const itemWarehouseQuantity = await ItemWarehouseQuantity.query(trx)
.where('itemId', warehouseItemQuantity.itemId)
.where('warehouseId', warehouseItemQuantity.warehouseId)
.first();
if (itemWarehouseQuantity) {
await ItemWarehouseQuantity.changeAmount(
{
itemId: warehouseItemQuantity.itemId,
warehouseId: warehouseItemQuantity.warehouseId,
},
'quantityOnHand',
warehouseItemQuantity.amount,
trx
);
} else {
await ItemWarehouseQuantity.query(trx).insert({
...omit(warehouseItemQuantity, ['amount']),
quantityOnHand: warehouseItemQuantity.amount,
});
}
};
/**
* Mutates warehouses items quantity from inventory transactions.
* @param {number} tenantId -
* @param {IInventoryTransaction[]} inventoryTransactions -
* @param {Knex.Transaction}
*/
public mutateWarehousesItemsQuantityFromTransactions = async (
tenantId: number,
inventoryTransactions: IInventoryTransaction[],
trx?: Knex.Transaction
) => {
const changes = this.getWarehousesItemsQuantityChange(
inventoryTransactions
);
await this.mutateWarehousesItemsQuantity(tenantId, changes, trx);
};
/**
* Reverses warehouses items quantity from inventory transactions.
* @param {number} tenantId
* @param {IInventoryTransaction[]} inventoryTransactions
* @param {Knex.Transaction} trx
*/
public reverseWarehousesItemsQuantityFromTransactions = async (
tenantId: number,
inventoryTransactions: IInventoryTransaction[],
trx?: Knex.Transaction
) => {
const changes = this.getReverseWarehousesItemsQuantityChanges(
inventoryTransactions
);
await this.mutateWarehousesItemsQuantity(tenantId, changes, trx);
};
}

View File

@@ -0,0 +1,4 @@
export const ERRORS = {
WAREHOUSE_ID_NOT_FOUND: 'WAREHOUSE_ID_NOT_FOUND',
ITEM_ENTRY_WAREHOUSE_ID_NOT_FOUND: 'ITEM_ENTRY_WAREHOUSE_ID_NOT_FOUND',
};

View File

@@ -0,0 +1,37 @@
import { Service, Inject } from 'typedi';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { GetItemWarehouseTransformer } from './GettItemWarehouseTransformer';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
@Service()
export class GetItemWarehouses {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private transformer: TransformerInjectable;
/**
* Retrieves the item warehouses.
* @param {number} tenantId
* @param {number} itemId
* @returns
*/
public getItemWarehouses = async (tenantId: number, itemId: number) => {
const { ItemWarehouseQuantity, Item } = this.tenancy.models(tenantId);
// Retrieves specific item or throw not found service error.
const item = await Item.query().findById(itemId).throwIfNotFound();
const itemWarehouses = await ItemWarehouseQuantity.query()
.where('itemId', itemId)
.withGraphFetched('warehouse');
// Retrieves the transformed items warehouses.
return this.transformer.transform(
tenantId,
itemWarehouses,
new GetItemWarehouseTransformer()
);
};
}

View File

@@ -0,0 +1,42 @@
import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
export class GetItemWarehouseTransformer extends Transformer {
/**
* Include these attributes to sale invoice object.
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return [
'warehouseId',
'warehouseName',
'warehouseCode',
'quantityOnHandFormatted',
];
};
public excludeAttributes = (): string[] => {
return ['warehouse'];
};
/**
* Formatted sell price.
* @param item
* @returns {string}
*/
public quantityOnHandFormatted(item): string {
return formatNumber(item.quantityOnHand, { money: false });
}
public warehouseCode(item): string {
return item.warehouse.code;
}
public warehouseName(item): string {
return item.warehouse.name;
}
public warehouseId(item): number {
return item.warehouse.id;
}
}

View File

@@ -0,0 +1,36 @@
import { Service, Inject } from 'typedi';
import { IWarehousesActivatedPayload } from '@/interfaces';
import events from '@/subscribers/events';
import { BillActivateWarehouses } from '../../Activate/BillWarehousesActivate';
@Service()
export class BillsActivateWarehousesSubscriber {
@Inject()
private billsActivateWarehouses: BillActivateWarehouses;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.warehouse.onActivated,
this.updateBillsWithWarehouseOnActivated
);
return bus;
}
/**
* Updates all inventory transactions with the primary warehouse once
* multi-warehouses feature is activated.
* @param {IWarehousesActivatedPayload}
*/
private updateBillsWithWarehouseOnActivated = async ({
tenantId,
primaryWarehouse,
}: IWarehousesActivatedPayload) => {
await this.billsActivateWarehouses.updateBillsWithWarehouse(
tenantId,
primaryWarehouse
);
};
}

View File

@@ -0,0 +1,36 @@
import { Service, Inject } from 'typedi';
import { IWarehousesActivatedPayload } from '@/interfaces';
import events from '@/subscribers/events';
import { CreditNotesActivateWarehouses } from '../../Activate/CreditNoteWarehousesActivate';
@Service()
export class CreditsActivateWarehousesSubscriber {
@Inject()
private creditsActivateWarehouses: CreditNotesActivateWarehouses;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.warehouse.onActivated,
this.updateInvoicesWithWarehouseOnActivated
);
return bus;
}
/**
* Updates all inventory transactions with the primary warehouse once
* multi-warehouses feature is activated.
* @param {IWarehousesActivatedPayload}
*/
private updateInvoicesWithWarehouseOnActivated = async ({
tenantId,
primaryWarehouse,
}: IWarehousesActivatedPayload) => {
await this.creditsActivateWarehouses.updateCreditsWithWarehouse(
tenantId,
primaryWarehouse
);
};
}

View File

@@ -0,0 +1,36 @@
import { Service, Inject } from 'typedi';
import { IWarehousesActivatedPayload } from '@/interfaces';
import events from '@/subscribers/events';
import { EstimatesActivateWarehouses } from '../../Activate/EstimateWarehousesActivate';
@Service()
export class EstimatesActivateWarehousesSubscriber {
@Inject()
private estimatesActivateWarehouses: EstimatesActivateWarehouses;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.warehouse.onActivated,
this.updateEstimatessWithWarehouseOnActivated
);
return bus;
}
/**
* Updates all inventory transactions with the primary warehouse once
* multi-warehouses feature is activated.
* @param {IWarehousesActivatedPayload}
*/
private updateEstimatessWithWarehouseOnActivated = async ({
tenantId,
primaryWarehouse,
}: IWarehousesActivatedPayload) => {
await this.estimatesActivateWarehouses.updateEstimatesWithWarehouse(
tenantId,
primaryWarehouse
);
};
}

View File

@@ -0,0 +1,36 @@
import { Service, Inject } from 'typedi';
import { IWarehousesActivatedPayload } from '@/interfaces';
import events from '@/subscribers/events';
import { InventoryActivateWarehouses } from '../../Activate/InventoryTransactionsWarehousesActivate';
@Service()
export class InventoryActivateWarehousesSubscriber {
@Inject()
private inventoryActivateWarehouses: InventoryActivateWarehouses;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.warehouse.onActivated,
this.updateInventoryTransactionsWithWarehouseOnActivated
);
return bus;
}
/**
* Updates all inventory transactions with the primary warehouse once
* multi-warehouses feature is activated.
* @param {IWarehousesActivatedPayload}
*/
private updateInventoryTransactionsWithWarehouseOnActivated = async ({
tenantId,
primaryWarehouse,
}: IWarehousesActivatedPayload) => {
await this.inventoryActivateWarehouses.updateInventoryTransactionsWithWarehouse(
tenantId,
primaryWarehouse
);
};
}

View File

@@ -0,0 +1,36 @@
import { Service, Inject } from 'typedi';
import { IWarehousesActivatedPayload } from '@/interfaces';
import events from '@/subscribers/events';
import { InvoicesActivateWarehouses } from '../../Activate/InvoiceWarehousesActivate';
@Service()
export class InvoicesActivateWarehousesSubscriber {
@Inject()
private invoicesActivateWarehouses: InvoicesActivateWarehouses;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.warehouse.onActivated,
this.updateInvoicesWithWarehouseOnActivated
);
return bus;
}
/**
* Updates all inventory transactions with the primary warehouse once
* multi-warehouses feature is activated.
* @param {IWarehousesActivatedPayload}
*/
private updateInvoicesWithWarehouseOnActivated = async ({
tenantId,
primaryWarehouse,
}: IWarehousesActivatedPayload) => {
await this.invoicesActivateWarehouses.updateInvoicesWithWarehouse(
tenantId,
primaryWarehouse
);
};
}

View File

@@ -0,0 +1,36 @@
import { Service, Inject } from 'typedi';
import { IWarehousesActivatedPayload } from '@/interfaces';
import events from '@/subscribers/events';
import { ReceiptActivateWarehouses } from '../../Activate/ReceiptWarehousesActivate';
@Service()
export class ReceiptsActivateWarehousesSubscriber {
@Inject()
private receiptsActivateWarehouses: ReceiptActivateWarehouses;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.warehouse.onActivated,
this.updateInventoryTransactionsWithWarehouseOnActivated
);
return bus;
}
/**
* Updates all receipts transactions with the primary warehouse once
* multi-warehouses feature is activated.
* @param {IWarehousesActivatedPayload}
*/
private updateInventoryTransactionsWithWarehouseOnActivated = async ({
tenantId,
primaryWarehouse,
}: IWarehousesActivatedPayload) => {
await this.receiptsActivateWarehouses.updateReceiptsWithWarehouse(
tenantId,
primaryWarehouse
);
};
}

View File

@@ -0,0 +1,36 @@
import { Service, Inject } from 'typedi';
import { IWarehousesActivatedPayload } from '@/interfaces';
import events from '@/subscribers/events';
import { VendorCreditActivateWarehouses } from '../../Activate/VendorCreditWarehousesActivate';
@Service()
export class VendorCreditsActivateWarehousesSubscriber {
@Inject()
private creditsActivateWarehouses: VendorCreditActivateWarehouses;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.warehouse.onActivated,
this.updateCreditsWithWarehouseOnActivated
);
return bus;
}
/**
* Updates all inventory transactions with the primary warehouse once
* multi-warehouses feature is activated.
* @param {IWarehousesActivatedPayload}
*/
private updateCreditsWithWarehouseOnActivated = async ({
tenantId,
primaryWarehouse,
}: IWarehousesActivatedPayload) => {
await this.creditsActivateWarehouses.updateCreditsWithWarehouse(
tenantId,
primaryWarehouse
);
};
}

View File

@@ -0,0 +1,8 @@
/* eslint-disable import/extensions */
export * from './BillWarehousesActivateSubscriber';
export * from './CreditNoteWarehousesActivateSubscriber';
export * from './EstimateWarehousesActivateSubscriber';
export * from './InventoryTransactionsWarehousesActivateSubscriber';
export * from './VendorCreditWarehousesActivateSubscriber';
export * from './ReceiptWarehousesActivateSubscriber';
export * from './InvoiceWarehousesActivateSubscriber';

View File

@@ -0,0 +1,36 @@
import { Inject, Service } from 'typedi';
import events from '@/subscribers/events';
import { DeleteItemWarehousesQuantity } from '../DeleteItemWarehousesQuantity';
import { IItemEventDeletingPayload } from '@/interfaces';
@Service()
export class DeleteItemWarehousesQuantitySubscriber {
@Inject()
private deleteItemWarehousesQuantity: DeleteItemWarehousesQuantity;
/**
* Attaches events.
*/
public attach(bus) {
bus.subscribe(
events.item.onDeleting,
this.deleteItemWarehouseQuantitiesOnItemDelete
);
}
/**
* Deletes the given item warehouses quantities once the item deleting.
* @param {IItemEventDeletingPayload} payload -
*/
private deleteItemWarehouseQuantitiesOnItemDelete = async ({
tenantId,
oldItem,
trx,
}: IItemEventDeletingPayload) => {
await this.deleteItemWarehousesQuantity.deleteItemWarehousesQuantity(
tenantId,
oldItem.id,
trx
);
};
}

View File

@@ -0,0 +1,35 @@
import { Inject, Service } from 'typedi';
import { IInventoryAdjustmentCreatingPayload } from '@/interfaces';
import events from '@/subscribers/events';
import { WarehousesDTOValidators } from '../../../Integrations/WarehousesDTOValidators';
@Service()
export class InventoryAdjustmentWarehouseValidatorSubscriber {
@Inject()
private warehouseDTOValidator: WarehousesDTOValidators;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.inventoryAdjustment.onQuickCreating,
this.validateAdjustmentWarehouseExistanceOnCreating
);
return bus;
}
/**
* Validate warehouse existance of sale invoice once creating.
* @param {IBillCreatingPayload}
*/
private validateAdjustmentWarehouseExistanceOnCreating = async ({
quickAdjustmentDTO,
tenantId,
}: IInventoryAdjustmentCreatingPayload) => {
await this.warehouseDTOValidator.validateDTOWarehouseWhenActive(
tenantId,
quickAdjustmentDTO
);
};
}

View File

@@ -0,0 +1,53 @@
import { Inject, Service } from 'typedi';
import { IBillCreatingPayload, IBillEditingPayload } from '@/interfaces';
import events from '@/subscribers/events';
import { WarehousesDTOValidators } from '../../../Integrations/WarehousesDTOValidators';
@Service()
export class BillWarehousesValidateSubscriber {
@Inject()
private warehouseDTOValidator: WarehousesDTOValidators;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.bill.onCreating,
this.validateBillWarehouseExistanceOnCreating
);
bus.subscribe(
events.bill.onEditing,
this.validateSaleEstimateWarehouseExistanceOnEditing
);
return bus;
}
/**
* Validate warehouse existance of sale invoice once creating.
* @param {IBillCreatingPayload}
*/
private validateBillWarehouseExistanceOnCreating = async ({
billDTO,
tenantId,
}: IBillCreatingPayload) => {
await this.warehouseDTOValidator.validateDTOWarehouseWhenActive(
tenantId,
billDTO
);
};
/**
* Validate warehouse existance of sale invoice once editing.
* @param {IBillEditingPayload}
*/
private validateSaleEstimateWarehouseExistanceOnEditing = async ({
tenantId,
billDTO,
}: IBillEditingPayload) => {
await this.warehouseDTOValidator.validateDTOWarehouseWhenActive(
tenantId,
billDTO
);
};
}

View File

@@ -0,0 +1,56 @@
import { Inject, Service } from 'typedi';
import {
IVendorCreditCreatingPayload,
IVendorCreditEditingPayload,
} from '@/interfaces';
import events from '@/subscribers/events';
import { WarehousesDTOValidators } from '../../../Integrations/WarehousesDTOValidators';
@Service()
export class VendorCreditWarehousesValidateSubscriber {
@Inject()
warehouseDTOValidator: WarehousesDTOValidators;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.vendorCredit.onCreating,
this.validateVendorCreditWarehouseExistanceOnCreating
);
bus.subscribe(
events.vendorCredit.onEditing,
this.validateSaleEstimateWarehouseExistanceOnEditing
);
return bus;
}
/**
* Validate warehouse existance of sale invoice once creating.
* @param {IVendorCreditCreatingPayload}
*/
private validateVendorCreditWarehouseExistanceOnCreating = async ({
vendorCreditCreateDTO,
tenantId,
}: IVendorCreditCreatingPayload) => {
await this.warehouseDTOValidator.validateDTOWarehouseWhenActive(
tenantId,
vendorCreditCreateDTO
);
};
/**
* Validate warehouse existance of sale invoice once editing.
* @param {IVendorCreditEditingPayload}
*/
private validateSaleEstimateWarehouseExistanceOnEditing = async ({
tenantId,
vendorCreditDTO,
}: IVendorCreditEditingPayload) => {
await this.warehouseDTOValidator.validateDTOWarehouseWhenActive(
tenantId,
vendorCreditDTO
);
};
}

View File

@@ -0,0 +1,56 @@
import { Inject, Service } from 'typedi';
import {
ICreditNoteCreatingPayload,
ICreditNoteEditingPayload,
} from '@/interfaces';
import events from '@/subscribers/events';
import { WarehousesDTOValidators } from '../../../Integrations/WarehousesDTOValidators';
@Service()
export class CreditNoteWarehousesValidateSubscriber {
@Inject()
warehouseDTOValidator: WarehousesDTOValidators;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.creditNote.onCreating,
this.validateCreditNoteWarehouseExistanceOnCreating
);
bus.subscribe(
events.creditNote.onEditing,
this.validateCreditNoteWarehouseExistanceOnEditing
);
return bus;
}
/**
* Validate warehouse existance of sale invoice once creating.
* @param {ICreditNoteCreatingPayload}
*/
private validateCreditNoteWarehouseExistanceOnCreating = async ({
creditNoteDTO,
tenantId,
}: ICreditNoteCreatingPayload) => {
await this.warehouseDTOValidator.validateDTOWarehouseWhenActive(
tenantId,
creditNoteDTO
);
};
/**
* Validate warehouse existance of sale invoice once editing.
* @param {ICreditNoteEditingPayload}
*/
private validateCreditNoteWarehouseExistanceOnEditing = async ({
tenantId,
creditNoteEditDTO,
}: ICreditNoteEditingPayload) => {
await this.warehouseDTOValidator.validateDTOWarehouseWhenActive(
tenantId,
creditNoteEditDTO
);
};
}

View File

@@ -0,0 +1,56 @@
import { Inject, Service } from 'typedi';
import {
ISaleEstimateCreatingPayload,
ISaleEstimateEditingPayload,
} from '@/interfaces';
import events from '@/subscribers/events';
import { WarehousesDTOValidators } from '../../../Integrations/WarehousesDTOValidators';
@Service()
export class SaleEstimateWarehousesValidateSubscriber {
@Inject()
warehouseDTOValidator: WarehousesDTOValidators;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.saleEstimate.onCreating,
this.validateSaleEstimateWarehouseExistanceOnCreating
);
bus.subscribe(
events.saleEstimate.onEditing,
this.validateSaleEstimateWarehouseExistanceOnEditing
);
return bus;
}
/**
* Validate warehouse existance of sale invoice once creating.
* @param {ISaleEstimateCreatingPayload}
*/
private validateSaleEstimateWarehouseExistanceOnCreating = async ({
estimateDTO,
tenantId,
}: ISaleEstimateCreatingPayload) => {
await this.warehouseDTOValidator.validateDTOWarehouseWhenActive(
tenantId,
estimateDTO
);
};
/**
* Validate warehouse existance of sale invoice once editing.
* @param {ISaleEstimateEditingPayload}
*/
private validateSaleEstimateWarehouseExistanceOnEditing = async ({
tenantId,
estimateDTO,
}: ISaleEstimateEditingPayload) => {
await this.warehouseDTOValidator.validateDTOWarehouseWhenActive(
tenantId,
estimateDTO
);
};
}

View File

@@ -0,0 +1,56 @@
import { Inject, Service } from 'typedi';
import {
ISaleInvoiceCreatingPaylaod,
ISaleInvoiceEditingPayload,
} from '@/interfaces';
import events from '@/subscribers/events';
import { WarehousesDTOValidators } from '../../../Integrations/WarehousesDTOValidators';
@Service()
export class SaleInvoicesWarehousesValidateSubscriber {
@Inject()
warehousesDTOValidator: WarehousesDTOValidators;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.saleInvoice.onCreating,
this.validateSaleInvoiceWarehouseExistanceOnCreating
);
bus.subscribe(
events.saleInvoice.onEditing,
this.validateSaleInvoiceWarehouseExistanceOnEditing
);
return bus;
}
/**
* Validate warehouse existance of sale invoice once creating.
* @param {ISaleInvoiceCreatingPaylaod}
*/
private validateSaleInvoiceWarehouseExistanceOnCreating = async ({
saleInvoiceDTO,
tenantId,
}: ISaleInvoiceCreatingPaylaod) => {
await this.warehousesDTOValidator.validateDTOWarehouseWhenActive(
tenantId,
saleInvoiceDTO
);
};
/**
* Validate warehouse existance of sale invoice once editing.
* @param {ISaleInvoiceEditingPayload}
*/
private validateSaleInvoiceWarehouseExistanceOnEditing = async ({
tenantId,
saleInvoiceDTO,
}: ISaleInvoiceEditingPayload) => {
await this.warehousesDTOValidator.validateDTOWarehouseWhenActive(
tenantId,
saleInvoiceDTO
);
};
}

View File

@@ -0,0 +1,56 @@
import { Inject, Service } from 'typedi';
import {
ISaleReceiptCreatingPayload,
ISaleReceiptEditingPayload,
} from '@/interfaces';
import events from '@/subscribers/events';
import { WarehousesDTOValidators } from '../../../Integrations/WarehousesDTOValidators';
@Service()
export class SaleReceiptWarehousesValidateSubscriber {
@Inject()
private warehousesDTOValidator: WarehousesDTOValidators;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.saleReceipt.onCreating,
this.validateSaleReceiptWarehouseExistanceOnCreating
);
bus.subscribe(
events.saleReceipt.onEditing,
this.validateSaleReceiptWarehouseExistanceOnEditing
);
return bus;
}
/**
* Validate warehouse existance of sale invoice once creating.
* @param {ISaleReceiptCreatingPayload}
*/
private validateSaleReceiptWarehouseExistanceOnCreating = async ({
saleReceiptDTO,
tenantId,
}: ISaleReceiptCreatingPayload) => {
await this.warehousesDTOValidator.validateDTOWarehouseWhenActive(
tenantId,
saleReceiptDTO
);
};
/**
* Validate warehouse existance of sale invoice once editing.
* @param {ISaleReceiptEditingPayload}
*/
private validateSaleReceiptWarehouseExistanceOnEditing = async ({
tenantId,
saleReceiptDTO,
}: ISaleReceiptEditingPayload) => {
await this.warehousesDTOValidator.validateDTOWarehouseWhenActive(
tenantId,
saleReceiptDTO
);
};
}

View File

@@ -0,0 +1,9 @@
export * from './Purchases/BillWarehousesSubscriber';
export * from './Purchases/VendorCreditWarehousesSubscriber';
export * from './Sales/SaleEstimateWarehousesSubscriber';
export * from './Sales/CreditNoteWarehousesSubscriber';
export * from './Sales/SaleInvoicesWarehousesSubscriber';
export * from './Sales/SaleReceiptWarehousesSubscriber';
export * from './InventoryAdjustment/InventoryAdjustmentWarehouseValidatorSubscriber';

View File

@@ -0,0 +1,21 @@
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { Service, Inject } from 'typedi';
@Service()
export class UpdateInventoryTransactionsWithWarehouse {
@Inject()
tenancy: HasTenancyService;
/**
* Updates all inventory transactions with primary warehouse.
* @param {number} tenantId -
* @param {number} warehouseId -
*/
public run = async (tenantId: number, primaryWarehouseId: number) => {
const { InventoryTransaction } = this.tenancy.models(tenantId);
await InventoryTransaction.query().update({
warehouseId: primaryWarehouseId,
});
};
}

View File

@@ -0,0 +1,64 @@
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 { CRUDWarehouse } from './CRUDWarehouse';
import {
IWarehouseMarkAsPrimaryPayload,
IWarehouseMarkedAsPrimaryPayload,
} from '@/interfaces';
@Service()
export class WarehouseMarkPrimary extends CRUDWarehouse {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private uow: UnitOfWork;
@Inject()
private eventPublisher: EventPublisher;
/**
* Marks the given warehouse as primary.
* @param {number} tenantId
* @param {number} warehouseId
* @returns {Promise<IWarehouse>}
*/
public markAsPrimary = async (tenantId: number, warehouseId: number) => {
const { Warehouse } = this.tenancy.models(tenantId);
const oldWarehouse = await this.getWarehouseOrThrowNotFound(
tenantId,
warehouseId
);
// Updates the branches under unit-of-work enivrement.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onWarehouseMarkPrimary` event.
await this.eventPublisher.emitAsync(events.warehouse.onMarkPrimary, {
tenantId,
oldWarehouse,
trx,
} as IWarehouseMarkAsPrimaryPayload);
// marks all warehouses as not primary.
await Warehouse.query(trx).update({ primary: false });
// Marks the particular branch as primary.
const markedWarehouse = await Warehouse.query(trx).patchAndFetchById(
warehouseId,
{ primary: true }
);
// Triggers `onWarehouseMarkedPrimary` event.
await this.eventPublisher.emitAsync(events.warehouse.onMarkedPrimary, {
tenantId,
oldWarehouse,
markedWarehouse,
trx,
} as IWarehouseMarkedAsPrimaryPayload);
return markedWarehouse;
});
};
}

View File

@@ -0,0 +1,57 @@
import { Inject, Service } from 'typedi';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { ServiceError, ServiceErrors } from '@/exceptions';
import { ERRORS } from './contants';
@Service()
export class WarehouseValidator {
@Inject()
tenancy: HasTenancyService;
/**
*
* @param {number} tenantId
* @param {number} warehouseId
*/
public validateWarehouseNotOnlyWarehouse = async (
tenantId: number,
warehouseId: number
) => {
const { Warehouse } = this.tenancy.models(tenantId);
const warehouses = await Warehouse.query().whereNot('id', warehouseId);
if (warehouses.length === 0) {
throw new ServiceError(ERRORS.COULD_NOT_DELETE_ONLY_WAERHOUSE);
}
};
/**
*
* @param tenantId
* @param code
* @param exceptWarehouseId
*/
public validateWarehouseCodeUnique = async (
tenantId: number,
code: string,
exceptWarehouseId?: number
) => {
const { Warehouse } = this.tenancy.models(tenantId);
const warehouse = await Warehouse.query()
.onBuild((query) => {
query.select(['id']);
query.where('code', code);
if (exceptWarehouseId) {
query.whereNot('id', exceptWarehouseId);
}
})
.first();
if (warehouse) {
throw new ServiceError(ERRORS.WAREHOUSE_CODE_NOT_UNIQUE);
}
};
}

View File

@@ -0,0 +1,137 @@
import { ICreateWarehouseDTO, IEditWarehouseDTO, IWarehouse } from '@/interfaces';
import { Inject, Service } from 'typedi';
import { ActivateWarehouses } from './ActivateWarehouses';
import { CreateWarehouse } from './CreateWarehouse';
import { DeleteWarehouse } from './DeleteWarehouse';
import { EditWarehouse } from './EditWarehouse';
import { GetWarehouse } from './GetWarehouse';
import { GetWarehouses } from './GetWarehouses';
import { GetItemWarehouses } from './Items/GetItemWarehouses';
import { WarehouseMarkPrimary } from './WarehouseMarkPrimary';
@Service()
export class WarehousesApplication {
@Inject()
private createWarehouseService: CreateWarehouse;
@Inject()
private editWarehouseService: EditWarehouse;
@Inject()
private deleteWarehouseService: DeleteWarehouse;
@Inject()
private getWarehouseService: GetWarehouse;
@Inject()
private getWarehousesService: GetWarehouses;
@Inject()
private activateWarehousesService: ActivateWarehouses;
@Inject()
private markWarehousePrimaryService: WarehouseMarkPrimary;
@Inject()
private getItemWarehousesService: GetItemWarehouses;
/**
* Creates a new warehouse.
* @param {number} tenantId
* @param {ICreateWarehouseDTO} createWarehouseDTO
* @returns
*/
public createWarehouse = (
tenantId: number,
createWarehouseDTO: ICreateWarehouseDTO
) => {
return this.createWarehouseService.createWarehouse(
tenantId,
createWarehouseDTO
);
};
/**
* Edits the given warehouse.
* @param {number} tenantId
* @param {number} warehouseId
* @param {IEditWarehouseDTO} editWarehouseDTO
* @returns {Promise<void>}
*/
public editWarehouse = (
tenantId: number,
warehouseId: number,
editWarehouseDTO: IEditWarehouseDTO
) => {
return this.editWarehouseService.editWarehouse(
tenantId,
warehouseId,
editWarehouseDTO
);
};
/**
* Deletes the given warehouse.
* @param {number} tenantId
* @param {number} warehouseId
*/
public deleteWarehouse = (tenantId: number, warehouseId: number) => {
return this.deleteWarehouseService.deleteWarehouse(tenantId, warehouseId);
};
/**
* Retrieves the specific warehouse.
* @param {number} tenantId
* @param {number} warehouseId
* @returns
*/
public getWarehouse = (tenantId: number, warehouseId: number) => {
return this.getWarehouseService.getWarehouse(tenantId, warehouseId);
};
/**
*
* @param {number} tenantId
* @returns
*/
public getWarehouses = (tenantId: number) => {
return this.getWarehousesService.getWarehouses(tenantId);
};
/**
* Activates the warehouses feature.
* @param {number} tenantId
* @returns {Promise<void>}
*/
public activateWarehouses = (tenantId: number) => {
return this.activateWarehousesService.activateWarehouses(tenantId);
};
/**
* Mark the given warehouse as primary.
* @param {number} tenantId -
* @returns {Promise<IWarehouse>}
*/
public markWarehousePrimary = (
tenantId: number,
warehouseId: number
): Promise<IWarehouse> => {
return this.markWarehousePrimaryService.markAsPrimary(
tenantId,
warehouseId
);
};
/**
* Retrieves the specific item warehouses quantity.
* @param {number} tenantId
* @param {number} itemId
* @returns
*/
public getItemWarehouses = (
tenantId: number,
itemId: number
): Promise<any> => {
return this.getItemWarehousesService.getItemWarehouses(tenantId, itemId);
};
}

View File

@@ -0,0 +1,36 @@
import { Service } from 'typedi';
@Service()
export class WarehousesService {
/**
*
* @param {number} tenantId
* @param {number} warehouseId
*/
getWarehouse = (tenantId: number, warehouseId: number) => {};
/**
*
* @param {number} tenantId
*/
getWarehouses = (tenantId: number) => {};
/**
*
* @param {number} tenantId
* @param {number} warehouseId
*/
deleteWarehouse = (tenantId: number, warehouseId: number) => {};
/**
*
* @param {number} tenantId
* @param {number} warehouseId
* @param {IEditWarehouseDTO} warehouseDTO
*/
editWarehouse = (
tenantId: number,
warehouseId: number,
warehouseDTO: IEditWarehouseDTO
) => {};
}

View File

@@ -0,0 +1,29 @@
import { Service, Inject } from 'typedi';
import { Features } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class WarehousesSettings {
@Inject()
tenancy: HasTenancyService;
/**
* Marks multi-warehouses as activated.
*/
public markMutliwarehoussAsActivated = (tenantId: number) => {
const settings = this.tenancy.settings(tenantId);
settings.set({ group: 'features', key: Features.WAREHOUSES, value: 1 });
};
/**
* Detarmines multi-warehouses is active.
* @param {number} tenantId
* @returns {boolean}
*/
public isMultiWarehousesActive = (tenantId: number) => {
const settings = this.tenancy.settings(tenantId);
return settings.get({ group: 'features', key: Features.WAREHOUSES });
};
}

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: [],
},
];

View File

@@ -0,0 +1,23 @@
import { Service, Inject } from 'typedi';
export class WarehousesTransfersService {
createWarehouseTranser = (
tenantId: number,
createWarehouseTransfer: ICreateWarehouseTransferDTO
) => {};
editWarehouseTranser = (
tenantId: number,
editWarehouseTransfer: IEditWarehouseTransferDTO
) => {};
deleteWarehouseTranser = (
tenantId: number,
warehouseTransferId: number
) => {};
getWarehouseTransfer = (tenantId: number, warehouseTransferId: number) => {};
getWarehouseTransfers = (tenantId: number) => {};
}

View File

@@ -0,0 +1,7 @@
export const ERRORS = {
WAREHOUSE_NOT_FOUND: 'WAREHOUSE_NOT_FOUND',
MUTLI_WAREHOUSES_ALREADY_ACTIVATED: 'MUTLI_WAREHOUSES_ALREADY_ACTIVATED',
COULD_NOT_DELETE_ONLY_WAERHOUSE: 'COULD_NOT_DELETE_ONLY_WAERHOUSE',
WAREHOUSE_CODE_NOT_UNIQUE: 'WAREHOUSE_CODE_NOT_UNIQUE',
WAREHOUSE_HAS_ASSOCIATED_TRANSACTIONS: 'WAREHOUSE_HAS_ASSOCIATED_TRANSACTIONS'
};