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,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',
};