mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 15:20:34 +00:00
add server to monorepo.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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',
|
||||
};
|
||||
Reference in New Issue
Block a user