feat(nestjs): migrate to NestJS

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

View File

@@ -0,0 +1,27 @@
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { AccountTransaction } from '../Accounts/models/AccountTransaction.model';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
@Injectable()
export class InventoryTransactionsWarehouses {
constructor(
@Inject(AccountTransaction.name)
private readonly accountTransactionModel: TenantModelProxy<
typeof AccountTransaction
>,
) {}
/**
* Updates all accounts transctions with the priamry branch.
* @param {number} primaryBranchId - The primary branch id.
*/
public updateTransactionsWithWarehouse = async (
primaryBranchId: number,
trx?: Knex.Transaction,
) => {
await this.accountTransactionModel().query(trx).update({
branchId: primaryBranchId,
});
};
}

View File

@@ -0,0 +1,28 @@
import { Injectable } from '@nestjs/common';
import { InventoryTransactionsWarehouses } from './AccountsTransactionsWarehouses';
import { OnEvent } from '@nestjs/event-emitter';
import { IBranchesActivatedPayload } from '../Branches/Branches.types';
import { events } from '@/common/events/events';
@Injectable()
export class AccountsTransactionsWarehousesSubscribe {
constructor(
private readonly accountsTransactionsWarehouses: InventoryTransactionsWarehouses,
) {}
/**
* Updates all GL transactions to primary branch once
* the multi-branches activated.
* @param {IBranchesActivatedPayload}
*/
@OnEvent(events.branch.onActivated)
async updateGLTransactionsToPrimaryBranchOnActivated({
primaryBranch,
trx,
}: IBranchesActivatedPayload) {
await this.accountsTransactionsWarehouses.updateTransactionsWithWarehouse(
primaryBranch.id,
trx,
);
}
}

View File

@@ -0,0 +1,34 @@
import { Inject, Injectable } from '@nestjs/common';
import { Warehouse } from '../models/Warehouse.model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { Bill } from '@/modules/Bills/models/Bill';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
@Injectable()
export class BillActivateWarehouses {
constructor(
@Inject(Bill.name)
private readonly billModel: TenantModelProxy<typeof Bill>,
@Inject(ItemEntry.name)
private readonly itemEntryModel: TenantModelProxy<typeof ItemEntry>,
) {}
/**
* Updates all credit note transactions with the primary warehouse.
* @param {Warehouse} primaryWarehouse
* @returns {Promise<void>}
*/
public updateBillsWithWarehouse = async (
primaryWarehouse: Warehouse,
): Promise<void> => {
// Updates the sale estimates with primary warehouse.
await this.billModel().query().update({
warehouseId: primaryWarehouse.id,
});
// Update the sale estimates entries with primary warehouse.
await this.itemEntryModel().query().where('referenceType', 'Bill').update({
warehouseId: primaryWarehouse.id,
});
};
}

View File

@@ -0,0 +1,37 @@
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { CreditNote } from '@/modules/CreditNotes/models/CreditNote';
import { Inject, Injectable } from '@nestjs/common';
import { Warehouse } from '../models/Warehouse.model';
@Injectable()
export class CreditNotesActivateWarehouses {
constructor(
@Inject(CreditNote.name)
private readonly creditNoteModel: TenantModelProxy<typeof CreditNote>,
@Inject(ItemEntry.name)
private readonly itemEntryModel: TenantModelProxy<typeof ItemEntry>,
) {}
/**
* Updates all credit note transactions with the primary warehouse.
* @param {Warehouse} primaryWarehouse
* @returns {Promise<void>}
*/
public updateCreditsWithWarehouse = async (
primaryWarehouse: Warehouse
): Promise<void> => {
// Updates the sale estimates with primary warehouse.
await this.creditNoteModel().query().update({
warehouseId: primaryWarehouse.id,
});
// Update the sale estimates entries with primary warehouse.
await this.itemEntryModel()
.query()
.where('referenceType', 'CreditNote')
.update({
warehouseId: primaryWarehouse.id,
});
};
}

View File

@@ -0,0 +1,35 @@
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { SaleEstimate } from '@/modules/SaleEstimates/models/SaleEstimate';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { Injectable } from '@nestjs/common';
import { Warehouse } from '../models/Warehouse.model';
@Injectable()
export class EstimatesActivateWarehouses {
constructor(
private readonly saleEstimateModel: TenantModelProxy<typeof SaleEstimate>,
private readonly itemEntryModel: TenantModelProxy<typeof ItemEntry>,
) {}
/**
* Updates all inventory transactions with the primary warehouse.
* @param {Warehouse} primaryWarehouse
* @returns {Promise<void>}
*/
public updateEstimatesWithWarehouse = async (
primaryWarehouse: Warehouse,
): Promise<void> => {
// Updates the sale estimates with primary warehouse.
await this.saleEstimateModel()
.query()
.update({ warehouseId: primaryWarehouse.id });
// Update the sale estimates entries with primary warehouse.
await this.itemEntryModel()
.query()
.where('referenceType', 'SaleEstimate')
.update({
warehouseId: primaryWarehouse.id,
});
};
}

View File

@@ -0,0 +1,31 @@
import { InventoryTransaction } from "@/modules/InventoryCost/models/InventoryTransaction";
import { TenantModelProxy } from "@/modules/System/models/TenantBaseModel";
import { Injectable } from "@nestjs/common";
import { Warehouse } from "../models/Warehouse.model";
import { InventoryCostLotTracker } from "@/modules/InventoryCost/models/InventoryCostLotTracker";
@Injectable()
export class InventoryActivateWarehouses {
constructor(
private readonly inventoryTransactionModel: TenantModelProxy<typeof InventoryTransaction>,
private readonly inventoryCostLotTrackerModel: TenantModelProxy<typeof InventoryCostLotTracker>,
) {}
/**
* Updates all inventory transactions with the primary warehouse.
* @param {number} tenantId
* @param {number} primaryWarehouse
* @returns {Promise<void>}
*/
public updateInventoryTransactionsWithWarehouse = async (
primaryWarehouse: Warehouse
): Promise<void> => {
// Updates the inventory transactions with primary warehouse.
await this.inventoryTransactionModel().query().update({
warehouseId: primaryWarehouse.id,
});
await this.inventoryCostLotTrackerModel().query().update({
warehouseId: primaryWarehouse.id,
});
};
}

View File

@@ -0,0 +1,38 @@
import { Inject, Injectable } from '@nestjs/common';
import { Warehouse } from '../models/Warehouse.model';
import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class InvoicesActivateWarehouses {
constructor(
@Inject(SaleInvoice.name)
private readonly saleInvoiceModel: TenantModelProxy<typeof SaleInvoice>,
@Inject(ItemEntry.name)
private readonly itemEntryModel: TenantModelProxy<typeof ItemEntry>,
) {}
/**
* Updates all inventory transactions with the primary warehouse.
* @param {Warehouse} primaryWarehouse
* @returns {Promise<void>}
*/
updateInvoicesWithWarehouse = async (
primaryWarehouse: Warehouse,
): Promise<void> => {
// Updates the sale invoices with primary warehouse.
await this.saleInvoiceModel().query().update({
warehouseId: primaryWarehouse.id,
});
// Update the sale invoices entries with primary warehouse.
await this.itemEntryModel()
.query()
.where('referenceType', 'SaleInvoice')
.update({
warehouseId: primaryWarehouse.id,
});
};
}

View File

@@ -0,0 +1,37 @@
import { Inject, Injectable } from '@nestjs/common';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { Warehouse } from '../models/Warehouse.model';
import { SaleReceipt } from '@/modules/SaleReceipts/models/SaleReceipt';
@Injectable()
export class ReceiptActivateWarehouses {
constructor(
@Inject(SaleReceipt.name)
private readonly saleReceiptModel: TenantModelProxy<typeof SaleReceipt>,
@Inject(ItemEntry.name)
private readonly itemEntryModel: TenantModelProxy<typeof ItemEntry>,
) {}
/**
* Updates all sale receipts transactions with the primary warehouse.
* @param {Warehouse} primaryWarehouse
* @returns {Promise<void>}
*/
public updateReceiptsWithWarehouse = async (
primaryWarehouse: Warehouse,
): Promise<void> => {
// Updates the vendor credits transactions with primary warehouse.
await this.saleReceiptModel().query().update({
warehouseId: primaryWarehouse.id,
});
// Update the sale invoices entries with primary warehouse.
await this.itemEntryModel()
.query()
.where('referenceType', 'SaleReceipt')
.update({
warehouseId: primaryWarehouse.id,
});
};
}

View File

@@ -0,0 +1,38 @@
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { VendorCredit } from '@/modules/VendorCredit/models/VendorCredit';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { Warehouse } from '../models/Warehouse.model';
import { Inject, Injectable } from '@nestjs/common';
@Injectable()
export class VendorCreditActivateWarehouses {
constructor(
@Inject(VendorCredit.name)
private readonly vendorCreditModel: TenantModelProxy<typeof VendorCredit>,
@Inject(ItemEntry.name)
private readonly itemEntryModel: TenantModelProxy<typeof ItemEntry>,
) {}
/**
* Updates all vendor credits transactions with the primary warehouse.
* @param {Warehouse} primaryWarehouse
* @returns {Promise<void>}
*/
public updateCreditsWithWarehouse = async (
primaryWarehouse: Warehouse,
): Promise<void> => {
// Updates the vendor credits transactions with primary warehouse.
await this.vendorCreditModel().query().update({
warehouseId: primaryWarehouse.id,
});
// Update the sale invoices entries with primary warehouse.
await this.itemEntryModel()
.query()
.where('referenceType', 'VendorCredit')
.update({
warehouseId: primaryWarehouse.id,
});
};
}

View File

@@ -0,0 +1,39 @@
import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { UpdateInventoryTransactionsWithWarehouse } from './UpdateInventoryTransactionsWithWarehouse';
import { CreateInitialWarehousesItemsQuantity } from './CreateInitialWarehousesitemsQuantity';
import { IWarehousesActivatedPayload } from './Warehouse.types';
@Injectable()
export class ActivateWarehousesSubscriber {
constructor(
private readonly updateInventoryTransactionsWithWarehouse: UpdateInventoryTransactionsWithWarehouse,
private readonly createInitialWarehousesItemsQuantity: CreateInitialWarehousesItemsQuantity,
) {}
/**
* Updates inventory transactiont to primary warehouse once
* multi-warehouses activated.
* @param {IWarehousesActivatedPayload}
*/
@OnEvent(events.warehouse.onActivated)
async updateInventoryTransactionsWithWarehouseOnActivating({
primaryWarehouse,
}: IWarehousesActivatedPayload) {
await this.updateInventoryTransactionsWithWarehouse.run(
primaryWarehouse.id,
);
}
/**
* Creates initial warehouses items quantity once the multi-warehouses activated.
* @param {IWarehousesActivatedPayload}
*/
@OnEvent(events.warehouse.onActivated)
async createInitialWarehousesItemsQuantityOnActivating({
primaryWarehouse,
}: IWarehousesActivatedPayload) {
await this.createInitialWarehousesItemsQuantity.run(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,55 @@
import { Knex } from 'knex';
import { WarehousesItemsQuantitySync } from './Integrations/WarehousesItemsQuantitySync';
import { Inject, Injectable } from '@nestjs/common';
import { IItemWarehouseQuantityChange } from './Warehouse.types';
import { Item } from '../Items/models/Item';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
@Injectable()
export class CreateInitialWarehousesItemsQuantity {
constructor(
private readonly warehousesItemsQuantitySync: WarehousesItemsQuantitySync,
@Inject(Item.name)
private readonly itemModel: TenantModelProxy<typeof Item>,
) {}
/**
* Retrieves items warehouses quantity changes of the given inventory items.
* @param {IItem[]} items
* @param {IWarehouse} primaryWarehouse
* @returns {IItemWarehouseQuantityChange[]}
*/
private getWarehousesItemsChanges = (
items: Item[],
primaryWarehouseId: number,
): IItemWarehouseQuantityChange[] => {
return items
.filter((item: Item) => item.quantityOnHand)
.map((item: Item) => ({
itemId: item.id,
warehouseId: primaryWarehouseId,
amount: item.quantityOnHand,
}));
};
/**
* Creates initial warehouses items quantity.
* @param {number} tenantId
*/
public run = async (
primaryWarehouseId: number,
trx?: Knex.Transaction,
): Promise<void> => {
const items = await this.itemModel().query(trx).where('type', 'Inventory');
const warehousesChanges = this.getWarehousesItemsChanges(
items,
primaryWarehouseId,
);
await this.warehousesItemsQuantitySync.mutateWarehousesItemsQuantity(
warehousesChanges,
trx,
);
};
}

View File

@@ -0,0 +1,74 @@
import { chain, difference } from 'lodash';
import { Inject, Injectable } from '@nestjs/common';
import { ERRORS } from './constants';
import { ServiceError } from '@/modules/Items/ServiceError';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { Warehouse } from '../models/Warehouse.model';
@Injectable()
export class ValidateWarehouseExistance {
/**
* @param {TenantModelProxy<typeof Warehouse>} warehouseModel - Warehouse model.
*/
constructor(
@Inject(Warehouse.name)
private readonly warehouseModel: TenantModelProxy<typeof Warehouse>,
) {}
/**
* 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} warehouseId - Warehouse id.
*/
public validateWarehouseExistance = (warehouseId: number) => {
const warehouse = this.warehouseModel().query().findById(warehouseId);
if (!warehouse) {
throw new ServiceError(ERRORS.WAREHOUSE_ID_NOT_FOUND);
}
};
/**
* Validate item entries warehouses existance.
* @param {{ warehouseId?: number }[]} entries
*/
public validateItemEntriesWarehousesExistance = async (
entries: { warehouseId?: number }[],
) => {
const entriesWarehousesIds = chain(entries)
.filter((e) => !!e.warehouseId)
.map((e) => e.warehouseId)
.uniq()
.value();
const warehouses = await this.warehouseModel()
.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,35 @@
import { omit } from 'lodash';
import { Injectable } from '@nestjs/common';
import { WarehousesSettings } from '../WarehousesSettings';
@Injectable()
export class WarehouseTransactionDTOTransform {
constructor(private readonly warehousesSettings: WarehousesSettings) {}
/**
* Excludes DTO warehouse id when mutli-warehouses feature is inactive.
* @param {number} tenantId
* @returns {Promise<Omit<T, 'warehouseId'> | T>}
*/
private excludeDTOWarehouseIdWhenInactive = async <
T extends { warehouseId?: number },
>(
DTO: T,
): Promise<Omit<T, 'warehouseId'> | T> => {
const isActive = await this.warehousesSettings.isMultiWarehousesActive();
return !isActive ? omit(DTO, ['warehouseId']) : DTO;
};
/**
*
* @param {number} tenantId
* @param {T} DTO -
* @returns {Omit<T, 'warehouseId'> | T}
*/
public transformDTO = async <T extends { warehouseId?: number }>(
DTO: T,
): Promise<Omit<T, 'warehouseId'> | T> => {
return this.excludeDTOWarehouseIdWhenInactive<T>(DTO);
};
}

View File

@@ -0,0 +1,59 @@
import { Injectable } from '@nestjs/common';
import { isEmpty } from 'lodash';
import { ValidateWarehouseExistance } from './ValidateWarehouseExistance';
import { WarehousesSettings } from '../WarehousesSettings';
interface IWarehouseTransactionDTO {
warehouseId?: number | null;
entries?: { warehouseId?: number | null }[];
}
@Injectable()
export class WarehousesDTOValidators {
constructor(
private readonly validateWarehouseExistanceService: ValidateWarehouseExistance,
private readonly warehousesSettings: WarehousesSettings,
) {}
/**
* Validates the warehouse existance of sale invoice transaction.
* @param {IWarehouseTransactionDTO} DTO
*/
public validateDTOWarehouseExistance = async (
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(
DTO.warehouseId,
);
}
// Validate the sale invoice entries warehouses existance on the storage.
if (!isEmpty(DTO.entries)) {
await this.validateWarehouseExistanceService.validateItemEntriesWarehousesExistance(
DTO.entries,
);
}
};
/**
* Validate the warehouse existance of
* @param {IWarehouseTransactionDTO} saleInvoiceDTO
* @returns
*/
public validateDTOWarehouseWhenActive = async (
DTO: IWarehouseTransactionDTO,
): Promise<void> => {
const isActive = await this.warehousesSettings.isMultiWarehousesActive();
// Can't continue if the multi-warehouses feature is inactive.
if (!isActive) return;
return this.validateDTOWarehouseExistance(DTO);
};
}

View File

@@ -0,0 +1,115 @@
import { IItemWarehouseQuantityChange } from '@/modules/Warehouses/Warehouse.types';
import { set, get, chain, toPairs } from 'lodash';
import { InventoryTransaction } from '@/modules/InventoryCost/models/InventoryTransaction';
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: InventoryTransaction[],
): WarehousesItemsQuantity => {
const warehouseTransactions = inventoryTransactions.filter(
(transaction) => transaction.warehouseId,
);
const warehouseItemsQuantity = new WarehousesItemsQuantity();
warehouseTransactions.forEach((transaction: InventoryTransaction) => {
const change =
transaction.direction === 'IN'
? warehouseItemsQuantity.increment
: warehouseItemsQuantity.decrement;
change(transaction.warehouseId, transaction.itemId, transaction.quantity);
});
return warehouseItemsQuantity;
};
}

View File

@@ -0,0 +1,57 @@
import { WarehousesItemsQuantitySync } from './WarehousesItemsQuantitySync';
import { WarehousesSettings } from '../WarehousesSettings';
import { OnEvent } from '@nestjs/event-emitter';
import { Injectable } from '@nestjs/common';
import { events } from '@/common/events/events';
import {
IInventoryTransactionsCreatedPayload,
IInventoryTransactionsDeletedPayload,
} from '@/modules/InventoryCost/types/InventoryCost.types';
@Injectable()
export class WarehousesItemsQuantitySyncSubscriber {
constructor(
private readonly warehousesItemsQuantitySync: WarehousesItemsQuantitySync,
private readonly warehousesSettings: WarehousesSettings,
) {}
/**
* Syncs warehouses items quantity once inventory transactions created.
* @param {IInventoryTransactionsCreatedPayload}
*/
@OnEvent(events.inventory.onInventoryTransactionsCreated)
async syncWarehousesItemsQuantityOnInventoryTransCreated({
inventoryTransactions,
trx,
}: IInventoryTransactionsCreatedPayload) {
const isActive = await this.warehousesSettings.isMultiWarehousesActive();
// Can't continue if the warehouses features is not active.
if (!isActive) return;
await this.warehousesItemsQuantitySync.mutateWarehousesItemsQuantityFromTransactions(
inventoryTransactions,
trx,
);
}
/**
* Syncs warehouses items quantity once inventory transactions deleted.
* @param {IInventoryTransactionsDeletedPayload}
*/
@OnEvent(events.inventory.onInventoryTransactionsDeleted)
async syncWarehousesItemsQuantityOnInventoryTransDeleted({
oldInventoryTransactions,
trx,
}: IInventoryTransactionsDeletedPayload) {
const isActive = await this.warehousesSettings.isMultiWarehousesActive();
// Can't continue if the warehouses feature is not active yet.
if (!isActive) return;
await this.warehousesItemsQuantitySync.reverseWarehousesItemsQuantityFromTransactions(
oldInventoryTransactions,
trx,
);
}
}

View File

@@ -0,0 +1,128 @@
import { InventoryTransaction } from '@/modules/InventoryCost/models/InventoryTransaction';
import { Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { omit } from 'lodash';
import { IItemWarehouseQuantityChange } from '../Warehouse.types';
import { WarehousesItemsQuantity } from './WarehousesItemsQuantity';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { ItemWarehouseQuantity } from '../models/ItemWarehouseQuantity';
@Injectable()
export class WarehousesItemsQuantitySync {
constructor(
private readonly warehouseItemsQuantityModel: WarehousesItemsQuantity,
private readonly itemWarehouseQuantityModel: TenantModelProxy<
typeof ItemWarehouseQuantity
>,
) {}
/**
* Retrieves the reversed warehouses items quantity changes.
* @param {InventoryTransaction[]} inventoryTransactions
* @returns {IItemWarehouseQuantityChange[]}
*/
public getReverseWarehousesItemsQuantityChanges = (
inventoryTransactions: InventoryTransaction[],
): IItemWarehouseQuantityChange[] => {
const warehouseItemsQuantity =
WarehousesItemsQuantity.fromInventoryTransaction(inventoryTransactions);
return warehouseItemsQuantity.reverse().toArray();
};
/**
* Retrieves the warehouses items changes from the given inventory tranasctions.
* @param {InventoryTransaction[]} inventoryTransactions
* @returns {IItemWarehouseQuantityChange[]}
*/
public getWarehousesItemsQuantityChange = (
inventoryTransactions: InventoryTransaction[],
): IItemWarehouseQuantityChange[] => {
const warehouseItemsQuantity =
WarehousesItemsQuantity.fromInventoryTransaction(inventoryTransactions);
return warehouseItemsQuantity.toArray();
};
/**
* Mutates warehouses items quantity on hand on the storage.
* @param {IItemWarehouseQuantityChange[]} warehousesItemsQuantity
* @param {Knex.Transaction} trx
*/
public mutateWarehousesItemsQuantity = async (
warehousesItemsQuantity: IItemWarehouseQuantityChange[],
trx?: Knex.Transaction,
): Promise<void> => {
const mutationsOpers = warehousesItemsQuantity.map(
(change: IItemWarehouseQuantityChange) =>
this.mutateWarehouseItemQuantity(change, trx),
);
await Promise.all(mutationsOpers);
};
/**
* Mutates the warehouse item quantity.
* @param {number} warehouseItemQuantity
* @param {Knex.Transaction} trx
*/
public mutateWarehouseItemQuantity = async (
warehouseItemQuantity: IItemWarehouseQuantityChange,
trx: Knex.Transaction,
): Promise<void> => {
const itemWarehouseQuantity = await this.itemWarehouseQuantityModel()
.query(trx)
.where('itemId', warehouseItemQuantity.itemId)
.where('warehouseId', warehouseItemQuantity.warehouseId)
.first();
if (itemWarehouseQuantity) {
// @ts-ignore
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 (
inventoryTransactions: InventoryTransaction[],
trx?: Knex.Transaction,
) => {
const changes = this.getWarehousesItemsQuantityChange(
inventoryTransactions,
);
await this.mutateWarehousesItemsQuantity(changes, trx);
};
/**
* Reverses warehouses items quantity from inventory transactions.
* @param {number} tenantId
* @param {IInventoryTransaction[]} inventoryTransactions
* @param {Knex.Transaction} trx
*/
public reverseWarehousesItemsQuantityFromTransactions = async (
inventoryTransactions: InventoryTransaction[],
trx?: Knex.Transaction,
) => {
const changes = this.getReverseWarehousesItemsQuantityChanges(
inventoryTransactions,
);
await this.mutateWarehousesItemsQuantity(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,44 @@
import { GetItemWarehouseTransformer } from './GettItemWarehouseTransformer';
import { Inject, Injectable } from '@nestjs/common';
import { ItemWarehouseQuantity } from '../models/ItemWarehouseQuantity';
import { Item } from '@/modules/Items/models/Item';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetItemWarehouses {
constructor(
@Inject(ItemWarehouseQuantity.name)
private readonly itemWarehouseQuantityModel: TenantModelProxy<
typeof ItemWarehouseQuantity
>,
@Inject(Item.name)
private readonly itemModel: TenantModelProxy<typeof Item>,
private readonly transformer: TransformerInjectable,
) {}
/**
* Retrieves the item warehouses.
* @param {number} itemId
* @returns
*/
public getItemWarehouses = async (itemId: number) => {
// Retrieves specific item or throw not found service error.
const item = await this.itemModel()
.query()
.findById(itemId)
.throwIfNotFound();
const itemWarehouses = await this.itemWarehouseQuantityModel()
.query()
.where('itemId', itemId)
.withGraphFetched('warehouse');
// Retrieves the transformed items warehouses.
return this.transformer.transform(
itemWarehouses,
new GetItemWarehouseTransformer(),
);
};
}

View File

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

View File

@@ -0,0 +1,23 @@
import { Inject, Injectable } from '@nestjs/common';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
import { InventoryTransaction } from '../InventoryCost/models/InventoryTransaction';
@Injectable()
export class UpdateInventoryTransactionsWithWarehouse {
constructor(
@Inject(InventoryTransaction.name)
private readonly inventoryTransactionModel: TenantModelProxy<
typeof InventoryTransaction
>,
) {}
/**
* Updates all inventory transactions with primary warehouse.
* @param {number} warehouseId - Warehouse ID.
*/
public run = async (primaryWarehouseId: number) => {
await this.inventoryTransactionModel().query().update({
warehouseId: primaryWarehouseId,
});
};
}

View File

@@ -0,0 +1,186 @@
import { Knex } from 'knex';
import { Warehouse } from './models/Warehouse.model';
import { WarehouseTransfer } from '../WarehousesTransfers/models/WarehouseTransfer';
import { ModelObject } from 'objection';
export interface IWarehouse {
id?: number;
}
export interface IWarehouseTransferEntry {
id?: number;
index?: number;
itemId: number;
description: string;
quantity: number;
cost: number;
}
export interface ICreateWarehouseDTO {
name: string;
code: string;
city?: string;
country?: string;
address?: string;
primary?: boolean;
}
export interface IEditWarehouseDTO {
name: string;
code: string;
city: string;
country: string;
address: string;
}
export interface IWarehouseTransferEntryDTO {
index?: number;
itemId: number;
description: string;
quantity: number;
cost?: number;
}
export interface ICreateWarehouseTransferDTO {
fromWarehouseId: number;
toWarehouseId: number;
transactionNumber: string;
date: Date;
transferInitiated: boolean;
transferDelivered: boolean;
entries: IWarehouseTransferEntryDTO[];
}
export interface IEditWarehouseTransferDTO {
fromWarehouseId: number;
toWarehouseId: number;
transactionNumber: string;
date: Date;
entries: {
id?: number;
itemId: number;
description: string;
quantity: number;
}[];
}
export interface IWarehouseEditPayload {
tenantId: number;
warehouseId: number;
warehouseDTO: IEditWarehouseDTO;
trx: Knex.Transaction;
}
export interface IWarehouseEditedPayload {
tenantId: number;
warehouse: IWarehouse;
warehouseDTO: IEditWarehouseDTO;
trx: Knex.Transaction;
}
export interface IWarehouseDeletePayload {
// tenantId: number;
warehouseId: number;
trx: Knex.Transaction;
}
export interface IWarehouseDeletedPayload {
tenantId: number;
warehouseId: number;
trx: Knex.Transaction;
}
export interface IWarehouseCreatePayload {
warehouseDTO: ICreateWarehouseDTO;
trx: Knex.Transaction;
}
export interface IWarehouseCreatedPayload {
warehouse: IWarehouse;
warehouseDTO: ICreateWarehouseDTO;
trx: Knex.Transaction;
}
export interface IWarehouseTransferCreate {
trx: Knex.Transaction;
warehouseTransferDTO: ICreateWarehouseTransferDTO;
tenantId: number;
}
export interface IWarehouseTransferCreated {
trx?: Knex.Transaction;
warehouseTransfer: ModelObject<WarehouseTransfer>;
warehouseTransferDTO: ICreateWarehouseTransferDTO;
}
export interface IWarehouseTransferEditPayload {
editWarehouseDTO: IEditWarehouseTransferDTO;
oldWarehouseTransfer: ModelObject<WarehouseTransfer>;
trx: Knex.Transaction;
}
export interface IWarehouseTransferEditedPayload {
editWarehouseDTO: IEditWarehouseTransferDTO;
oldWarehouseTransfer: ModelObject<WarehouseTransfer>;
warehouseTransfer: ModelObject<WarehouseTransfer>;
trx: Knex.Transaction;
}
export interface IWarehouseTransferDeletePayload {
oldWarehouseTransfer: ModelObject<WarehouseTransfer>;
trx: Knex.Transaction;
}
export interface IWarehouseTransferDeletedPayload {
oldWarehouseTransfer: ModelObject<WarehouseTransfer>;
trx: Knex.Transaction;
}
export interface IGetWarehousesTransfersFilterDTO {
page: number;
pageSize: number;
searchKeyword: string;
}
export interface IItemWarehouseQuantityChange {
itemId: number;
warehouseId: number;
amount: number;
}
export interface IWarehousesActivatePayload {
// tenantId: number;
}
export interface IWarehousesActivatedPayload {
// tenantId: number;
primaryWarehouse: Warehouse;
}
export interface IWarehouseMarkAsPrimaryPayload {
oldWarehouse: Warehouse;
trx: Knex.Transaction;
}
export interface IWarehouseMarkedAsPrimaryPayload {
oldWarehouse: Warehouse;
markedWarehouse: Warehouse;
trx: Knex.Transaction;
}
export interface IWarehouseTransferInitiatePayload {
oldWarehouseTransfer: ModelObject<WarehouseTransfer>;
trx: Knex.Transaction;
}
export interface IWarehouseTransferInitiatedPayload {
warehouseTransfer: ModelObject<WarehouseTransfer>;
oldWarehouseTransfer: ModelObject<WarehouseTransfer>;
trx: Knex.Transaction;
}
export interface IWarehouseTransferTransferingPayload {
oldWarehouseTransfer: ModelObject<WarehouseTransfer>;
trx: Knex.Transaction;
}
export interface IWarehouseTransferTransferredPayload {
warehouseTransfer: ModelObject<WarehouseTransfer>;
oldWarehouseTransfer: ModelObject<WarehouseTransfer>;
trx: Knex.Transaction;
}

View File

@@ -0,0 +1,71 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Put,
} from '@nestjs/common';
import { WarehousesApplication } from './WarehousesApplication.service';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { CreateWarehouseDto, EditWarehouseDto } from './dtos/Warehouse.dto';
@Controller('warehouses')
@ApiTags('warehouses')
export class WarehousesController {
constructor(private warehousesApplication: WarehousesApplication) {}
@Post()
@ApiOperation({ summary: 'Create a warehouse' })
createWarehouse(@Body() createWarehouseDTO: CreateWarehouseDto) {
return this.warehousesApplication.createWarehouse(createWarehouseDTO);
}
@Put(':id')
editWarehouse(
@Param('id') warehouseId: string,
@Body() editWarehouseDTO: EditWarehouseDto,
) {
return this.warehousesApplication.editWarehouse(
Number(warehouseId),
editWarehouseDTO,
);
}
@Delete(':id')
@ApiOperation({ summary: 'Delete a warehouse' })
deleteWarehouse(@Param('id') warehouseId: string) {
return this.warehousesApplication.deleteWarehouse(Number(warehouseId));
}
@Get(':id')
@ApiOperation({ summary: 'Get a warehouse' })
getWarehouse(@Param('id') warehouseId: string) {
return this.warehousesApplication.getWarehouse(Number(warehouseId));
}
@Get()
@ApiOperation({ summary: 'Get all warehouses' })
getWarehouses() {
return this.warehousesApplication.getWarehouses();
}
@Post('activate')
@ApiOperation({ summary: 'Activate a warehouse' })
activateWarehouses() {
return this.warehousesApplication.activateWarehouses();
}
@Post(':id/mark-primary')
@ApiOperation({ summary: 'Mark a warehouse as primary' })
markWarehousePrimary(@Param('id') warehouseId: string) {
return this.warehousesApplication.markWarehousePrimary(Number(warehouseId));
}
@Get('items/:itemId')
@ApiOperation({ summary: 'Get item warehouses' })
getItemWarehouses(@Param('itemId') itemId: string) {
return this.warehousesApplication.getItemWarehouses(Number(itemId));
}
}

View File

@@ -0,0 +1,95 @@
import { Module } from '@nestjs/common';
import { I18nContext } from 'nestjs-i18n';
import { TenancyDatabaseModule } from '../Tenancy/TenancyDB/TenancyDB.module';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
import { CreateWarehouse } from './commands/CreateWarehouse.service';
import { EditWarehouse } from './commands/EditWarehouse.service';
import { DeleteWarehouseService } from './commands/DeleteWarehouse.service';
import { WarehousesController } from './Warehouses.controller';
import { GetWarehouse } from './queries/GetWarehouse';
import { WarehouseMarkPrimary } from './commands/WarehouseMarkPrimary.service';
import { GetWarehouses } from './queries/GetWarehouses';
import { GetItemWarehouses } from './Items/GetItemWarehouses';
import { WarehouseValidator } from './commands/WarehouseValidator.service';
import { WarehousesApplication } from './WarehousesApplication.service';
import { ActivateWarehousesService } from './commands/ActivateWarehouses.service';
import { CreateInitialWarehouse } from './commands/CreateInitialWarehouse.service';
import { WarehousesSettings } from './WarehousesSettings';
import { WarehouseTransactionDTOTransform } from './Integrations/WarehouseTransactionDTOTransform';
import { BillsActivateWarehousesSubscriber } from './subscribers/Activate/BillWarehousesActivateSubscriber';
import { VendorCreditsActivateWarehousesSubscriber } from './subscribers/Activate/VendorCreditWarehousesActivateSubscriber';
import { ReceiptsActivateWarehousesSubscriber } from './subscribers/Activate/ReceiptWarehousesActivateSubscriber';
import { InvoicesActivateWarehousesSubscriber } from './subscribers/Activate/InvoiceWarehousesActivateSubscriber';
import { CreditsActivateWarehousesSubscriber } from './subscribers/Activate/CreditNoteWarehousesActivateSubscriber';
import { InventoryAdjustmentWarehouseValidatorSubscriber } from './subscribers/Validators/InventoryAdjustment/InventoryAdjustmentWarehouseValidatorSubscriber';
import { DeleteItemWarehousesQuantitySubscriber } from './subscribers/DeleteItemWarehousesQuantitySubscriber';
import { VendorCreditWarehousesValidateSubscriber } from './subscribers/Validators/Purchases/VendorCreditWarehousesSubscriber';
import { SaleInvoicesWarehousesValidateSubscriber } from './subscribers/Validators/Sales/SaleInvoicesWarehousesSubscriber';
import { SaleEstimateWarehousesValidateSubscriber } from './subscribers/Validators/Sales/SaleEstimateWarehousesSubscriber';
import { SaleReceiptWarehousesValidateSubscriber } from './subscribers/Validators/Sales/SaleReceiptWarehousesSubscriber';
import { CreditNoteWarehousesValidateSubscriber } from './subscribers/Validators/Sales/CreditNoteWarehousesSubscriber';
import { BillWarehousesValidateSubscriber } from './subscribers/Validators/Purchases/BillWarehousesSubscriber';
import { AccountsTransactionsWarehousesSubscribe } from './AccountsTransactionsWarehousesSubscribe';
import { BillActivateWarehouses } from './Activate/BillWarehousesActivate';
import { CreditNotesActivateWarehouses } from './Activate/CreditNoteWarehousesActivate';
import { VendorCreditActivateWarehouses } from './Activate/VendorCreditWarehousesActivate';
import { InvoicesActivateWarehouses } from './Activate/InvoiceWarehousesActivate';
import { ReceiptActivateWarehouses } from './Activate/ReceiptWarehousesActivate';
import { WarehousesDTOValidators } from './Integrations/WarehousesDTOValidators';
import { DeleteItemWarehousesQuantity } from './commands/DeleteItemWarehousesQuantity';
import { InventoryTransactionsWarehouses } from './AccountsTransactionsWarehouses';
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
import { Warehouse } from './models/Warehouse.model';
import { ValidateWarehouseExistance } from './Integrations/ValidateWarehouseExistance';
const models = [RegisterTenancyModel(Warehouse)];
@Module({
imports: [TenancyDatabaseModule, ...models],
controllers: [WarehousesController],
providers: [
CreateWarehouse,
EditWarehouse,
DeleteWarehouseService,
GetWarehouse,
GetWarehouses,
GetItemWarehouses,
WarehouseMarkPrimary,
WarehouseValidator,
WarehousesApplication,
ActivateWarehousesService,
CreateInitialWarehouse,
WarehousesSettings,
I18nContext,
TenancyContext,
TransformerInjectable,
WarehouseTransactionDTOTransform,
BillsActivateWarehousesSubscriber,
CreditsActivateWarehousesSubscriber,
InvoicesActivateWarehousesSubscriber,
ReceiptsActivateWarehousesSubscriber,
VendorCreditsActivateWarehousesSubscriber,
InventoryAdjustmentWarehouseValidatorSubscriber,
DeleteItemWarehousesQuantitySubscriber,
BillWarehousesValidateSubscriber,
CreditNoteWarehousesValidateSubscriber,
SaleReceiptWarehousesValidateSubscriber,
SaleEstimateWarehousesValidateSubscriber,
SaleInvoicesWarehousesValidateSubscriber,
VendorCreditWarehousesValidateSubscriber,
AccountsTransactionsWarehousesSubscribe,
BillActivateWarehouses,
CreditNotesActivateWarehouses,
VendorCreditActivateWarehouses,
CreditNotesActivateWarehouses,
InvoicesActivateWarehouses,
ReceiptActivateWarehouses,
WarehousesDTOValidators,
DeleteItemWarehousesQuantity,
InventoryTransactionsWarehouses,
ValidateWarehouseExistance
],
exports: [WarehouseTransactionDTOTransform, ...models],
})
export class WarehousesModule {}

View File

@@ -0,0 +1,104 @@
import {
IWarehouse,
} from './Warehouse.types';
import { ActivateWarehousesService } from './commands/ActivateWarehouses.service';
import { CreateWarehouse } from './commands/CreateWarehouse.service';
import { DeleteWarehouseService } from './commands/DeleteWarehouse.service';
import { EditWarehouse } from './commands/EditWarehouse.service';
import { GetWarehouse } from './queries/GetWarehouse';
import { GetWarehouses } from './queries/GetWarehouses';
import { GetItemWarehouses } from './Items/GetItemWarehouses';
import { WarehouseMarkPrimary } from './commands/WarehouseMarkPrimary.service';
import { Injectable } from '@nestjs/common';
import { CreateWarehouseDto, EditWarehouseDto } from './dtos/Warehouse.dto';
@Injectable()
export class WarehousesApplication {
constructor(
private createWarehouseService: CreateWarehouse,
private editWarehouseService: EditWarehouse,
private deleteWarehouseService: DeleteWarehouseService,
private getWarehouseService: GetWarehouse,
private getWarehousesService: GetWarehouses,
private activateWarehousesService: ActivateWarehousesService,
private markWarehousePrimaryService: WarehouseMarkPrimary,
private getItemWarehousesService: GetItemWarehouses,
) {}
/**
* Creates a new warehouse.
* @param {ICreateWarehouseDTO} createWarehouseDTO
* @returns {Promise<IWarehouse>}
*/
public createWarehouse = (createWarehouseDTO: CreateWarehouseDto) => {
return this.createWarehouseService.createWarehouse(createWarehouseDTO);
};
/**
* Edits the given warehouse.
* @param {number} warehouseId
* @param {EditWarehouseDto} editWarehouseDTO
* @returns {Promise<void>}
*/
public editWarehouse = (
warehouseId: number,
editWarehouseDTO: EditWarehouseDto,
) => {
return this.editWarehouseService.editWarehouse(
warehouseId,
editWarehouseDTO,
);
};
/**
* Deletes the given warehouse.
* @param {number} warehouseId
*/
public deleteWarehouse = (warehouseId: number) => {
return this.deleteWarehouseService.deleteWarehouse(warehouseId);
};
/**
* Retrieves the specific warehouse.
* @param {number} warehouseId
* @returns
*/
public getWarehouse = (warehouseId: number) => {
return this.getWarehouseService.getWarehouse(warehouseId);
};
/**
* Retrieves the warehouses list.
* @returns {Promise<IWarehouse[]>}
*/
public getWarehouses = () => {
return this.getWarehousesService.getWarehouses();
};
/**
* Activates the warehouses feature.
* @returns {Promise<void>}
*/
public activateWarehouses = () => {
return this.activateWarehousesService.activateWarehouses();
};
/**
* Mark the given warehouse as primary.
* @param {number} tenantId -
* @returns {Promise<IWarehouse>}
*/
public markWarehousePrimary = (warehouseId: number): Promise<IWarehouse> => {
return this.markWarehousePrimaryService.markAsPrimary(warehouseId);
};
/**
* Retrieves the specific item warehouses quantity.
* @param {number} tenantId
* @param {number} itemId
* @returns
*/
public getItemWarehouses = (itemId: number): Promise<any> => {
return this.getItemWarehousesService.getItemWarehouses(itemId);
};
}

View File

@@ -0,0 +1,32 @@
import { Inject, Injectable } from '@nestjs/common';
import { SettingsStore } from '../Settings/SettingsStore';
import { SETTINGS_PROVIDER } from '../Settings/Settings.types';
import { Features } from '@/common/types/Features';
@Injectable()
export class WarehousesSettings {
constructor(
@Inject(SETTINGS_PROVIDER)
private readonly settingsStore: () => SettingsStore,
) {}
/**
* Marks multi-warehouses as activated.
*/
public markMutliwarehoussAsActivated = async () => {
const settings = await this.settingsStore();
settings.set({ group: 'features', key: Features.WAREHOUSES, value: 1 });
};
/**
* Determines multi-warehouses is active.
* @param {number} tenantId
* @returns {boolean}
*/
public isMultiWarehousesActive = async () => {
const settings = await this.settingsStore();
return settings.get({ group: 'features', key: Features.WAREHOUSES });
};
}

View File

@@ -0,0 +1,60 @@
import { Knex } from 'knex';
import { Injectable } from '@nestjs/common';
import { CreateInitialWarehouse } from './CreateInitialWarehouse.service';
import { WarehousesSettings } from '../WarehousesSettings';
import { ERRORS } from '../contants';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { ServiceError } from '@/modules/Items/ServiceError';
import { events } from '@/common/events/events';
@Injectable()
export class ActivateWarehousesService {
/**
* @param {UnitOfWork} uow - Unit of work.
* @param {EventEmitter2} eventEmitter - Event emitter.
* @param {CreateInitialWarehouse} createInitialWarehouse - Create initial warehouse service.
* @param {WarehousesSettings} settings - Warehouses settings.
*/
constructor(
private readonly uow: UnitOfWork,
private readonly eventEmitter: EventEmitter2,
private readonly createInitialWarehouse: CreateInitialWarehouse,
private readonly settings: WarehousesSettings,
) {}
/**
* Throws error if the multi-warehouses is already activated.
*/
private throwIfWarehousesActivated(isActivated: boolean): void {
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.
*/
public async activateWarehouses(): Promise<void> {
const isActivated = await this.settings.isMultiWarehousesActive();
this.throwIfWarehousesActivated(isActivated);
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
await this.eventEmitter.emitAsync(events.warehouse.onActivate, { trx });
const primaryWarehouse =
await this.createInitialWarehouse.createInitialWarehouse();
this.settings.markMutliwarehoussAsActivated();
await this.eventEmitter.emitAsync(events.warehouse.onActivated, {
primaryWarehouse,
trx,
});
});
}
}

View File

@@ -0,0 +1,27 @@
import { CreateWarehouse } from './CreateWarehouse.service';
import { Injectable } from '@nestjs/common';
import { I18nContext } from 'nestjs-i18n';
@Injectable()
export class CreateInitialWarehouse {
/**
* @param {CreateWarehouse} createWarehouse - Create warehouse service.
* @param {I18nContext} i18n - I18n context.
*/
constructor(
private readonly createWarehouse: CreateWarehouse,
private readonly i18n: I18nContext,
) {}
/**
* Creates a initial warehouse.
* @param {number} tenantId
*/
public createInitialWarehouse = async () => {
return this.createWarehouse.createWarehouse({
name: this.i18n.t('warehouses.primary_warehouse'),
code: '10001',
primary: true,
});
};
}

View File

@@ -0,0 +1,76 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import {
IWarehouseCreatedPayload,
IWarehouseCreatePayload,
} from '../Warehouse.types';
import { WarehouseValidator } from './WarehouseValidator.service';
import { Warehouse } from '../models/Warehouse.model';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { CreateWarehouseDto } from '../dtos/Warehouse.dto';
@Injectable()
export class CreateWarehouse {
/**
* @param {UnitOfWork} uow - Unit of work.
* @param {EventEmitter2} eventEmitter - Event emitter.
* @param {WarehouseValidator} validator - Warehouse command validator.
* @param {typeof Warehouse} warehouseModel - Warehouse model.
*/
constructor(
private readonly uow: UnitOfWork,
private readonly eventEmitter: EventEmitter2,
private readonly validator: WarehouseValidator,
@Inject(Warehouse.name)
private readonly warehouseModel: TenantModelProxy<typeof Warehouse>,
) {}
/**
* Authorize the warehouse before creating.
* @param {ICreateWarehouseDTO} warehouseDTO -
*/
public authorize = async (warehouseDTO: CreateWarehouseDto) => {
if (warehouseDTO.code) {
await this.validator.validateWarehouseCodeUnique(warehouseDTO.code);
}
};
/**
* Creates a new warehouse on the system.
* @param {ICreateWarehouseDTO} warehouseDTO
*/
public createWarehouse = async (
warehouseDTO: CreateWarehouseDto,
): Promise<Warehouse> => {
// Authorize warehouse before creating.
await this.authorize(warehouseDTO);
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onWarehouseCreate` event.
await this.eventEmitter.emitAsync(events.warehouse.onEdit, {
warehouseDTO,
trx,
} as IWarehouseCreatePayload);
// Creates a new warehouse on the storage.
const warehouse = await this.warehouseModel()
.query(trx)
.insertAndFetch({
...warehouseDTO,
});
// Triggers `onWarehouseCreated` event.
await this.eventEmitter.emitAsync(events.warehouse.onCreated, {
warehouseDTO,
warehouse,
trx,
} as IWarehouseCreatedPayload);
return warehouse;
});
};
}

View File

@@ -0,0 +1,32 @@
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { ItemWarehouseQuantity } from '../models/ItemWarehouseQuantity';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class DeleteItemWarehousesQuantity {
/**
* @param {typeof ItemWarehouseQuantity} itemWarehouseQuantityModel - Item warehouse quantity model.
*/
constructor(
@Inject(ItemWarehouseQuantity.name)
private readonly itemWarehouseQuantityModel: TenantModelProxy<
typeof ItemWarehouseQuantity
>,
) {}
/**
* Deletes the given item warehouses quantities.
* @param {number} itemId
* @param {Knex.Transaction} trx -
*/
public deleteItemWarehousesQuantity = async (
itemId: number,
trx?: Knex.Transaction,
): Promise<void> => {
await this.itemWarehouseQuantityModel()
.query(trx)
.where('itemId', itemId)
.delete();
};
}

View File

@@ -0,0 +1,82 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { EventEmitter2 } from '@nestjs/event-emitter';
import {
IWarehouseDeletedPayload,
IWarehouseDeletePayload,
} from '../Warehouse.types';
import { WarehouseValidator } from './WarehouseValidator.service';
import { ERRORS } from '../contants';
import { Warehouse } from '../models/Warehouse.model';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class DeleteWarehouseService {
/**
* @param {UnitOfWork} uow - Unit of work.
* @param {EventEmitter2} eventPublisher - Event emitter.
* @param {WarehouseValidator} validator - Warehouse command validator.
* @param {typeof Warehouse} warehouseModel - Warehouse model.
*/
constructor(
private readonly uow: UnitOfWork,
private readonly eventPublisher: EventEmitter2,
private readonly validator: WarehouseValidator,
@Inject(Warehouse.name)
private readonly warehouseModel: TenantModelProxy<typeof Warehouse>,
) {}
/**
* Validates the given warehouse before deleting.
* @param {number} warehouseId
* @returns {Promise<void>}
*/
public authorize = async (warehouseId: number): Promise<void> => {
await this.validator.validateWarehouseNotOnlyWarehouse(warehouseId);
};
/**
* Deletes specific warehouse.
* @param {number} warehouseId
* @returns {Promise<void>}
*/
public deleteWarehouse = async (warehouseId: number): Promise<void> => {
// Retrieves the old warehouse or throw not found service error.
const oldWarehouse = await this.warehouseModel()
.query()
.findById(warehouseId)
.throwIfNotFound();
// .queryAndThrowIfHasRelations({
// type: ERRORS.WAREHOUSE_HAS_ASSOCIATED_TRANSACTIONS,
// });
// Validates the given warehouse before deleting.
await this.authorize(warehouseId);
// Creates a new warehouse under unit-of-work.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
const eventPayload = {
warehouseId,
oldWarehouse,
trx,
} as IWarehouseDeletePayload | IWarehouseDeletedPayload;
// Triggers `onWarehouseCreate`.
await this.eventPublisher.emitAsync(
events.warehouse.onDelete,
eventPayload,
);
// Deletes the given warehouse from the storage.
await this.warehouseModel().query().findById(warehouseId).delete();
// Triggers `onWarehouseCreated`.
await this.eventPublisher.emitAsync(
events.warehouse.onDeleted,
eventPayload as IWarehouseDeletedPayload,
);
});
};
}

View File

@@ -0,0 +1,81 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { IEditWarehouseDTO, IWarehouse } from '../Warehouse.types';
import { WarehouseValidator } from './WarehouseValidator.service';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Warehouse } from '../models/Warehouse.model';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { EditWarehouseDto } from '../dtos/Warehouse.dto';
@Injectable()
export class EditWarehouse {
/**
* @param {UnitOfWork} uow - Unit of work.
* @param {EventEmitter2} eventPublisher - Event emitter.
* @param {WarehouseValidator} validator - Warehouse command validator.
* @param {typeof Warehouse} warehouseModel - Warehouse model.
*/
constructor(
private readonly uow: UnitOfWork,
private readonly eventPublisher: EventEmitter2,
private readonly validator: WarehouseValidator,
@Inject(Warehouse.name)
private readonly warehouseModel: TenantModelProxy<typeof Warehouse>,
) {}
/**
* Authorize the warehouse before deleting.
*/
public authorize = async (
warehouseDTO: EditWarehouseDto,
warehouseId: number,
) => {
if (warehouseDTO.code) {
await this.validator.validateWarehouseCodeUnique(
warehouseDTO.code,
warehouseId,
);
}
};
/**
* Edits a new warehouse on the system.
* @param {ICreateWarehouseDTO} warehouseDTO
* @returns {Promise<IWarehouse>}
*/
public editWarehouse = async (
warehouseId: number,
warehouseDTO: EditWarehouseDto,
): Promise<Warehouse> => {
// Authorize the warehouse DTO before editing.
await this.authorize(warehouseDTO, warehouseId);
// Edits warehouse under unit-of-work.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onWarehouseEdit` event.
await this.eventPublisher.emitAsync(events.warehouse.onEdit, {
warehouseId,
warehouseDTO,
trx,
});
// Updates the given branch on the storage.
const warehouse = await this.warehouseModel()
.query()
.patchAndFetchById(warehouseId, {
...warehouseDTO,
});
// Triggers `onWarehouseEdited` event.
await this.eventPublisher.emitAsync(events.warehouse.onEdited, {
warehouse,
warehouseDTO,
trx,
});
return warehouse;
});
};
}

View File

@@ -0,0 +1,55 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import {
IWarehouseMarkAsPrimaryPayload,
IWarehouseMarkedAsPrimaryPayload,
} from '../Warehouse.types';
import { Warehouse } from '../models/Warehouse.model';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class WarehouseMarkPrimary {
constructor(
@Inject(Warehouse.name)
private readonly warehouseModel: TenantModelProxy<typeof Warehouse>,
private readonly uow: UnitOfWork,
private readonly eventPublisher: EventEmitter2,
) {}
/**
* Marks the given warehouse as primary.
* @param {number} warehouseId
* @returns {Promise<IWarehouse>}
*/
public async markAsPrimary(warehouseId: number) {
const oldWarehouse = await this.warehouseModel()
.query()
.findById(warehouseId)
.throwIfNotFound();
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
await this.eventPublisher.emitAsync(events.warehouse.onMarkPrimary, {
oldWarehouse,
trx,
} as IWarehouseMarkAsPrimaryPayload);
await this.warehouseModel().query(trx).update({ primary: false });
const markedWarehouse = await this.warehouseModel()
.query(trx)
.patchAndFetchById(warehouseId, { primary: true });
await this.eventPublisher.emitAsync(events.warehouse.onMarkedPrimary, {
oldWarehouse,
markedWarehouse,
trx,
} as IWarehouseMarkedAsPrimaryPayload);
return markedWarehouse;
});
}
}

View File

@@ -0,0 +1,54 @@
import { ServiceError } from '@/modules/Items/ServiceError';
import { Inject } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { ERRORS } from '../contants';
import { Warehouse } from '../models/Warehouse.model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class WarehouseValidator {
constructor(
@Inject(Warehouse.name)
private readonly warehouseModel: TenantModelProxy<typeof Warehouse>,
) {}
/**
* Validates the warehouse not only warehouse.
* @param {number} warehouseId
*/
public validateWarehouseNotOnlyWarehouse = async (warehouseId: number) => {
const warehouses = await this.warehouseModel()
.query()
.whereNot('id', warehouseId);
if (warehouses.length === 0) {
throw new ServiceError(ERRORS.COULD_NOT_DELETE_ONLY_WAERHOUSE);
}
};
/**
* Validates the warehouse code uniqueness.
* @param {string} code
* @param {number} exceptWarehouseId
*/
public validateWarehouseCodeUnique = async (
code: string,
exceptWarehouseId?: number,
) => {
const warehouse = await this.warehouseModel()
.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,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'
};

View File

@@ -0,0 +1,55 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsBoolean, IsEmail, IsOptional, IsUrl } from "class-validator";
import { IsNotEmpty } from "class-validator";
import { IsString } from "class-validator";
export class CommandWarehouseDto {
@IsString()
@IsNotEmpty()
@ApiProperty({ description: 'The name of the warehouse' })
name: string;
@IsBoolean()
@IsOptional()
@ApiProperty({ description: 'Whether the warehouse is primary' })
primary?: boolean;
@IsString()
@IsOptional()
@ApiProperty({ description: 'The code of the warehouse' })
code?: string;
@IsString()
@IsOptional()
@ApiProperty({ description: 'The address of the warehouse' })
address?: string;
@IsString()
@IsOptional()
@ApiProperty({ description: 'The city of the warehouse' })
city?: string;
@IsString()
@IsOptional()
@ApiProperty({ description: 'The country of the warehouse' })
country?: string;
@IsString()
@IsOptional()
@ApiProperty({ description: 'The phone number of the warehouse' })
phoneNumber?: string;
@IsEmail()
@IsOptional()
@ApiProperty({ description: 'The email of the warehouse' })
email?: string;
@IsUrl()
@IsOptional()
@ApiProperty({ description: 'The website of the warehouse' })
website?: string;
}
export class CreateWarehouseDto extends CommandWarehouseDto {}
export class EditWarehouseDto extends CommandWarehouseDto {}

View File

@@ -0,0 +1,38 @@
import { BaseModel } from '@/models/Model';
import { Model } from 'objection';
export class ItemWarehouseQuantity extends BaseModel{
/**
* Table name.
*/
static get tableName() {
return 'items_warehouses_quantity';
}
/**
* Relation mappings.
*/
static get relationMappings() {
const { Item } = require('../../Items/models/Item');
const { Warehouse } = require('./Warehouse.model');
return {
item: {
relation: Model.BelongsToOneRelation,
modelClass: Item,
join: {
from: 'items_warehouses_quantity.itemId',
to: 'items.id',
},
},
warehouse: {
relation: Model.BelongsToOneRelation,
modelClass: Warehouse,
join: {
from: 'items_warehouses_quantity.warehouseId',
to: 'warehouses.id',
},
},
};
}
}

View File

@@ -0,0 +1,172 @@
// import { Model } from 'objection';
import { BaseModel } from '@/models/Model';
import { Item } from '@/modules/Items/models/Item';
export class Warehouse extends BaseModel {
name!: string;
code!: string;
city!: string;
country!: string;
address!: string;
primary!: boolean;
items!: Item[];
/**
* Table name.
*/
static get tableName() {
return 'warehouses';
}
/**
* Timestamps columns.
*/
get timestamps() {
return ['created_at', 'updated_at'];
}
/**
* Model modifiers.
*/
static get modifiers() {
return {
/**
* Filters accounts by the given ids.
* @param {Query} query
* @param {number[]} accountsIds
*/
isPrimary(query) {
query.where('primary', true);
},
};
}
/**
* Relationship mapping.
*/
// static get relationMappings() {
// const SaleInvoice = require('models/SaleInvoice');
// const SaleEstimate = require('models/SaleEstimate');
// const SaleReceipt = require('models/SaleReceipt');
// const Bill = require('models/Bill');
// const VendorCredit = require('models/VendorCredit');
// const CreditNote = require('models/CreditNote');
// const InventoryTransaction = require('models/InventoryTransaction');
// const WarehouseTransfer = require('models/WarehouseTransfer');
// const InventoryAdjustment = require('models/InventoryAdjustment');
// return {
// /**
// * Warehouse may belongs to associated sale invoices.
// */
// invoices: {
// relation: Model.HasManyRelation,
// modelClass: SaleInvoice.default,
// join: {
// from: 'warehouses.id',
// to: 'sales_invoices.warehouseId',
// },
// },
// /**
// * Warehouse may belongs to associated sale estimates.
// */
// estimates: {
// relation: Model.HasManyRelation,
// modelClass: SaleEstimate.default,
// join: {
// from: 'warehouses.id',
// to: 'sales_estimates.warehouseId',
// },
// },
// /**
// * Warehouse may belongs to associated sale receipts.
// */
// receipts: {
// relation: Model.HasManyRelation,
// modelClass: SaleReceipt.default,
// join: {
// from: 'warehouses.id',
// to: 'sales_receipts.warehouseId',
// },
// },
// /**
// * Warehouse may belongs to associated bills.
// */
// bills: {
// relation: Model.HasManyRelation,
// modelClass: Bill.default,
// join: {
// from: 'warehouses.id',
// to: 'bills.warehouseId',
// },
// },
// /**
// * Warehouse may belongs to associated credit notes.
// */
// creditNotes: {
// relation: Model.HasManyRelation,
// modelClass: CreditNote.default,
// join: {
// from: 'warehouses.id',
// to: 'credit_notes.warehouseId',
// },
// },
// /**
// * Warehouse may belongs to associated to vendor credits.
// */
// vendorCredit: {
// relation: Model.HasManyRelation,
// modelClass: VendorCredit.default,
// join: {
// from: 'warehouses.id',
// to: 'vendor_credits.warehouseId',
// },
// },
// /**
// * Warehouse may belongs to associated to inventory transactions.
// */
// inventoryTransactions: {
// relation: Model.HasManyRelation,
// modelClass: InventoryTransaction.default,
// join: {
// from: 'warehouses.id',
// to: 'inventory_transactions.warehouseId',
// },
// },
// warehouseTransferTo: {
// relation: Model.HasManyRelation,
// modelClass: WarehouseTransfer.default,
// join: {
// from: 'warehouses.id',
// to: 'warehouses_transfers.toWarehouseId',
// },
// },
// warehouseTransferFrom: {
// relation: Model.HasManyRelation,
// modelClass: WarehouseTransfer.default,
// join: {
// from: 'warehouses.id',
// to: 'warehouses_transfers.fromWarehouseId',
// },
// },
// inventoryAdjustment: {
// relation: Model.HasManyRelation,
// modelClass: InventoryAdjustment.default,
// join: {
// from: 'warehouses.id',
// to: 'inventory_adjustments.warehouseId',
// },
// },
// };
// }
}

View File

@@ -0,0 +1,24 @@
import { Inject, Injectable } from '@nestjs/common';
import { Warehouse } from '../models/Warehouse.model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetWarehouse {
constructor(
@Inject(Warehouse.name)
private readonly warehouseModel: TenantModelProxy<typeof Warehouse>,
) {}
/**
* Retrieves warehouse details.
* @param {number} warehouseId
* @returns {Promise<IWarehouse>}
*/
public getWarehouse = async (warehouseId: number) => {
const warehouse = await this.warehouseModel()
.query()
.findById(warehouseId)
.throwIfNotFound();
return warehouse;
};
}

View File

@@ -0,0 +1,23 @@
import { Inject, Injectable } from '@nestjs/common';
import { Warehouse } from '../models/Warehouse.model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetWarehouses {
constructor(
@Inject(Warehouse.name)
private readonly warehouseModel: TenantModelProxy<typeof Warehouse>,
) {}
/**
* Retrieves warehouses list.
* @returns
*/
public getWarehouses = async () => {
const warehouses = await this.warehouseModel()
.query()
.orderBy('name', 'DESC');
return warehouses;
};
}

View File

@@ -0,0 +1,26 @@
import { BillActivateWarehouses } from '../../Activate/BillWarehousesActivate';
import { OnEvent } from '@nestjs/event-emitter';
import { Injectable } from '@nestjs/common';
import { events } from '@/common/events/events';
import { IWarehousesActivatedPayload } from '../../Warehouse.types';
@Injectable()
export class BillsActivateWarehousesSubscriber {
constructor(
private readonly billsActivateWarehouses: BillActivateWarehouses,
) {}
/**
* Updates all inventory transactions with the primary warehouse once
* multi-warehouses feature is activated.
* @param {IWarehousesActivatedPayload}
*/
@OnEvent(events.warehouse.onActivated)
async updateBillsWithWarehouseOnActivated ({
primaryWarehouse,
}: IWarehousesActivatedPayload) {
await this.billsActivateWarehouses.updateBillsWithWarehouse(
primaryWarehouse
);
};
}

View File

@@ -0,0 +1,26 @@
import { Injectable } from '@nestjs/common';
import { CreditNotesActivateWarehouses } from '../../Activate/CreditNoteWarehousesActivate';
import { OnEvent } from '@nestjs/event-emitter';
import { IWarehousesActivatedPayload } from '../../Warehouse.types';
import { events } from '@/common/events/events';
@Injectable()
export class CreditsActivateWarehousesSubscriber {
constructor(
private readonly creditsActivateWarehouses: CreditNotesActivateWarehouses,
) {}
/**
* Updates all inventory transactions with the primary warehouse once
* multi-warehouses feature is activated.
* @param {IWarehousesActivatedPayload}
*/
@OnEvent(events.warehouse.onActivated)
async updateInvoicesWithWarehouseOnActivated({
primaryWarehouse,
}: IWarehousesActivatedPayload) {
await this.creditsActivateWarehouses.updateCreditsWithWarehouse(
primaryWarehouse,
);
}
}

View File

@@ -0,0 +1,26 @@
import { EstimatesActivateWarehouses } from '../../Activate/EstimateWarehousesActivate';
import { OnEvent } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { IWarehousesActivatedPayload } from '../../Warehouse.types';
import { Injectable } from '@nestjs/common';
@Injectable()
export class EstimatesActivateWarehousesSubscriber {
constructor(
private readonly estimatesActivateWarehouses: EstimatesActivateWarehouses,
) {}
/**
* Updates all inventory transactions with the primary warehouse once
* multi-warehouses feature is activated.
* @param {IWarehousesActivatedPayload}
*/
@OnEvent(events.warehouse.onActivated)
async updateEstimatessWithWarehouseOnActivated({
primaryWarehouse,
}: IWarehousesActivatedPayload) {
await this.estimatesActivateWarehouses.updateEstimatesWithWarehouse(
primaryWarehouse,
);
}
}

View File

@@ -0,0 +1,26 @@
import { InventoryActivateWarehouses } from '../../Activate/InventoryTransactionsWarehousesActivate';
import { OnEvent } from '@nestjs/event-emitter';
import { Injectable } from '@nestjs/common';
import { events } from '@/common/events/events';
import { IWarehousesActivatedPayload } from '../../Warehouse.types';
@Injectable()
export class InventoryActivateWarehousesSubscriber {
constructor(
private readonly inventoryActivateWarehouses: InventoryActivateWarehouses,
) {}
/**
* Updates all inventory transactions with the primary warehouse once
* multi-warehouses feature is activated.
* @param {IWarehousesActivatedPayload}
*/
@OnEvent(events.warehouse.onActivated)
async updateInventoryTransactionsWithWarehouseOnActivated({
primaryWarehouse,
}: IWarehousesActivatedPayload) {
await this.inventoryActivateWarehouses.updateInventoryTransactionsWithWarehouse(
primaryWarehouse,
);
}
}

View File

@@ -0,0 +1,26 @@
import { Injectable } from '@nestjs/common';
import { InvoicesActivateWarehouses } from '../../Activate/InvoiceWarehousesActivate';
import { OnEvent } from '@nestjs/event-emitter';
import { IWarehousesActivatedPayload } from '../../Warehouse.types';
import { events } from '@/common/events/events';
@Injectable()
export class InvoicesActivateWarehousesSubscriber {
constructor(
private readonly invoicesActivateWarehouses: InvoicesActivateWarehouses,
) {}
/**
* Updates all inventory transactions with the primary warehouse once
* multi-warehouses feature is activated.
* @param {IWarehousesActivatedPayload}
*/
@OnEvent(events.warehouse.onActivated)
async updateInvoicesWithWarehouseOnActivated({
primaryWarehouse,
}: IWarehousesActivatedPayload) {
await this.invoicesActivateWarehouses.updateInvoicesWithWarehouse(
primaryWarehouse,
);
}
}

View File

@@ -0,0 +1,27 @@
import { OnEvent } from '@nestjs/event-emitter';
import { Injectable } from '@nestjs/common';
import { ReceiptActivateWarehouses } from '../../Activate/ReceiptWarehousesActivate';
import { events } from '@/common/events/events';
import { IWarehousesActivatedPayload } from '../../Warehouse.types';
@Injectable()
export class ReceiptsActivateWarehousesSubscriber {
constructor(
private readonly receiptsActivateWarehouses: ReceiptActivateWarehouses,
) {}
/**
* Updates all receipts transactions with the primary warehouse once
* multi-warehouses feature is activated.
* @param {IWarehousesActivatedPayload}
*/
@OnEvent(events.warehouse.onActivated)
async updateInventoryTransactionsWithWarehouseOnActivated({
primaryWarehouse,
}: IWarehousesActivatedPayload) {
await this.receiptsActivateWarehouses.updateReceiptsWithWarehouse(
primaryWarehouse
);
};
}

View File

@@ -0,0 +1,26 @@
import { VendorCreditActivateWarehouses } from '../../Activate/VendorCreditWarehousesActivate';
import { OnEvent } from '@nestjs/event-emitter';
import { Injectable } from '@nestjs/common';
import { IWarehousesActivatedPayload } from '../../Warehouse.types';
import { events } from '@/common/events/events';
@Injectable()
export class VendorCreditsActivateWarehousesSubscriber {
constructor(
private readonly creditsActivateWarehouses: VendorCreditActivateWarehouses,
) {}
/**
* Updates all inventory transactions with the primary warehouse once
* multi-warehouses feature is activated.
* @param {IWarehousesActivatedPayload}
*/
@OnEvent(events.warehouse.onActivated)
async updateCreditsWithWarehouseOnActivated({
primaryWarehouse,
}: IWarehousesActivatedPayload) {
await this.creditsActivateWarehouses.updateCreditsWithWarehouse(
primaryWarehouse,
);
}
}

View File

@@ -0,0 +1,27 @@
import { Injectable } from '@nestjs/common';
import { DeleteItemWarehousesQuantity } from '../commands/DeleteItemWarehousesQuantity';
import { OnEvent } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { IItemEventDeletingPayload } from '@/interfaces/Item';
@Injectable()
export class DeleteItemWarehousesQuantitySubscriber {
constructor(
private readonly deleteItemWarehousesQuantity: DeleteItemWarehousesQuantity,
) {}
/**
* Deletes the given item warehouses quantities once the item deleting.
* @param {IItemEventDeletingPayload} payload -
*/
@OnEvent(events.item.onDeleting)
async deleteItemWarehouseQuantitiesOnItemDelete({
oldItem,
trx,
}: IItemEventDeletingPayload) {
await this.deleteItemWarehousesQuantity.deleteItemWarehousesQuantity(
oldItem.id,
trx,
);
}
}

View File

@@ -0,0 +1,25 @@
import { WarehousesDTOValidators } from '../../../Integrations/WarehousesDTOValidators';
import { OnEvent } from '@nestjs/event-emitter';
import { Injectable } from '@nestjs/common';
import { IInventoryAdjustmentCreatingPayload } from '@/modules/InventoryAdjutments/types/InventoryAdjustments.types';
import { events } from '@/common/events/events';
@Injectable()
export class InventoryAdjustmentWarehouseValidatorSubscriber {
constructor(
private readonly warehouseDTOValidator: WarehousesDTOValidators,
) {}
/**
* Validate warehouse existance of sale invoice once creating.
* @param {IBillCreatingPayload}
*/
@OnEvent(events.inventoryAdjustment.onQuickCreating)
async validateAdjustmentWarehouseExistanceOnCreating({
quickAdjustmentDTO,
}: IInventoryAdjustmentCreatingPayload) {
await this.warehouseDTOValidator.validateDTOWarehouseWhenActive(
quickAdjustmentDTO,
);
}
}

View File

@@ -0,0 +1,36 @@
import { OnEvent } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { WarehousesDTOValidators } from '../../../Integrations/WarehousesDTOValidators';
import { IBillEditingPayload } from '@/modules/Bills/Bills.types';
import { Injectable } from '@nestjs/common';
import { IBillCreatingPayload } from '@/modules/Bills/Bills.types';
@Injectable()
export class BillWarehousesValidateSubscriber {
constructor(
private readonly warehouseDTOValidator: WarehousesDTOValidators,
) {}
/**
* Validate warehouse existance of sale invoice once creating.
* @param {IBillCreatingPayload}
*/
@OnEvent(events.bill.onCreating)
async validateBillWarehouseExistanceOnCreating({
billDTO,
}: IBillCreatingPayload) {
await this.warehouseDTOValidator.validateDTOWarehouseWhenActive(billDTO);
}
/**
* Validate warehouse existance of sale invoice once editing.
* @param {IBillEditingPayload}
*/
@OnEvent(events.bill.onEditing)
async validateSaleEstimateWarehouseExistanceOnEditing({
billDTO,
}: IBillEditingPayload) {
await this.warehouseDTOValidator.validateDTOWarehouseWhenActive(billDTO);
}
}

View File

@@ -0,0 +1,39 @@
import { WarehousesDTOValidators } from '../../../Integrations/WarehousesDTOValidators';
import { OnEvent } from '@nestjs/event-emitter';
import { Injectable } from '@nestjs/common';
import { IVendorCreditEditingPayload } from '@/modules/VendorCredit/types/VendorCredit.types';
import { events } from '@/common/events/events';
import { IVendorCreditCreatingPayload } from '@/modules/VendorCredit/types/VendorCredit.types';
@Injectable()
export class VendorCreditWarehousesValidateSubscriber {
constructor(
private readonly warehouseDTOValidator: WarehousesDTOValidators,
) {}
/**
* Validate warehouse existance of sale invoice once creating.
* @param {IVendorCreditCreatingPayload}
*/
@OnEvent(events.vendorCredit.onCreating)
async validateVendorCreditWarehouseExistanceOnCreating({
vendorCreditCreateDTO,
}: IVendorCreditCreatingPayload) {
await this.warehouseDTOValidator.validateDTOWarehouseWhenActive(
vendorCreditCreateDTO,
);
}
/**
* Validate warehouse existance of sale invoice once editing.
* @param {IVendorCreditEditingPayload}
*/
@OnEvent(events.vendorCredit.onEditing)
async validateSaleEstimateWarehouseExistanceOnEditing({
vendorCreditDTO,
}: IVendorCreditEditingPayload) {
await this.warehouseDTOValidator.validateDTOWarehouseWhenActive(
vendorCreditDTO,
);
}
}

View File

@@ -0,0 +1,41 @@
import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { WarehousesDTOValidators } from '../../../Integrations/WarehousesDTOValidators';
import {
ICreditNoteEditingPayload,
ICreditNoteCreatingPayload,
} from '@/modules/CreditNotes/types/CreditNotes.types';
import { events } from '@/common/events/events';
@Injectable()
export class CreditNoteWarehousesValidateSubscriber {
constructor(
private readonly warehouseDTOValidator: WarehousesDTOValidators,
) {}
/**
* Validate warehouse existance of sale invoice once creating.
* @param {ICreditNoteCreatingPayload}
*/
@OnEvent(events.creditNote.onCreating)
async validateCreditNoteWarehouseExistanceOnCreating({
creditNoteDTO,
}: ICreditNoteCreatingPayload) {
await this.warehouseDTOValidator.validateDTOWarehouseWhenActive(
creditNoteDTO,
);
}
/**
* Validate warehouse existance of sale invoice once editing.
* @param {ICreditNoteEditingPayload}
*/
@OnEvent(events.creditNote.onEditing)
async validateCreditNoteWarehouseExistanceOnEditing({
creditNoteEditDTO,
}: ICreditNoteEditingPayload) {
await this.warehouseDTOValidator.validateDTOWarehouseWhenActive(
creditNoteEditDTO,
);
}
}

View File

@@ -0,0 +1,39 @@
import { WarehousesDTOValidators } from '../../../Integrations/WarehousesDTOValidators';
import { OnEvent } from '@nestjs/event-emitter';
import { Injectable } from '@nestjs/common';
import { events } from '@/common/events/events';
import { ISaleEstimateEditingPayload } from '@/modules/SaleEstimates/types/SaleEstimates.types';
import { ISaleEstimateCreatingPayload } from '@/modules/SaleEstimates/types/SaleEstimates.types';
@Injectable()
export class SaleEstimateWarehousesValidateSubscriber {
constructor(
private readonly warehouseDTOValidator: WarehousesDTOValidators,
) {}
/**
* Validate warehouse existance of sale invoice once creating.
* @param {ISaleEstimateCreatingPayload}
*/
@OnEvent(events.saleEstimate.onCreating)
async validateSaleEstimateWarehouseExistanceOnCreating({
estimateDTO,
}: ISaleEstimateCreatingPayload) {
await this.warehouseDTOValidator.validateDTOWarehouseWhenActive(
estimateDTO,
);
}
/**
* Validate warehouse existance of sale invoice once editing.
* @param {ISaleEstimateEditingPayload}
*/
@OnEvent(events.saleEstimate.onEditing)
async validateSaleEstimateWarehouseExistanceOnEditing({
estimateDTO,
}: ISaleEstimateEditingPayload) {
await this.warehouseDTOValidator.validateDTOWarehouseWhenActive(
estimateDTO,
);
}
}

View File

@@ -0,0 +1,42 @@
import { WarehousesDTOValidators } from '../../../Integrations/WarehousesDTOValidators';
import { OnEvent } from '@nestjs/event-emitter';
import { Injectable } from '@nestjs/common';
import {
ISaleInvoiceCreatingPaylaod,
ISaleInvoiceEditingPayload,
} from '@/modules/SaleInvoices/SaleInvoice.types';
import { events } from '@/common/events/events';
@Injectable()
export class SaleInvoicesWarehousesValidateSubscriber {
constructor(
private readonly warehousesDTOValidator: WarehousesDTOValidators,
) {}
/**
/**
* Validate warehouse existance of sale invoice once creating.
* @param {ISaleInvoiceCreatingPaylaod}
*/
@OnEvent(events.saleInvoice.onCreating)
async validateSaleInvoiceWarehouseExistanceOnCreating({
saleInvoiceDTO,
}: ISaleInvoiceCreatingPaylaod) {
await this.warehousesDTOValidator.validateDTOWarehouseWhenActive(
saleInvoiceDTO,
);
}
/**
* Validate warehouse existance of sale invoice once editing.
* @param {ISaleInvoiceEditingPayload}
*/
@OnEvent(events.saleInvoice.onEditing)
async validateSaleInvoiceWarehouseExistanceOnEditing({
saleInvoiceDTO,
}: ISaleInvoiceEditingPayload) {
await this.warehousesDTOValidator.validateDTOWarehouseWhenActive(
saleInvoiceDTO,
);
}
}

View File

@@ -0,0 +1,41 @@
import {
ISaleReceiptCreatingPayload,
ISaleReceiptEditingPayload,
} from '@/modules/SaleReceipts/types/SaleReceipts.types';
import { events } from '@/common/events/events';
import { WarehousesDTOValidators } from '../../../Integrations/WarehousesDTOValidators';
import { OnEvent } from '@nestjs/event-emitter';
import { Injectable } from '@nestjs/common';
@Injectable()
export class SaleReceiptWarehousesValidateSubscriber {
constructor(
private readonly warehousesDTOValidator: WarehousesDTOValidators,
) {}
/**
* Validate warehouse existance of sale invoice once creating.
* @param {ISaleReceiptCreatingPayload}
*/
@OnEvent(events.saleReceipt.onCreating)
async validateSaleReceiptWarehouseExistanceOnCreating({
saleReceiptDTO,
}: ISaleReceiptCreatingPayload) {
await this.warehousesDTOValidator.validateDTOWarehouseWhenActive(
saleReceiptDTO,
);
}
/**
* Validate warehouse existance of sale invoice once editing.
* @param {ISaleReceiptEditingPayload}
*/
@OnEvent(events.saleReceipt.onEditing)
async validateSaleReceiptWarehouseExistanceOnEditing({
saleReceiptDTO,
}: ISaleReceiptEditingPayload) {
await this.warehousesDTOValidator.validateDTOWarehouseWhenActive(
saleReceiptDTO,
);
}
}