feat(inventory): Add AlmacenProyectoService for project warehouse management
Implements missing service for project-specific warehouse assignments: - CRUD operations for project warehouse assignments - Stock queries by project (fraccionamiento) - Transfer between project warehouses with transaction support - Project inventory summary with stock value calculations - Integration with ERP Core inventory tables Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3b4bb3d80e
commit
6a64edf4c8
650
src/modules/inventory/services/almacen-proyecto.service.ts
Normal file
650
src/modules/inventory/services/almacen-proyecto.service.ts
Normal file
@ -0,0 +1,650 @@
|
||||
/**
|
||||
* AlmacenProyectoService - Project Warehouse Management Service
|
||||
*
|
||||
* Manages project-specific warehouse assignments, stock queries by project,
|
||||
* transfers between project warehouses, and project inventory summaries.
|
||||
*
|
||||
* @module Inventory
|
||||
*/
|
||||
|
||||
import { Repository, FindOptionsWhere, DataSource } from 'typeorm';
|
||||
import { AlmacenProyecto, WarehouseTypeConstruction } from '../entities/almacen-proyecto.entity';
|
||||
|
||||
interface ServiceContext {
|
||||
tenantId: string;
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
interface PaginatedResult<T> {
|
||||
data: T[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export interface CreateAlmacenProyectoDto {
|
||||
warehouseId: string;
|
||||
fraccionamientoId: string;
|
||||
warehouseType?: WarehouseTypeConstruction;
|
||||
locationDescription?: string;
|
||||
responsibleId?: string;
|
||||
}
|
||||
|
||||
export interface UpdateAlmacenProyectoDto {
|
||||
warehouseType?: WarehouseTypeConstruction;
|
||||
locationDescription?: string;
|
||||
responsibleId?: string;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export interface AlmacenProyectoFilters {
|
||||
fraccionamientoId?: string;
|
||||
warehouseType?: WarehouseTypeConstruction;
|
||||
responsibleId?: string;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export interface TransferRequest {
|
||||
sourceWarehouseId: string;
|
||||
destinationWarehouseId: string;
|
||||
productId: string;
|
||||
quantity: number;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface TransferResult {
|
||||
transferId: string;
|
||||
sourceWarehouseId: string;
|
||||
destinationWarehouseId: string;
|
||||
productId: string;
|
||||
quantity: number;
|
||||
transferredAt: Date;
|
||||
transferredById: string;
|
||||
}
|
||||
|
||||
export interface StockByProject {
|
||||
productId: string;
|
||||
productName?: string;
|
||||
warehouseId: string;
|
||||
warehouseName?: string;
|
||||
quantity: number;
|
||||
reservedQuantity: number;
|
||||
availableQuantity: number;
|
||||
}
|
||||
|
||||
export interface ProjectInventorySummary {
|
||||
fraccionamientoId: string;
|
||||
totalWarehouses: number;
|
||||
activeWarehouses: number;
|
||||
warehousesByType: { type: WarehouseTypeConstruction; count: number }[];
|
||||
totalProducts: number;
|
||||
totalStockValue: number;
|
||||
}
|
||||
|
||||
export class AlmacenProyectoService {
|
||||
constructor(
|
||||
private readonly repository: Repository<AlmacenProyecto>,
|
||||
private readonly dataSource?: DataSource
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Find project warehouses with filters and pagination
|
||||
*/
|
||||
async findWithFilters(
|
||||
ctx: ServiceContext,
|
||||
filters: AlmacenProyectoFilters = {},
|
||||
page: number = 1,
|
||||
limit: number = 20
|
||||
): Promise<PaginatedResult<AlmacenProyecto>> {
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const queryBuilder = this.repository
|
||||
.createQueryBuilder('ap')
|
||||
.leftJoinAndSelect('ap.fraccionamiento', 'fraccionamiento')
|
||||
.leftJoinAndSelect('ap.responsible', 'responsible')
|
||||
.where('ap.tenant_id = :tenantId', { tenantId: ctx.tenantId })
|
||||
.andWhere('ap.deleted_at IS NULL');
|
||||
|
||||
if (filters.fraccionamientoId) {
|
||||
queryBuilder.andWhere('ap.fraccionamiento_id = :fraccionamientoId', {
|
||||
fraccionamientoId: filters.fraccionamientoId,
|
||||
});
|
||||
}
|
||||
|
||||
if (filters.warehouseType) {
|
||||
queryBuilder.andWhere('ap.warehouse_type = :warehouseType', {
|
||||
warehouseType: filters.warehouseType,
|
||||
});
|
||||
}
|
||||
|
||||
if (filters.responsibleId) {
|
||||
queryBuilder.andWhere('ap.responsible_id = :responsibleId', {
|
||||
responsibleId: filters.responsibleId,
|
||||
});
|
||||
}
|
||||
|
||||
if (filters.isActive !== undefined) {
|
||||
queryBuilder.andWhere('ap.is_active = :isActive', {
|
||||
isActive: filters.isActive,
|
||||
});
|
||||
}
|
||||
|
||||
queryBuilder
|
||||
.orderBy('ap.created_at', 'DESC')
|
||||
.skip(skip)
|
||||
.take(limit);
|
||||
|
||||
const [data, total] = await queryBuilder.getManyAndCount();
|
||||
|
||||
return {
|
||||
data,
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a project warehouse by ID
|
||||
*/
|
||||
async findById(ctx: ServiceContext, id: string): Promise<AlmacenProyecto | null> {
|
||||
return this.repository.findOne({
|
||||
where: {
|
||||
id,
|
||||
tenantId: ctx.tenantId,
|
||||
deletedAt: null,
|
||||
} as unknown as FindOptionsWhere<AlmacenProyecto>,
|
||||
relations: ['fraccionamiento', 'responsible', 'createdBy'],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a project warehouse by warehouse ID
|
||||
*/
|
||||
async findByWarehouseId(ctx: ServiceContext, warehouseId: string): Promise<AlmacenProyecto | null> {
|
||||
return this.repository.findOne({
|
||||
where: {
|
||||
warehouseId,
|
||||
tenantId: ctx.tenantId,
|
||||
deletedAt: null,
|
||||
} as unknown as FindOptionsWhere<AlmacenProyecto>,
|
||||
relations: ['fraccionamiento', 'responsible'],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all warehouses for a specific project (fraccionamiento)
|
||||
*/
|
||||
async findByProject(ctx: ServiceContext, fraccionamientoId: string): Promise<AlmacenProyecto[]> {
|
||||
return this.repository.find({
|
||||
where: {
|
||||
fraccionamientoId,
|
||||
tenantId: ctx.tenantId,
|
||||
deletedAt: null,
|
||||
} as unknown as FindOptionsWhere<AlmacenProyecto>,
|
||||
relations: ['responsible'],
|
||||
order: { createdAt: 'ASC' },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find active warehouses by type for a project
|
||||
*/
|
||||
async findByProjectAndType(
|
||||
ctx: ServiceContext,
|
||||
fraccionamientoId: string,
|
||||
warehouseType: WarehouseTypeConstruction
|
||||
): Promise<AlmacenProyecto[]> {
|
||||
return this.repository.find({
|
||||
where: {
|
||||
fraccionamientoId,
|
||||
warehouseType,
|
||||
isActive: true,
|
||||
tenantId: ctx.tenantId,
|
||||
deletedAt: null,
|
||||
} as unknown as FindOptionsWhere<AlmacenProyecto>,
|
||||
relations: ['responsible'],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new project warehouse assignment
|
||||
*/
|
||||
async create(ctx: ServiceContext, dto: CreateAlmacenProyectoDto): Promise<AlmacenProyecto> {
|
||||
// Check if warehouse is already assigned to a project
|
||||
const existing = await this.findByWarehouseId(ctx, dto.warehouseId);
|
||||
if (existing) {
|
||||
throw new Error('Warehouse is already assigned to a project');
|
||||
}
|
||||
|
||||
const almacenProyecto = this.repository.create({
|
||||
tenantId: ctx.tenantId,
|
||||
createdById: ctx.userId,
|
||||
warehouseId: dto.warehouseId,
|
||||
fraccionamientoId: dto.fraccionamientoId,
|
||||
warehouseType: dto.warehouseType || 'obra',
|
||||
locationDescription: dto.locationDescription,
|
||||
responsibleId: dto.responsibleId,
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
return this.repository.save(almacenProyecto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a project warehouse
|
||||
*/
|
||||
async update(
|
||||
ctx: ServiceContext,
|
||||
id: string,
|
||||
dto: UpdateAlmacenProyectoDto
|
||||
): Promise<AlmacenProyecto | null> {
|
||||
const almacenProyecto = await this.findById(ctx, id);
|
||||
if (!almacenProyecto) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (dto.warehouseType !== undefined) {
|
||||
almacenProyecto.warehouseType = dto.warehouseType;
|
||||
}
|
||||
if (dto.locationDescription !== undefined) {
|
||||
almacenProyecto.locationDescription = dto.locationDescription;
|
||||
}
|
||||
if (dto.responsibleId !== undefined) {
|
||||
almacenProyecto.responsibleId = dto.responsibleId;
|
||||
}
|
||||
if (dto.isActive !== undefined) {
|
||||
almacenProyecto.isActive = dto.isActive;
|
||||
}
|
||||
|
||||
if (ctx.userId) {
|
||||
almacenProyecto.updatedById = ctx.userId;
|
||||
}
|
||||
|
||||
return this.repository.save(almacenProyecto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate a project warehouse
|
||||
*/
|
||||
async activate(ctx: ServiceContext, id: string): Promise<AlmacenProyecto | null> {
|
||||
return this.update(ctx, id, { isActive: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate a project warehouse
|
||||
*/
|
||||
async deactivate(ctx: ServiceContext, id: string): Promise<AlmacenProyecto | null> {
|
||||
return this.update(ctx, id, { isActive: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stock by project (all warehouses)
|
||||
* Note: This queries against ERP Core inventory tables
|
||||
*/
|
||||
async getStockByProject(
|
||||
ctx: ServiceContext,
|
||||
fraccionamientoId: string
|
||||
): Promise<StockByProject[]> {
|
||||
if (!this.dataSource) {
|
||||
throw new Error('DataSource is required for stock queries');
|
||||
}
|
||||
|
||||
// Get all warehouse IDs for this project
|
||||
const projectWarehouses = await this.findByProject(ctx, fraccionamientoId);
|
||||
if (projectWarehouses.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const warehouseIds = projectWarehouses.map((w) => w.warehouseId);
|
||||
|
||||
// Query stock from ERP Core inventory.stock table
|
||||
const stockQuery = `
|
||||
SELECT
|
||||
s.product_id as "productId",
|
||||
p.name as "productName",
|
||||
s.warehouse_id as "warehouseId",
|
||||
w.name as "warehouseName",
|
||||
COALESCE(s.quantity, 0) as quantity,
|
||||
COALESCE(s.reserved_quantity, 0) as "reservedQuantity",
|
||||
COALESCE(s.quantity, 0) - COALESCE(s.reserved_quantity, 0) as "availableQuantity"
|
||||
FROM inventory.stock s
|
||||
LEFT JOIN inventory.products p ON s.product_id = p.id
|
||||
LEFT JOIN inventory.warehouses w ON s.warehouse_id = w.id
|
||||
WHERE s.tenant_id = $1
|
||||
AND s.warehouse_id = ANY($2::uuid[])
|
||||
ORDER BY p.name, w.name
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = await this.dataSource.query(stockQuery, [ctx.tenantId, warehouseIds]);
|
||||
return result.map((row: Record<string, unknown>) => ({
|
||||
productId: row.productId as string,
|
||||
productName: row.productName as string | undefined,
|
||||
warehouseId: row.warehouseId as string,
|
||||
warehouseName: row.warehouseName as string | undefined,
|
||||
quantity: parseFloat(String(row.quantity) || '0'),
|
||||
reservedQuantity: parseFloat(String(row.reservedQuantity) || '0'),
|
||||
availableQuantity: parseFloat(String(row.availableQuantity) || '0'),
|
||||
}));
|
||||
} catch {
|
||||
// If inventory.stock table doesn't exist (ERP Core not fully set up),
|
||||
// return empty array
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stock for a specific product across all project warehouses
|
||||
*/
|
||||
async getProductStockByProject(
|
||||
ctx: ServiceContext,
|
||||
fraccionamientoId: string,
|
||||
productId: string
|
||||
): Promise<StockByProject[]> {
|
||||
const allStock = await this.getStockByProject(ctx, fraccionamientoId);
|
||||
return allStock.filter((s) => s.productId === productId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer stock between project warehouses
|
||||
* Note: This creates movement records in ERP Core inventory
|
||||
*/
|
||||
async transferBetweenWarehouses(
|
||||
ctx: ServiceContext,
|
||||
request: TransferRequest
|
||||
): Promise<TransferResult> {
|
||||
if (!ctx.userId) {
|
||||
throw new Error('User ID is required for transfer operations');
|
||||
}
|
||||
|
||||
if (!this.dataSource) {
|
||||
throw new Error('DataSource is required for transfer operations');
|
||||
}
|
||||
|
||||
// Validate source warehouse belongs to a project
|
||||
const sourceWarehouse = await this.findByWarehouseId(ctx, request.sourceWarehouseId);
|
||||
if (!sourceWarehouse) {
|
||||
throw new Error('Source warehouse is not assigned to any project');
|
||||
}
|
||||
|
||||
// Validate destination warehouse belongs to a project
|
||||
const destWarehouse = await this.findByWarehouseId(ctx, request.destinationWarehouseId);
|
||||
if (!destWarehouse) {
|
||||
throw new Error('Destination warehouse is not assigned to any project');
|
||||
}
|
||||
|
||||
// Validate quantity
|
||||
if (request.quantity <= 0) {
|
||||
throw new Error('Transfer quantity must be positive');
|
||||
}
|
||||
|
||||
// Check available stock in source warehouse
|
||||
const sourceStock = await this.getWarehouseProductStock(
|
||||
ctx,
|
||||
request.sourceWarehouseId,
|
||||
request.productId
|
||||
);
|
||||
if (sourceStock.availableQuantity < request.quantity) {
|
||||
throw new Error(
|
||||
`Insufficient stock. Available: ${sourceStock.availableQuantity}, Requested: ${request.quantity}`
|
||||
);
|
||||
}
|
||||
|
||||
// Create transfer movement in ERP Core
|
||||
const transferId = await this.createTransferMovement(ctx, request);
|
||||
|
||||
return {
|
||||
transferId,
|
||||
sourceWarehouseId: request.sourceWarehouseId,
|
||||
destinationWarehouseId: request.destinationWarehouseId,
|
||||
productId: request.productId,
|
||||
quantity: request.quantity,
|
||||
transferredAt: new Date(),
|
||||
transferredById: ctx.userId,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stock for a specific product in a warehouse
|
||||
*/
|
||||
private async getWarehouseProductStock(
|
||||
ctx: ServiceContext,
|
||||
warehouseId: string,
|
||||
productId: string
|
||||
): Promise<{ quantity: number; reservedQuantity: number; availableQuantity: number }> {
|
||||
if (!this.dataSource) {
|
||||
return { quantity: 0, reservedQuantity: 0, availableQuantity: 0 };
|
||||
}
|
||||
|
||||
const query = `
|
||||
SELECT
|
||||
COALESCE(quantity, 0) as quantity,
|
||||
COALESCE(reserved_quantity, 0) as "reservedQuantity"
|
||||
FROM inventory.stock
|
||||
WHERE tenant_id = $1
|
||||
AND warehouse_id = $2
|
||||
AND product_id = $3
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = await this.dataSource.query(query, [ctx.tenantId, warehouseId, productId]);
|
||||
if (result.length === 0) {
|
||||
return { quantity: 0, reservedQuantity: 0, availableQuantity: 0 };
|
||||
}
|
||||
const row = result[0];
|
||||
const quantity = parseFloat(row.quantity || '0');
|
||||
const reservedQuantity = parseFloat(row.reservedQuantity || '0');
|
||||
return {
|
||||
quantity,
|
||||
reservedQuantity,
|
||||
availableQuantity: quantity - reservedQuantity,
|
||||
};
|
||||
} catch {
|
||||
return { quantity: 0, reservedQuantity: 0, availableQuantity: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a transfer movement between warehouses
|
||||
*/
|
||||
private async createTransferMovement(
|
||||
ctx: ServiceContext,
|
||||
request: TransferRequest
|
||||
): Promise<string> {
|
||||
if (!this.dataSource) {
|
||||
throw new Error('DataSource is required');
|
||||
}
|
||||
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
// Generate transfer ID
|
||||
const transferId = this.generateUUID();
|
||||
const now = new Date();
|
||||
|
||||
// Create outbound movement from source
|
||||
await queryRunner.query(
|
||||
`
|
||||
INSERT INTO inventory.stock_moves (
|
||||
id, tenant_id, product_id, warehouse_id,
|
||||
quantity, movement_type, reference_type, reference_id,
|
||||
notes, created_at, created_by
|
||||
) VALUES ($1, $2, $3, $4, $5, 'out', 'transfer', $6, $7, $8, $9)
|
||||
`,
|
||||
[
|
||||
this.generateUUID(),
|
||||
ctx.tenantId,
|
||||
request.productId,
|
||||
request.sourceWarehouseId,
|
||||
-request.quantity,
|
||||
transferId,
|
||||
request.notes || 'Transfer between project warehouses',
|
||||
now,
|
||||
ctx.userId,
|
||||
]
|
||||
);
|
||||
|
||||
// Create inbound movement to destination
|
||||
await queryRunner.query(
|
||||
`
|
||||
INSERT INTO inventory.stock_moves (
|
||||
id, tenant_id, product_id, warehouse_id,
|
||||
quantity, movement_type, reference_type, reference_id,
|
||||
notes, created_at, created_by
|
||||
) VALUES ($1, $2, $3, $4, $5, 'in', 'transfer', $6, $7, $8, $9)
|
||||
`,
|
||||
[
|
||||
this.generateUUID(),
|
||||
ctx.tenantId,
|
||||
request.productId,
|
||||
request.destinationWarehouseId,
|
||||
request.quantity,
|
||||
transferId,
|
||||
request.notes || 'Transfer between project warehouses',
|
||||
now,
|
||||
ctx.userId,
|
||||
]
|
||||
);
|
||||
|
||||
// Update stock in source warehouse (decrease)
|
||||
await queryRunner.query(
|
||||
`
|
||||
UPDATE inventory.stock
|
||||
SET quantity = quantity - $1, updated_at = $2
|
||||
WHERE tenant_id = $3 AND warehouse_id = $4 AND product_id = $5
|
||||
`,
|
||||
[request.quantity, now, ctx.tenantId, request.sourceWarehouseId, request.productId]
|
||||
);
|
||||
|
||||
// Update or insert stock in destination warehouse (increase)
|
||||
await queryRunner.query(
|
||||
`
|
||||
INSERT INTO inventory.stock (id, tenant_id, warehouse_id, product_id, quantity, created_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
ON CONFLICT (tenant_id, warehouse_id, product_id)
|
||||
DO UPDATE SET quantity = inventory.stock.quantity + $5, updated_at = $6
|
||||
`,
|
||||
[
|
||||
this.generateUUID(),
|
||||
ctx.tenantId,
|
||||
request.destinationWarehouseId,
|
||||
request.productId,
|
||||
request.quantity,
|
||||
now,
|
||||
]
|
||||
);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
return transferId;
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get project inventory summary
|
||||
*/
|
||||
async getProjectInventorySummary(
|
||||
ctx: ServiceContext,
|
||||
fraccionamientoId: string
|
||||
): Promise<ProjectInventorySummary> {
|
||||
// Get warehouses for the project
|
||||
const warehouses = await this.findByProject(ctx, fraccionamientoId);
|
||||
const activeWarehouses = warehouses.filter((w) => w.isActive);
|
||||
|
||||
// Count by type
|
||||
const typeCount: Record<WarehouseTypeConstruction, number> = {
|
||||
central: 0,
|
||||
obra: 0,
|
||||
temporal: 0,
|
||||
transito: 0,
|
||||
};
|
||||
|
||||
for (const wh of activeWarehouses) {
|
||||
typeCount[wh.warehouseType]++;
|
||||
}
|
||||
|
||||
const warehousesByType = Object.entries(typeCount)
|
||||
.filter(([, count]) => count > 0)
|
||||
.map(([type, count]) => ({
|
||||
type: type as WarehouseTypeConstruction,
|
||||
count,
|
||||
}));
|
||||
|
||||
// Get stock summary if dataSource available
|
||||
let totalProducts = 0;
|
||||
let totalStockValue = 0;
|
||||
|
||||
if (this.dataSource && activeWarehouses.length > 0) {
|
||||
const warehouseIds = activeWarehouses.map((w) => w.warehouseId);
|
||||
|
||||
try {
|
||||
const stockSummary = await this.dataSource.query(
|
||||
`
|
||||
SELECT
|
||||
COUNT(DISTINCT s.product_id) as "totalProducts",
|
||||
COALESCE(SUM(s.quantity * COALESCE(p.cost, 0)), 0) as "totalValue"
|
||||
FROM inventory.stock s
|
||||
LEFT JOIN inventory.products p ON s.product_id = p.id
|
||||
WHERE s.tenant_id = $1
|
||||
AND s.warehouse_id = ANY($2::uuid[])
|
||||
AND s.quantity > 0
|
||||
`,
|
||||
[ctx.tenantId, warehouseIds]
|
||||
);
|
||||
|
||||
if (stockSummary.length > 0) {
|
||||
totalProducts = parseInt(stockSummary[0].totalProducts || '0', 10);
|
||||
totalStockValue = parseFloat(stockSummary[0].totalValue || '0');
|
||||
}
|
||||
} catch {
|
||||
// ERP Core tables may not exist
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
fraccionamientoId,
|
||||
totalWarehouses: warehouses.length,
|
||||
activeWarehouses: activeWarehouses.length,
|
||||
warehousesByType,
|
||||
totalProducts,
|
||||
totalStockValue,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Soft delete a project warehouse assignment
|
||||
*/
|
||||
async softDelete(ctx: ServiceContext, id: string): Promise<boolean> {
|
||||
const almacenProyecto = await this.findById(ctx, id);
|
||||
if (!almacenProyecto) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.repository.update(
|
||||
{ id, tenantId: ctx.tenantId } as unknown as FindOptionsWhere<AlmacenProyecto>,
|
||||
{ deletedAt: new Date(), deletedById: ctx.userId }
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a UUID v4
|
||||
*/
|
||||
private generateUUID(): string {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||
const r = (Math.random() * 16) | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -3,5 +3,6 @@
|
||||
* @module Inventory
|
||||
*/
|
||||
|
||||
export * from './almacen-proyecto.service';
|
||||
export * from './requisicion.service';
|
||||
export * from './consumo-obra.service';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user