[MAE-015] fix: Fix TypeScript errors in assets module

- Fix controllers to use Promise<void> return types
- Replace 'return res.status()' with 'res.status(); return;'
- Add NextFunction parameter to handlers
- Fix enum-style references with string literals
- Replace 'deletedAt: null' with 'IsNull()' for TypeORM
- Remove unused parameters and imports
- Fix grouped object initialization in getByStatus
- Remove non-existent 'updatedBy' property from WorkOrderPart

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Adrian Flores Cortes 2026-01-25 09:51:01 -06:00
parent e441e9c626
commit f14829d2ce
7 changed files with 182 additions and 145 deletions

View File

@ -6,7 +6,7 @@
* @module Assets (MAE-015) * @module Assets (MAE-015)
*/ */
import { Router, Request, Response } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import { AssetService } from '../services'; import { AssetService } from '../services';
@ -20,10 +20,9 @@ export function createAssetController(dataSource: DataSource): Router {
* GET / * GET /
* Lista activos con filtros y paginación * Lista activos con filtros y paginación
*/ */
router.get('/', async (req: Request, res: Response) => { router.get('/', async (req: Request, res: Response, _next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id;
const filters = { const filters = {
assetType: req.query.assetType as any, assetType: req.query.assetType as any,
@ -81,20 +80,21 @@ export function createAssetController(dataSource: DataSource): Router {
* GET /search * GET /search
* Búsqueda de activos para autocomplete * Búsqueda de activos para autocomplete
*/ */
router.get('/search', async (req: Request, res: Response) => { router.get('/search', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const query = req.query.q as string; const query = req.query.q as string;
const limit = parseInt(req.query.limit as string) || 10; const limit = parseInt(req.query.limit as string) || 10;
if (!query) { if (!query) {
return res.status(400).json({ error: 'Se requiere parámetro de búsqueda (q)' }); res.status(400).json({ error: 'Se requiere parámetro de búsqueda (q)' });
return;
} }
const assets = await service.search(tenantId, query, limit); const assets = await service.search(tenantId, query, limit);
res.json(assets); res.json(assets);
} catch (error) { } catch (error) {
res.status(500).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -102,18 +102,19 @@ export function createAssetController(dataSource: DataSource): Router {
* GET /by-code/:code * GET /by-code/:code
* Obtiene un activo por código * Obtiene un activo por código
*/ */
router.get('/by-code/:code', async (req: Request, res: Response) => { router.get('/by-code/:code', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const asset = await service.findByCode(tenantId, req.params.code); const asset = await service.findByCode(tenantId, req.params.code);
if (!asset) { if (!asset) {
return res.status(404).json({ error: 'Activo no encontrado' }); res.status(404).json({ error: 'Activo no encontrado' });
return;
} }
res.json(asset); res.json(asset);
} catch (error) { } catch (error) {
res.status(500).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -121,18 +122,19 @@ export function createAssetController(dataSource: DataSource): Router {
* GET /:id * GET /:id
* Obtiene un activo por ID * Obtiene un activo por ID
*/ */
router.get('/:id', async (req: Request, res: Response) => { router.get('/:id', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const asset = await service.findById(tenantId, req.params.id); const asset = await service.findById(tenantId, req.params.id);
if (!asset) { if (!asset) {
return res.status(404).json({ error: 'Activo no encontrado' }); res.status(404).json({ error: 'Activo no encontrado' });
return;
} }
res.json(asset); res.json(asset);
} catch (error) { } catch (error) {
res.status(500).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -156,19 +158,20 @@ export function createAssetController(dataSource: DataSource): Router {
* PUT /:id * PUT /:id
* Actualiza un activo * Actualiza un activo
*/ */
router.put('/:id', async (req: Request, res: Response) => { router.put('/:id', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id; const userId = (req as any).user?.id;
const asset = await service.update(tenantId, req.params.id, req.body, userId); const asset = await service.update(tenantId, req.params.id, req.body, userId);
if (!asset) { if (!asset) {
return res.status(404).json({ error: 'Activo no encontrado' }); res.status(404).json({ error: 'Activo no encontrado' });
return;
} }
res.json(asset); res.json(asset);
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -176,24 +179,26 @@ export function createAssetController(dataSource: DataSource): Router {
* PATCH /:id/status * PATCH /:id/status
* Actualiza el estado de un activo * Actualiza el estado de un activo
*/ */
router.patch('/:id/status', async (req: Request, res: Response) => { router.patch('/:id/status', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id; const userId = (req as any).user?.id;
const { status } = req.body; const { status } = req.body;
if (!status) { if (!status) {
return res.status(400).json({ error: 'Se requiere el nuevo estado' }); res.status(400).json({ error: 'Se requiere el nuevo estado' });
return;
} }
const asset = await service.updateStatus(tenantId, req.params.id, status, userId); const asset = await service.updateStatus(tenantId, req.params.id, status, userId);
if (!asset) { if (!asset) {
return res.status(404).json({ error: 'Activo no encontrado' }); res.status(404).json({ error: 'Activo no encontrado' });
return;
} }
res.json(asset); res.json(asset);
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -201,7 +206,7 @@ export function createAssetController(dataSource: DataSource): Router {
* PATCH /:id/usage * PATCH /:id/usage
* Actualiza métricas de uso (horas/kilómetros) * Actualiza métricas de uso (horas/kilómetros)
*/ */
router.patch('/:id/usage', async (req: Request, res: Response) => { router.patch('/:id/usage', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id; const userId = (req as any).user?.id;
@ -209,12 +214,13 @@ export function createAssetController(dataSource: DataSource): Router {
const asset = await service.updateUsage(tenantId, req.params.id, hours, kilometers, userId); const asset = await service.updateUsage(tenantId, req.params.id, hours, kilometers, userId);
if (!asset) { if (!asset) {
return res.status(404).json({ error: 'Activo no encontrado' }); res.status(404).json({ error: 'Activo no encontrado' });
return;
} }
res.json(asset); res.json(asset);
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -222,19 +228,20 @@ export function createAssetController(dataSource: DataSource): Router {
* DELETE /:id * DELETE /:id
* Elimina un activo (soft delete) * Elimina un activo (soft delete)
*/ */
router.delete('/:id', async (req: Request, res: Response) => { router.delete('/:id', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id; const userId = (req as any).user?.id;
const deleted = await service.delete(tenantId, req.params.id, userId); const deleted = await service.delete(tenantId, req.params.id, userId);
if (!deleted) { if (!deleted) {
return res.status(404).json({ error: 'Activo no encontrado' }); res.status(404).json({ error: 'Activo no encontrado' });
return;
} }
res.status(204).send(); res.status(204).send();
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -300,7 +307,7 @@ export function createAssetController(dataSource: DataSource): Router {
* POST /:id/return * POST /:id/return
* Retorna un activo de un proyecto * Retorna un activo de un proyecto
*/ */
router.post('/:id/return', async (req: Request, res: Response) => { router.post('/:id/return', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id; const userId = (req as any).user?.id;
@ -314,12 +321,13 @@ export function createAssetController(dataSource: DataSource): Router {
); );
if (!result) { if (!result) {
return res.status(400).json({ error: 'No hay asignación activa para este activo' }); res.status(400).json({ error: 'No hay asignación activa para este activo' });
return;
} }
res.json({ success: true, message: 'Activo retornado exitosamente' }); res.json({ success: true, message: 'Activo retornado exitosamente' });
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });

View File

@ -6,7 +6,7 @@
* @module Assets (MAE-015) * @module Assets (MAE-015)
*/ */
import { Router, Request, Response } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import { FuelLogService } from '../services'; import { FuelLogService } from '../services';
@ -18,7 +18,7 @@ export function createFuelLogController(dataSource: DataSource): Router {
* GET / * GET /
* Lista registros de combustible con filtros * Lista registros de combustible con filtros
*/ */
router.get('/', async (req: Request, res: Response) => { router.get('/', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
@ -38,7 +38,7 @@ export function createFuelLogController(dataSource: DataSource): Router {
const result = await service.findAll(tenantId, filters, pagination); const result = await service.findAll(tenantId, filters, pagination);
res.json(result); res.json(result);
} catch (error) { } catch (error) {
res.status(500).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -46,7 +46,7 @@ export function createFuelLogController(dataSource: DataSource): Router {
* GET /statistics/:assetId * GET /statistics/:assetId
* Obtiene estadísticas de combustible para un activo * Obtiene estadísticas de combustible para un activo
*/ */
router.get('/statistics/:assetId', async (req: Request, res: Response) => { router.get('/statistics/:assetId', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
@ -56,7 +56,7 @@ export function createFuelLogController(dataSource: DataSource): Router {
const stats = await service.getAssetStatistics(tenantId, req.params.assetId, fromDate, toDate); const stats = await service.getAssetStatistics(tenantId, req.params.assetId, fromDate, toDate);
res.json(stats); res.json(stats);
} catch (error) { } catch (error) {
res.status(500).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -64,18 +64,19 @@ export function createFuelLogController(dataSource: DataSource): Router {
* GET /:id * GET /:id
* Obtiene un registro por ID * Obtiene un registro por ID
*/ */
router.get('/:id', async (req: Request, res: Response) => { router.get('/:id', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const fuelLog = await service.findById(tenantId, req.params.id); const fuelLog = await service.findById(tenantId, req.params.id);
if (!fuelLog) { if (!fuelLog) {
return res.status(404).json({ error: 'Registro de combustible no encontrado' }); res.status(404).json({ error: 'Registro de combustible no encontrado' });
return;
} }
res.json(fuelLog); res.json(fuelLog);
} catch (error) { } catch (error) {
res.status(500).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -83,7 +84,7 @@ export function createFuelLogController(dataSource: DataSource): Router {
* POST / * POST /
* Crea un nuevo registro de combustible * Crea un nuevo registro de combustible
*/ */
router.post('/', async (req: Request, res: Response) => { router.post('/', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id; const userId = (req as any).user?.id;
@ -91,7 +92,7 @@ export function createFuelLogController(dataSource: DataSource): Router {
const fuelLog = await service.create(tenantId, req.body, userId); const fuelLog = await service.create(tenantId, req.body, userId);
res.status(201).json(fuelLog); res.status(201).json(fuelLog);
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -99,18 +100,19 @@ export function createFuelLogController(dataSource: DataSource): Router {
* DELETE /:id * DELETE /:id
* Elimina un registro de combustible * Elimina un registro de combustible
*/ */
router.delete('/:id', async (req: Request, res: Response) => { router.delete('/:id', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const deleted = await service.delete(tenantId, req.params.id); const deleted = await service.delete(tenantId, req.params.id);
if (!deleted) { if (!deleted) {
return res.status(404).json({ error: 'Registro de combustible no encontrado' }); res.status(404).json({ error: 'Registro de combustible no encontrado' });
return;
} }
res.status(204).send(); res.status(204).send();
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });

View File

@ -6,7 +6,7 @@
* @module Assets (MAE-015) * @module Assets (MAE-015)
*/ */
import { Router, Request, Response } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import { WorkOrderService } from '../services'; import { WorkOrderService } from '../services';
@ -20,7 +20,7 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* GET / * GET /
* Lista órdenes de trabajo con filtros y paginación * Lista órdenes de trabajo con filtros y paginación
*/ */
router.get('/', async (req: Request, res: Response) => { router.get('/', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
@ -44,7 +44,7 @@ export function createWorkOrderController(dataSource: DataSource): Router {
const result = await service.findAll(tenantId, filters, pagination); const result = await service.findAll(tenantId, filters, pagination);
res.json(result); res.json(result);
} catch (error) { } catch (error) {
res.status(500).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -52,14 +52,14 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* GET /statistics * GET /statistics
* Obtiene estadísticas de órdenes de trabajo * Obtiene estadísticas de órdenes de trabajo
*/ */
router.get('/statistics', async (req: Request, res: Response) => { router.get('/statistics', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const stats = await service.getStatistics(tenantId); const stats = await service.getStatistics(tenantId);
res.json(stats); res.json(stats);
} catch (error) { } catch (error) {
res.status(500).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -67,14 +67,14 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* GET /by-status * GET /by-status
* Obtiene órdenes agrupadas por estado * Obtiene órdenes agrupadas por estado
*/ */
router.get('/by-status', async (req: Request, res: Response) => { router.get('/by-status', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const grouped = await service.getByStatus(tenantId); const grouped = await service.getByStatus(tenantId);
res.json(grouped); res.json(grouped);
} catch (error) { } catch (error) {
res.status(500).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -82,18 +82,19 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* GET /by-number/:orderNumber * GET /by-number/:orderNumber
* Obtiene una orden por número * Obtiene una orden por número
*/ */
router.get('/by-number/:orderNumber', async (req: Request, res: Response) => { router.get('/by-number/:orderNumber', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const workOrder = await service.findByNumber(tenantId, req.params.orderNumber); const workOrder = await service.findByNumber(tenantId, req.params.orderNumber);
if (!workOrder) { if (!workOrder) {
return res.status(404).json({ error: 'Orden de trabajo no encontrada' }); res.status(404).json({ error: 'Orden de trabajo no encontrada' });
return;
} }
res.json(workOrder); res.json(workOrder);
} catch (error) { } catch (error) {
res.status(500).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -101,14 +102,14 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* GET /overdue * GET /overdue
* Lista órdenes vencidas * Lista órdenes vencidas
*/ */
router.get('/overdue', async (req: Request, res: Response) => { router.get('/overdue', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const orders = await service.getOverdue(tenantId); const orders = await service.getOverdue(tenantId);
res.json(orders); res.json(orders);
} catch (error) { } catch (error) {
res.status(500).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -116,18 +117,19 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* GET /:id * GET /:id
* Obtiene una orden de trabajo por ID * Obtiene una orden de trabajo por ID
*/ */
router.get('/:id', async (req: Request, res: Response) => { router.get('/:id', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const workOrder = await service.findById(tenantId, req.params.id); const workOrder = await service.findById(tenantId, req.params.id);
if (!workOrder) { if (!workOrder) {
return res.status(404).json({ error: 'Orden de trabajo no encontrada' }); res.status(404).json({ error: 'Orden de trabajo no encontrada' });
return;
} }
res.json(workOrder); res.json(workOrder);
} catch (error) { } catch (error) {
res.status(500).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -135,7 +137,7 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* POST / * POST /
* Crea una nueva orden de trabajo * Crea una nueva orden de trabajo
*/ */
router.post('/', async (req: Request, res: Response) => { router.post('/', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id; const userId = (req as any).user?.id;
@ -143,7 +145,7 @@ export function createWorkOrderController(dataSource: DataSource): Router {
const workOrder = await service.create(tenantId, req.body, userId); const workOrder = await service.create(tenantId, req.body, userId);
res.status(201).json(workOrder); res.status(201).json(workOrder);
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -151,19 +153,20 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* PUT /:id * PUT /:id
* Actualiza una orden de trabajo * Actualiza una orden de trabajo
*/ */
router.put('/:id', async (req: Request, res: Response) => { router.put('/:id', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id; const userId = (req as any).user?.id;
const workOrder = await service.update(tenantId, req.params.id, req.body, userId); const workOrder = await service.update(tenantId, req.params.id, req.body, userId);
if (!workOrder) { if (!workOrder) {
return res.status(404).json({ error: 'Orden de trabajo no encontrada' }); res.status(404).json({ error: 'Orden de trabajo no encontrada' });
return;
} }
res.json(workOrder); res.json(workOrder);
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -171,19 +174,20 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* DELETE /:id * DELETE /:id
* Elimina una orden de trabajo (soft delete) * Elimina una orden de trabajo (soft delete)
*/ */
router.delete('/:id', async (req: Request, res: Response) => { router.delete('/:id', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id; const userId = (req as any).user?.id;
const deleted = await service.delete(tenantId, req.params.id, userId); const deleted = await service.delete(tenantId, req.params.id, userId);
if (!deleted) { if (!deleted) {
return res.status(404).json({ error: 'Orden de trabajo no encontrada' }); res.status(404).json({ error: 'Orden de trabajo no encontrada' });
return;
} }
res.status(204).send(); res.status(204).send();
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -193,19 +197,20 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* POST /:id/start * POST /:id/start
* Inicia una orden de trabajo * Inicia una orden de trabajo
*/ */
router.post('/:id/start', async (req: Request, res: Response) => { router.post('/:id/start', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id; const userId = (req as any).user?.id;
const workOrder = await service.start(tenantId, req.params.id, userId); const workOrder = await service.start(tenantId, req.params.id, userId);
if (!workOrder) { if (!workOrder) {
return res.status(404).json({ error: 'Orden de trabajo no encontrada' }); res.status(404).json({ error: 'Orden de trabajo no encontrada' });
return;
} }
res.json(workOrder); res.json(workOrder);
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -213,7 +218,7 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* POST /:id/hold * POST /:id/hold
* Pone en espera una orden de trabajo * Pone en espera una orden de trabajo
*/ */
router.post('/:id/hold', async (req: Request, res: Response) => { router.post('/:id/hold', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id; const userId = (req as any).user?.id;
@ -221,12 +226,13 @@ export function createWorkOrderController(dataSource: DataSource): Router {
const workOrder = await service.hold(tenantId, req.params.id, reason, userId); const workOrder = await service.hold(tenantId, req.params.id, reason, userId);
if (!workOrder) { if (!workOrder) {
return res.status(404).json({ error: 'Orden de trabajo no encontrada' }); res.status(404).json({ error: 'Orden de trabajo no encontrada' });
return;
} }
res.json(workOrder); res.json(workOrder);
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -234,19 +240,20 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* POST /:id/resume * POST /:id/resume
* Reanuda una orden de trabajo en espera * Reanuda una orden de trabajo en espera
*/ */
router.post('/:id/resume', async (req: Request, res: Response) => { router.post('/:id/resume', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id; const userId = (req as any).user?.id;
const workOrder = await service.resume(tenantId, req.params.id, userId); const workOrder = await service.resume(tenantId, req.params.id, userId);
if (!workOrder) { if (!workOrder) {
return res.status(404).json({ error: 'Orden de trabajo no encontrada' }); res.status(404).json({ error: 'Orden de trabajo no encontrada' });
return;
} }
res.json(workOrder); res.json(workOrder);
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -254,19 +261,20 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* POST /:id/complete * POST /:id/complete
* Completa una orden de trabajo * Completa una orden de trabajo
*/ */
router.post('/:id/complete', async (req: Request, res: Response) => { router.post('/:id/complete', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id; const userId = (req as any).user?.id;
const workOrder = await service.complete(tenantId, req.params.id, req.body, userId); const workOrder = await service.complete(tenantId, req.params.id, req.body, userId);
if (!workOrder) { if (!workOrder) {
return res.status(404).json({ error: 'Orden de trabajo no encontrada' }); res.status(404).json({ error: 'Orden de trabajo no encontrada' });
return;
} }
res.json(workOrder); res.json(workOrder);
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -274,7 +282,7 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* POST /:id/cancel * POST /:id/cancel
* Cancela una orden de trabajo * Cancela una orden de trabajo
*/ */
router.post('/:id/cancel', async (req: Request, res: Response) => { router.post('/:id/cancel', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id; const userId = (req as any).user?.id;
@ -282,12 +290,13 @@ export function createWorkOrderController(dataSource: DataSource): Router {
const workOrder = await service.cancel(tenantId, req.params.id, reason, userId); const workOrder = await service.cancel(tenantId, req.params.id, reason, userId);
if (!workOrder) { if (!workOrder) {
return res.status(404).json({ error: 'Orden de trabajo no encontrada' }); res.status(404).json({ error: 'Orden de trabajo no encontrada' });
return;
} }
res.json(workOrder); res.json(workOrder);
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -297,14 +306,14 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* GET /:id/parts * GET /:id/parts
* Lista partes usadas en una orden de trabajo * Lista partes usadas en una orden de trabajo
*/ */
router.get('/:id/parts', async (req: Request, res: Response) => { router.get('/:id/parts', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const parts = await service.getParts(tenantId, req.params.id); const parts = await service.getParts(tenantId, req.params.id);
res.json(parts); res.json(parts);
} catch (error) { } catch (error) {
res.status(500).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -312,7 +321,7 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* POST /:id/parts * POST /:id/parts
* Agrega una parte a la orden de trabajo * Agrega una parte a la orden de trabajo
*/ */
router.post('/:id/parts', async (req: Request, res: Response) => { router.post('/:id/parts', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id; const userId = (req as any).user?.id;
@ -320,7 +329,7 @@ export function createWorkOrderController(dataSource: DataSource): Router {
const part = await service.addPart(tenantId, req.params.id, req.body, userId); const part = await service.addPart(tenantId, req.params.id, req.body, userId);
res.status(201).json(part); res.status(201).json(part);
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -328,19 +337,20 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* PUT /:id/parts/:partId * PUT /:id/parts/:partId
* Actualiza una parte de la orden * Actualiza una parte de la orden
*/ */
router.put('/:id/parts/:partId', async (req: Request, res: Response) => { router.put('/:id/parts/:partId', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id; const userId = (req as any).user?.id;
const part = await service.updatePart(tenantId, req.params.partId, req.body, userId); const part = await service.updatePart(tenantId, req.params.partId, req.body, userId);
if (!part) { if (!part) {
return res.status(404).json({ error: 'Parte no encontrada' }); res.status(404).json({ error: 'Parte no encontrada' });
return;
} }
res.json(part); res.json(part);
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -348,19 +358,20 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* DELETE /:id/parts/:partId * DELETE /:id/parts/:partId
* Elimina una parte de la orden * Elimina una parte de la orden
*/ */
router.delete('/:id/parts/:partId', async (req: Request, res: Response) => { router.delete('/:id/parts/:partId', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id; const userId = (req as any).user?.id;
const deleted = await service.removePart(tenantId, req.params.partId, userId); const deleted = await service.removePart(tenantId, req.params.partId, userId);
if (!deleted) { if (!deleted) {
return res.status(404).json({ error: 'Parte no encontrada' }); res.status(404).json({ error: 'Parte no encontrada' });
return;
} }
res.status(204).send(); res.status(204).send();
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -370,7 +381,7 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* GET /plans * GET /plans
* Lista planes de mantenimiento * Lista planes de mantenimiento
*/ */
router.get('/plans/list', async (req: Request, res: Response) => { router.get('/plans/list', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const assetId = req.query.assetId as string; const assetId = req.query.assetId as string;
@ -378,7 +389,7 @@ export function createWorkOrderController(dataSource: DataSource): Router {
const plans = await service.getMaintenancePlans(tenantId, assetId); const plans = await service.getMaintenancePlans(tenantId, assetId);
res.json(plans); res.json(plans);
} catch (error) { } catch (error) {
res.status(500).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -386,7 +397,7 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* POST /plans * POST /plans
* Crea un plan de mantenimiento * Crea un plan de mantenimiento
*/ */
router.post('/plans', async (req: Request, res: Response) => { router.post('/plans', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id; const userId = (req as any).user?.id;
@ -394,7 +405,7 @@ export function createWorkOrderController(dataSource: DataSource): Router {
const plan = await service.createMaintenancePlan(tenantId, req.body, userId); const plan = await service.createMaintenancePlan(tenantId, req.body, userId);
res.status(201).json(plan); res.status(201).json(plan);
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });
@ -402,7 +413,7 @@ export function createWorkOrderController(dataSource: DataSource): Router {
* POST /plans/:planId/generate * POST /plans/:planId/generate
* Genera órdenes de trabajo desde un plan * Genera órdenes de trabajo desde un plan
*/ */
router.post('/plans/:planId/generate', async (req: Request, res: Response) => { router.post('/plans/:planId/generate', async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
const tenantId = req.headers['x-tenant-id'] as string; const tenantId = req.headers['x-tenant-id'] as string;
const userId = (req as any).user?.id; const userId = (req as any).user?.id;
@ -410,7 +421,7 @@ export function createWorkOrderController(dataSource: DataSource): Router {
const orders = await service.generateFromPlan(tenantId, req.params.planId, userId); const orders = await service.generateFromPlan(tenantId, req.params.planId, userId);
res.status(201).json(orders); res.status(201).json(orders);
} catch (error) { } catch (error) {
res.status(400).json({ error: (error as Error).message }); next(error);
} }
}); });

View File

@ -24,6 +24,23 @@ export type MaintenanceType = 'preventive' | 'corrective' | 'predictive' | 'emer
export type WorkOrderStatus = 'draft' | 'scheduled' | 'in_progress' | 'on_hold' | 'completed' | 'cancelled'; export type WorkOrderStatus = 'draft' | 'scheduled' | 'in_progress' | 'on_hold' | 'completed' | 'cancelled';
export type WorkOrderPriority = 'low' | 'medium' | 'high' | 'critical'; export type WorkOrderPriority = 'low' | 'medium' | 'high' | 'critical';
// Const objects for runtime use
export const WorkOrderStatusValues: Record<string, WorkOrderStatus> = {
DRAFT: 'draft',
SCHEDULED: 'scheduled',
IN_PROGRESS: 'in_progress',
ON_HOLD: 'on_hold',
COMPLETED: 'completed',
CANCELLED: 'cancelled',
} as const;
export const WorkOrderPriorityValues: Record<string, WorkOrderPriority> = {
LOW: 'low',
MEDIUM: 'medium',
HIGH: 'high',
CRITICAL: 'critical',
} as const;
@Entity('work_orders', { schema: 'assets' }) @Entity('work_orders', { schema: 'assets' })
@Index(['tenantId', 'workOrderNumber'], { unique: true }) @Index(['tenantId', 'workOrderNumber'], { unique: true })
@Index(['tenantId', 'assetId']) @Index(['tenantId', 'assetId'])

View File

@ -5,7 +5,7 @@
* Logica de negocio para gestion de activos fijos y maquinaria. * Logica de negocio para gestion de activos fijos y maquinaria.
*/ */
import { Repository, DataSource, ILike } from 'typeorm'; import { Repository, DataSource, IsNull } from 'typeorm';
import { Asset, AssetType, AssetStatus, OwnershipType } from '../entities/asset.entity'; import { Asset, AssetType, AssetStatus, OwnershipType } from '../entities/asset.entity';
import { AssetCategory } from '../entities/asset-category.entity'; import { AssetCategory } from '../entities/asset-category.entity';
import { AssetAssignment } from '../entities/asset-assignment.entity'; import { AssetAssignment } from '../entities/asset-assignment.entity';
@ -94,7 +94,7 @@ export class AssetService {
private categoryRepository: Repository<AssetCategory>; private categoryRepository: Repository<AssetCategory>;
private assignmentRepository: Repository<AssetAssignment>; private assignmentRepository: Repository<AssetAssignment>;
constructor(private dataSource: DataSource) { constructor(dataSource: DataSource) {
this.assetRepository = dataSource.getRepository(Asset); this.assetRepository = dataSource.getRepository(Asset);
this.categoryRepository = dataSource.getRepository(AssetCategory); this.categoryRepository = dataSource.getRepository(AssetCategory);
this.assignmentRepository = dataSource.getRepository(AssetAssignment); this.assignmentRepository = dataSource.getRepository(AssetAssignment);
@ -120,8 +120,8 @@ export class AssetService {
const asset = this.assetRepository.create({ const asset = this.assetRepository.create({
tenantId, tenantId,
...dto, ...dto,
status: AssetStatus.AVAILABLE, status: 'available' as AssetStatus,
ownershipType: dto.ownershipType || OwnershipType.OWNED, ownershipType: dto.ownershipType || ('owned' as OwnershipType),
currentBookValue: dto.purchasePrice, currentBookValue: dto.purchasePrice,
createdBy: userId, createdBy: userId,
}); });
@ -263,7 +263,7 @@ export class AssetService {
async delete(tenantId: string, id: string, userId?: string): Promise<boolean> { async delete(tenantId: string, id: string, userId?: string): Promise<boolean> {
const result = await this.assetRepository.update( const result = await this.assetRepository.update(
{ id, tenantId }, { id, tenantId },
{ deletedAt: new Date(), status: AssetStatus.RETIRED, updatedBy: userId } { deletedAt: new Date(), status: 'retired' as AssetStatus, updatedBy: userId }
); );
return (result.affected ?? 0) > 0; return (result.affected ?? 0) > 0;
} }
@ -315,7 +315,7 @@ export class AssetService {
// Update asset with new project // Update asset with new project
await this.update(tenantId, dto.assetId, { await this.update(tenantId, dto.assetId, {
currentProjectId: dto.projectId, currentProjectId: dto.projectId,
status: AssetStatus.ASSIGNED, status: 'assigned' as AssetStatus,
assignedOperatorId: dto.operatorId, assignedOperatorId: dto.operatorId,
}, userId); }, userId);
@ -378,7 +378,7 @@ export class AssetService {
// Update asset status // Update asset status
await this.update(tenantId, assetId, { await this.update(tenantId, assetId, {
currentProjectId: undefined, currentProjectId: undefined,
status: AssetStatus.AVAILABLE, status: 'available' as AssetStatus,
}, userId); }, userId);
return true; return true;
@ -405,7 +405,7 @@ export class AssetService {
*/ */
async getCategories(tenantId: string): Promise<AssetCategory[]> { async getCategories(tenantId: string): Promise<AssetCategory[]> {
return this.categoryRepository.find({ return this.categoryRepository.find({
where: { tenantId, isActive: true, deletedAt: null }, where: { tenantId, isActive: true, deletedAt: IsNull() },
order: { level: 'ASC', name: 'ASC' }, order: { level: 'ASC', name: 'ASC' },
}); });
} }
@ -425,7 +425,7 @@ export class AssetService {
maintenanceDue: number; maintenanceDue: number;
}> { }> {
const [total, byStatusRaw, byTypeRaw, valueResult, maintenanceDue] = await Promise.all([ const [total, byStatusRaw, byTypeRaw, valueResult, maintenanceDue] = await Promise.all([
this.assetRepository.count({ where: { tenantId, deletedAt: null } }), this.assetRepository.count({ where: { tenantId, deletedAt: IsNull() } }),
this.assetRepository.createQueryBuilder('asset') this.assetRepository.createQueryBuilder('asset')
.select('asset.status', 'status') .select('asset.status', 'status')
@ -484,7 +484,7 @@ export class AssetService {
return this.assetRepository.createQueryBuilder('asset') return this.assetRepository.createQueryBuilder('asset')
.where('asset.tenant_id = :tenantId', { tenantId }) .where('asset.tenant_id = :tenantId', { tenantId })
.andWhere('asset.deleted_at IS NULL') .andWhere('asset.deleted_at IS NULL')
.andWhere('asset.status != :retired', { retired: AssetStatus.RETIRED }) .andWhere('asset.status != :retired', { retired: 'retired' })
.andWhere( .andWhere(
'(asset.next_maintenance_date <= :today OR asset.current_hours >= asset.next_maintenance_hours OR asset.current_kilometers >= asset.next_maintenance_kilometers)', '(asset.next_maintenance_date <= :today OR asset.current_hours >= asset.next_maintenance_hours OR asset.current_kilometers >= asset.next_maintenance_kilometers)',
{ today } { today }

View File

@ -5,7 +5,7 @@
* Logica de negocio para registro de combustible y calculo de rendimiento. * Logica de negocio para registro de combustible y calculo de rendimiento.
*/ */
import { Repository, DataSource, Between } from 'typeorm'; import { Repository, DataSource } from 'typeorm';
import { FuelLog } from '../entities/fuel-log.entity'; import { FuelLog } from '../entities/fuel-log.entity';
import { Asset } from '../entities/asset.entity'; import { Asset } from '../entities/asset.entity';
@ -48,7 +48,7 @@ export class FuelLogService {
private fuelLogRepository: Repository<FuelLog>; private fuelLogRepository: Repository<FuelLog>;
private assetRepository: Repository<Asset>; private assetRepository: Repository<Asset>;
constructor(private dataSource: DataSource) { constructor(dataSource: DataSource) {
this.fuelLogRepository = dataSource.getRepository(FuelLog); this.fuelLogRepository = dataSource.getRepository(FuelLog);
this.assetRepository = dataSource.getRepository(Asset); this.assetRepository = dataSource.getRepository(Asset);
} }

View File

@ -5,8 +5,8 @@
* Logica de negocio para ordenes de trabajo de mantenimiento. * Logica de negocio para ordenes de trabajo de mantenimiento.
*/ */
import { Repository, DataSource, LessThanOrEqual, In } from 'typeorm'; import { Repository, DataSource, LessThanOrEqual, In, IsNull } from 'typeorm';
import { WorkOrder, WorkOrderStatus, WorkOrderPriority, MaintenanceType } from '../entities/work-order.entity'; import { WorkOrder, WorkOrderStatus, WorkOrderPriority, MaintenanceType, WorkOrderStatusValues, WorkOrderPriorityValues } from '../entities/work-order.entity';
import { WorkOrderPart } from '../entities/work-order-part.entity'; import { WorkOrderPart } from '../entities/work-order-part.entity';
import { MaintenanceHistory } from '../entities/maintenance-history.entity'; import { MaintenanceHistory } from '../entities/maintenance-history.entity';
import { MaintenancePlan } from '../entities/maintenance-plan.entity'; import { MaintenancePlan } from '../entities/maintenance-plan.entity';
@ -102,7 +102,7 @@ export class WorkOrderService {
private planRepository: Repository<MaintenancePlan>; private planRepository: Repository<MaintenancePlan>;
private assetRepository: Repository<Asset>; private assetRepository: Repository<Asset>;
constructor(private dataSource: DataSource) { constructor(dataSource: DataSource) {
this.workOrderRepository = dataSource.getRepository(WorkOrder); this.workOrderRepository = dataSource.getRepository(WorkOrder);
this.partRepository = dataSource.getRepository(WorkOrderPart); this.partRepository = dataSource.getRepository(WorkOrderPart);
this.historyRepository = dataSource.getRepository(MaintenanceHistory); this.historyRepository = dataSource.getRepository(MaintenanceHistory);
@ -153,8 +153,8 @@ export class WorkOrderService {
assetCode: asset.assetCode, assetCode: asset.assetCode,
assetName: asset.name, assetName: asset.name,
maintenanceType: dto.maintenanceType, maintenanceType: dto.maintenanceType,
priority: dto.priority || WorkOrderPriority.MEDIUM, priority: dto.priority || WorkOrderPriorityValues.MEDIUM,
status: WorkOrderStatus.DRAFT, status: WorkOrderStatusValues.DRAFT,
title: dto.title, title: dto.title,
description: dto.description, description: dto.description,
problemReported: dto.problemReported, problemReported: dto.problemReported,
@ -290,15 +290,15 @@ export class WorkOrderService {
*/ */
private validateStatusTransition(from: WorkOrderStatus, to: WorkOrderStatus): void { private validateStatusTransition(from: WorkOrderStatus, to: WorkOrderStatus): void {
const validTransitions: Record<WorkOrderStatus, WorkOrderStatus[]> = { const validTransitions: Record<WorkOrderStatus, WorkOrderStatus[]> = {
[WorkOrderStatus.DRAFT]: [WorkOrderStatus.SCHEDULED, WorkOrderStatus.IN_PROGRESS, WorkOrderStatus.CANCELLED], draft: ['scheduled', 'in_progress', 'cancelled'],
[WorkOrderStatus.SCHEDULED]: [WorkOrderStatus.IN_PROGRESS, WorkOrderStatus.CANCELLED], scheduled: ['in_progress', 'cancelled'],
[WorkOrderStatus.IN_PROGRESS]: [WorkOrderStatus.ON_HOLD, WorkOrderStatus.COMPLETED, WorkOrderStatus.CANCELLED], in_progress: ['on_hold', 'completed', 'cancelled'],
[WorkOrderStatus.ON_HOLD]: [WorkOrderStatus.IN_PROGRESS, WorkOrderStatus.CANCELLED], on_hold: ['in_progress', 'cancelled'],
[WorkOrderStatus.COMPLETED]: [], completed: [],
[WorkOrderStatus.CANCELLED]: [], cancelled: [],
}; };
if (!validTransitions[from].includes(to)) { if (!validTransitions[from]?.includes(to)) {
throw new Error(`Invalid status transition from ${from} to ${to}`); throw new Error(`Invalid status transition from ${from} to ${to}`);
} }
} }
@ -310,12 +310,12 @@ export class WorkOrderService {
const now = new Date(); const now = new Date();
switch (newStatus) { switch (newStatus) {
case WorkOrderStatus.IN_PROGRESS: case 'in_progress':
if (!workOrder.actualStartDate) { if (!workOrder.actualStartDate) {
workOrder.actualStartDate = now; workOrder.actualStartDate = now;
} }
break; break;
case WorkOrderStatus.COMPLETED: case 'completed':
workOrder.actualEndDate = now; workOrder.actualEndDate = now;
break; break;
} }
@ -325,7 +325,7 @@ export class WorkOrderService {
* Start work order (change to in_progress) * Start work order (change to in_progress)
*/ */
async start(tenantId: string, id: string, userId?: string): Promise<WorkOrder | null> { async start(tenantId: string, id: string, userId?: string): Promise<WorkOrder | null> {
return this.update(tenantId, id, { status: WorkOrderStatus.IN_PROGRESS }, userId); return this.update(tenantId, id, { status: 'in_progress' }, userId);
} }
/** /**
@ -336,7 +336,7 @@ export class WorkOrderService {
if (!workOrder) return null; if (!workOrder) return null;
// Update work order // Update work order
workOrder.status = WorkOrderStatus.COMPLETED; workOrder.status = 'completed';
workOrder.actualEndDate = new Date(); workOrder.actualEndDate = new Date();
workOrder.workPerformed = dto.workPerformed; workOrder.workPerformed = dto.workPerformed;
workOrder.findings = dto.findings; workOrder.findings = dto.findings;
@ -454,7 +454,7 @@ export class WorkOrderService {
const [total, byStatusRaw, byTypeRaw, pendingCount, inProgressCount, completedThisMonth, costResult] = const [total, byStatusRaw, byTypeRaw, pendingCount, inProgressCount, completedThisMonth, costResult] =
await Promise.all([ await Promise.all([
this.workOrderRepository.count({ where: { tenantId, deletedAt: null } }), this.workOrderRepository.count({ where: { tenantId, deletedAt: IsNull() } }),
this.workOrderRepository.createQueryBuilder('wo') this.workOrderRepository.createQueryBuilder('wo')
.select('wo.status', 'status') .select('wo.status', 'status')
@ -473,23 +473,23 @@ export class WorkOrderService {
.getRawMany(), .getRawMany(),
this.workOrderRepository.count({ this.workOrderRepository.count({
where: { tenantId, status: WorkOrderStatus.DRAFT, deletedAt: null }, where: { tenantId, status: 'draft' as WorkOrderStatus, deletedAt: IsNull() },
}), }),
this.workOrderRepository.count({ this.workOrderRepository.count({
where: { tenantId, status: WorkOrderStatus.IN_PROGRESS, deletedAt: null }, where: { tenantId, status: 'in_progress' as WorkOrderStatus, deletedAt: IsNull() },
}), }),
this.workOrderRepository.createQueryBuilder('wo') this.workOrderRepository.createQueryBuilder('wo')
.where('wo.tenant_id = :tenantId', { tenantId }) .where('wo.tenant_id = :tenantId', { tenantId })
.andWhere('wo.status = :status', { status: WorkOrderStatus.COMPLETED }) .andWhere('wo.status = :status', { status: 'completed' as WorkOrderStatus })
.andWhere('wo.actual_end_date >= :startOfMonth', { startOfMonth }) .andWhere('wo.actual_end_date >= :startOfMonth', { startOfMonth })
.getCount(), .getCount(),
this.workOrderRepository.createQueryBuilder('wo') this.workOrderRepository.createQueryBuilder('wo')
.select('SUM(wo.total_cost)', 'total') .select('SUM(wo.total_cost)', 'total')
.where('wo.tenant_id = :tenantId', { tenantId }) .where('wo.tenant_id = :tenantId', { tenantId })
.andWhere('wo.status = :status', { status: WorkOrderStatus.COMPLETED }) .andWhere('wo.status = :status', { status: 'completed' as WorkOrderStatus })
.andWhere('wo.actual_end_date >= :startOfMonth', { startOfMonth }) .andWhere('wo.actual_end_date >= :startOfMonth', { startOfMonth })
.getRawOne(), .getRawOne(),
]); ]);
@ -520,18 +520,18 @@ export class WorkOrderService {
*/ */
async getByStatus(tenantId: string): Promise<Record<WorkOrderStatus, WorkOrder[]>> { async getByStatus(tenantId: string): Promise<Record<WorkOrderStatus, WorkOrder[]>> {
const workOrders = await this.workOrderRepository.find({ const workOrders = await this.workOrderRepository.find({
where: { tenantId, deletedAt: null }, where: { tenantId, deletedAt: IsNull() },
relations: ['asset'], relations: ['asset'],
order: { requestedDate: 'DESC' }, order: { requestedDate: 'DESC' },
}); });
const grouped: Record<WorkOrderStatus, WorkOrder[]> = { const grouped: Record<WorkOrderStatus, WorkOrder[]> = {
[WorkOrderStatus.DRAFT]: [], draft: [],
[WorkOrderStatus.SCHEDULED]: [], scheduled: [],
[WorkOrderStatus.IN_PROGRESS]: [], in_progress: [],
[WorkOrderStatus.ON_HOLD]: [], on_hold: [],
[WorkOrderStatus.COMPLETED]: [], completed: [],
[WorkOrderStatus.CANCELLED]: [], cancelled: [],
}; };
for (const wo of workOrders) { for (const wo of workOrders) {
@ -548,9 +548,9 @@ export class WorkOrderService {
const workOrder = await this.findById(tenantId, id); const workOrder = await this.findById(tenantId, id);
if (!workOrder) return null; if (!workOrder) return null;
this.validateStatusTransition(workOrder.status, WorkOrderStatus.ON_HOLD); this.validateStatusTransition(workOrder.status, 'on_hold' as WorkOrderStatus);
workOrder.status = WorkOrderStatus.ON_HOLD; workOrder.status = 'on_hold' as WorkOrderStatus;
workOrder.notes = reason ? `${workOrder.notes || ''}\n[ON HOLD] ${reason}` : workOrder.notes; workOrder.notes = reason ? `${workOrder.notes || ''}\n[ON HOLD] ${reason}` : workOrder.notes;
workOrder.updatedBy = userId; workOrder.updatedBy = userId;
@ -564,9 +564,9 @@ export class WorkOrderService {
const workOrder = await this.findById(tenantId, id); const workOrder = await this.findById(tenantId, id);
if (!workOrder) return null; if (!workOrder) return null;
this.validateStatusTransition(workOrder.status, WorkOrderStatus.IN_PROGRESS); this.validateStatusTransition(workOrder.status, 'in_progress' as WorkOrderStatus);
workOrder.status = WorkOrderStatus.IN_PROGRESS; workOrder.status = 'in_progress' as WorkOrderStatus;
workOrder.updatedBy = userId; workOrder.updatedBy = userId;
return this.workOrderRepository.save(workOrder); return this.workOrderRepository.save(workOrder);
@ -579,9 +579,9 @@ export class WorkOrderService {
const workOrder = await this.findById(tenantId, id); const workOrder = await this.findById(tenantId, id);
if (!workOrder) return null; if (!workOrder) return null;
this.validateStatusTransition(workOrder.status, WorkOrderStatus.CANCELLED); this.validateStatusTransition(workOrder.status, 'cancelled' as WorkOrderStatus);
workOrder.status = WorkOrderStatus.CANCELLED; workOrder.status = 'cancelled' as WorkOrderStatus;
workOrder.notes = reason ? `${workOrder.notes || ''}\n[CANCELLED] ${reason}` : workOrder.notes; workOrder.notes = reason ? `${workOrder.notes || ''}\n[CANCELLED] ${reason}` : workOrder.notes;
workOrder.updatedBy = userId; workOrder.updatedBy = userId;
@ -612,7 +612,7 @@ export class WorkOrderService {
/** /**
* Update a part * Update a part
*/ */
async updatePart(tenantId: string, partId: string, dto: Partial<AddPartDto>, userId?: string): Promise<WorkOrderPart | null> { async updatePart(tenantId: string, partId: string, dto: Partial<AddPartDto>, _userId?: string): Promise<WorkOrderPart | null> {
const part = await this.partRepository.findOne({ where: { id: partId, tenantId } }); const part = await this.partRepository.findOne({ where: { id: partId, tenantId } });
if (!part) return null; if (!part) return null;
@ -625,7 +625,6 @@ export class WorkOrderService {
if (part.unitCost && part.quantityUsed) { if (part.unitCost && part.quantityUsed) {
part.totalCost = part.unitCost * part.quantityUsed; part.totalCost = part.unitCost * part.quantityUsed;
} }
part.updatedBy = userId;
const savedPart = await this.partRepository.save(part); const savedPart = await this.partRepository.save(part);
await this.recalculatePartsCost(part.workOrderId); await this.recalculatePartsCost(part.workOrderId);
@ -636,7 +635,7 @@ export class WorkOrderService {
/** /**
* Remove a part from work order * Remove a part from work order
*/ */
async removePart(tenantId: string, partId: string, userId?: string): Promise<boolean> { async removePart(tenantId: string, partId: string, _userId?: string): Promise<boolean> {
const part = await this.partRepository.findOne({ where: { id: partId, tenantId } }); const part = await this.partRepository.findOne({ where: { id: partId, tenantId } });
if (!part) return false; if (!part) return false;
@ -657,7 +656,7 @@ export class WorkOrderService {
where: { where: {
tenantId, tenantId,
deletedAt: undefined, deletedAt: undefined,
status: In([WorkOrderStatus.DRAFT, WorkOrderStatus.SCHEDULED, WorkOrderStatus.IN_PROGRESS]), status: In(['draft' as WorkOrderStatus, 'scheduled' as WorkOrderStatus, 'in_progress' as WorkOrderStatus]),
scheduledEndDate: LessThanOrEqual(now), scheduledEndDate: LessThanOrEqual(now),
}, },
relations: ['asset'], relations: ['asset'],