Tipos de toma backend logic
This commit is contained in:
81
water-api/sql/create_meter_types.sql
Normal file
81
water-api/sql/create_meter_types.sql
Normal file
@@ -0,0 +1,81 @@
|
||||
-- ============================================================================
|
||||
-- Create meter_types table and add relationship to projects
|
||||
-- Meter types: LoRa, LoRaWAN, Grandes Consumidores
|
||||
-- ============================================================================
|
||||
|
||||
-- Create meter_types table
|
||||
CREATE TABLE IF NOT EXISTS meter_types (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(50) NOT NULL UNIQUE,
|
||||
code VARCHAR(20) NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Insert default meter types
|
||||
INSERT INTO meter_types (name, code, description) VALUES
|
||||
('LoRa', 'LORA', 'Medidores con tecnología LoRa'),
|
||||
('LoRaWAN', 'LORAWAN', 'Medidores con tecnología LoRaWAN'),
|
||||
('Grandes Consumidores', 'GRANDES', 'Medidores para grandes consumidores')
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- Add meter_type_id column to projects table
|
||||
ALTER TABLE projects
|
||||
ADD COLUMN IF NOT EXISTS meter_type_id UUID REFERENCES meter_types(id) ON DELETE SET NULL;
|
||||
|
||||
-- Add index for better query performance
|
||||
CREATE INDEX IF NOT EXISTS idx_projects_meter_type_id ON projects(meter_type_id);
|
||||
|
||||
-- Add comment
|
||||
COMMENT ON TABLE meter_types IS 'Catalog of meter types (LoRa, LoRaWAN, Grandes Consumidores)';
|
||||
COMMENT ON COLUMN projects.meter_type_id IS 'Default meter type for this project';
|
||||
|
||||
-- ============================================================================
|
||||
-- Helper function to get meter type by code
|
||||
-- ============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION get_meter_type_id(type_code VARCHAR)
|
||||
RETURNS UUID AS $$
|
||||
DECLARE
|
||||
type_id UUID;
|
||||
BEGIN
|
||||
SELECT id INTO type_id FROM meter_types WHERE code = type_code AND is_active = true;
|
||||
RETURN type_id;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- ============================================================================
|
||||
-- Update trigger for updated_at
|
||||
-- ============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_meter_types_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER trigger_meter_types_updated_at
|
||||
BEFORE UPDATE ON meter_types
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_meter_types_updated_at();
|
||||
|
||||
-- ============================================================================
|
||||
-- Verify the changes
|
||||
-- ============================================================================
|
||||
|
||||
-- Show meter types
|
||||
SELECT id, name, code, description, is_active FROM meter_types ORDER BY code;
|
||||
|
||||
-- Show projects table structure
|
||||
SELECT
|
||||
column_name,
|
||||
data_type,
|
||||
is_nullable,
|
||||
column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'projects'
|
||||
AND column_name = 'meter_type_id';
|
||||
185
water-api/src/controllers/meterType.controller.ts
Normal file
185
water-api/src/controllers/meterType.controller.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
import { Request, Response } from 'express';
|
||||
import * as meterTypeService from '../services/meterType.service';
|
||||
|
||||
/**
|
||||
* GET /meter-types
|
||||
* Get all active meter types
|
||||
*/
|
||||
export async function getAll(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const meterTypes = await meterTypeService.getAll();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: meterTypes,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching meter types:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to fetch meter types',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /meter-types/:id
|
||||
* Get a single meter type by ID
|
||||
*/
|
||||
export async function getById(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const meterType = await meterTypeService.getById(id);
|
||||
|
||||
if (!meterType) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: 'Meter type not found',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: meterType,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching meter type:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to fetch meter type',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /meter-types/code/:code
|
||||
* Get a meter type by code
|
||||
*/
|
||||
export async function getByCode(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { code } = req.params;
|
||||
const meterType = await meterTypeService.getByCode(code);
|
||||
|
||||
if (!meterType) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: 'Meter type not found',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: meterType,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching meter type:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to fetch meter type',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /meter-types
|
||||
* Create a new meter type (ADMIN only)
|
||||
*/
|
||||
export async function create(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { name, code, description } = req.body;
|
||||
|
||||
if (!name || !code) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: 'Name and code are required',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const meterType = await meterTypeService.create({
|
||||
name,
|
||||
code,
|
||||
description,
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: meterType,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error creating meter type:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to create meter type',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /meter-types/:id
|
||||
* Update a meter type (ADMIN only)
|
||||
*/
|
||||
export async function update(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name, code, description, is_active } = req.body;
|
||||
|
||||
const meterType = await meterTypeService.update(id, {
|
||||
name,
|
||||
code,
|
||||
description,
|
||||
is_active,
|
||||
});
|
||||
|
||||
if (!meterType) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: 'Meter type not found',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: meterType,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error updating meter type:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to update meter type',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /meter-types/:id
|
||||
* Soft delete a meter type (ADMIN only)
|
||||
*/
|
||||
export async function remove(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const success = await meterTypeService.remove(id);
|
||||
|
||||
if (!success) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: 'Meter type not found',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'Meter type deleted successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error deleting meter type:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to delete meter type',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { Router } from 'express';
|
||||
import authRoutes from './auth.routes';
|
||||
import projectRoutes from './project.routes';
|
||||
import meterRoutes from './meter.routes';
|
||||
import meterTypeRoutes from './meterType.routes';
|
||||
import concentratorRoutes from './concentrator.routes';
|
||||
import gatewayRoutes from './gateway.routes';
|
||||
import deviceRoutes from './device.routes';
|
||||
@@ -52,6 +53,17 @@ router.use('/projects', projectRoutes);
|
||||
*/
|
||||
router.use('/meters', meterRoutes);
|
||||
|
||||
/**
|
||||
* Meter Type routes:
|
||||
* - GET /meter-types - List all meter types
|
||||
* - GET /meter-types/:id - Get meter type by ID
|
||||
* - GET /meter-types/code/:code - Get meter type by code
|
||||
* - POST /meter-types - Create meter type (admin only)
|
||||
* - PUT /meter-types/:id - Update meter type (admin only)
|
||||
* - DELETE /meter-types/:id - Delete meter type (admin only)
|
||||
*/
|
||||
router.use('/meter-types', meterTypeRoutes);
|
||||
|
||||
/**
|
||||
* Concentrator routes:
|
||||
* - GET /concentrators - List all concentrators
|
||||
|
||||
64
water-api/src/routes/meterType.routes.ts
Normal file
64
water-api/src/routes/meterType.routes.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { Router } from 'express';
|
||||
import * as meterTypeController from '../controllers/meterType.controller';
|
||||
import { authenticateToken, requireRole } from '../middleware/auth.middleware';
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* @route GET /api/meter-types
|
||||
* @desc Get all active meter types
|
||||
* @access Private (any authenticated user)
|
||||
*/
|
||||
router.get('/', authenticateToken, meterTypeController.getAll);
|
||||
|
||||
/**
|
||||
* @route GET /api/meter-types/code/:code
|
||||
* @desc Get a meter type by code
|
||||
* @access Private (any authenticated user)
|
||||
*/
|
||||
router.get('/code/:code', authenticateToken, meterTypeController.getByCode);
|
||||
|
||||
/**
|
||||
* @route GET /api/meter-types/:id
|
||||
* @desc Get a single meter type by ID
|
||||
* @access Private (any authenticated user)
|
||||
*/
|
||||
router.get('/:id', authenticateToken, meterTypeController.getById);
|
||||
|
||||
/**
|
||||
* @route POST /api/meter-types
|
||||
* @desc Create a new meter type
|
||||
* @access Private (ADMIN only)
|
||||
*/
|
||||
router.post(
|
||||
'/',
|
||||
authenticateToken,
|
||||
requireRole('ADMIN'),
|
||||
meterTypeController.create
|
||||
);
|
||||
|
||||
/**
|
||||
* @route PUT /api/meter-types/:id
|
||||
* @desc Update a meter type
|
||||
* @access Private (ADMIN only)
|
||||
*/
|
||||
router.put(
|
||||
'/:id',
|
||||
authenticateToken,
|
||||
requireRole('ADMIN'),
|
||||
meterTypeController.update
|
||||
);
|
||||
|
||||
/**
|
||||
* @route DELETE /api/meter-types/:id
|
||||
* @desc Soft delete a meter type
|
||||
* @access Private (ADMIN only)
|
||||
*/
|
||||
router.delete(
|
||||
'/:id',
|
||||
authenticateToken,
|
||||
requireRole('ADMIN'),
|
||||
meterTypeController.remove
|
||||
);
|
||||
|
||||
export default router;
|
||||
142
water-api/src/services/meterType.service.ts
Normal file
142
water-api/src/services/meterType.service.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { query } from '../config/database';
|
||||
import { MeterType } from '../types';
|
||||
|
||||
/**
|
||||
* Get all meter types
|
||||
* @returns Promise resolving to array of meter types
|
||||
*/
|
||||
export async function getAll(): Promise<MeterType[]> {
|
||||
const result = await query<MeterType>(
|
||||
`SELECT id, name, code, description, is_active, created_at, updated_at
|
||||
FROM meter_types
|
||||
WHERE is_active = true
|
||||
ORDER BY code ASC`
|
||||
);
|
||||
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single meter type by ID
|
||||
* @param id - Meter type ID
|
||||
* @returns Promise resolving to meter type or null if not found
|
||||
*/
|
||||
export async function getById(id: string): Promise<MeterType | null> {
|
||||
const result = await query<MeterType>(
|
||||
`SELECT id, name, code, description, is_active, created_at, updated_at
|
||||
FROM meter_types
|
||||
WHERE id = $1`,
|
||||
[id]
|
||||
);
|
||||
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a meter type by code
|
||||
* @param code - Meter type code (LORA, LORAWAN, GRANDES)
|
||||
* @returns Promise resolving to meter type or null if not found
|
||||
*/
|
||||
export async function getByCode(code: string): Promise<MeterType | null> {
|
||||
const result = await query<MeterType>(
|
||||
`SELECT id, name, code, description, is_active, created_at, updated_at
|
||||
FROM meter_types
|
||||
WHERE code = $1 AND is_active = true`,
|
||||
[code]
|
||||
);
|
||||
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new meter type
|
||||
* @param data - Meter type data
|
||||
* @returns Promise resolving to created meter type
|
||||
*/
|
||||
export async function create(data: {
|
||||
name: string;
|
||||
code: string;
|
||||
description?: string | null;
|
||||
}): Promise<MeterType> {
|
||||
const result = await query<MeterType>(
|
||||
`INSERT INTO meter_types (name, code, description)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id, name, code, description, is_active, created_at, updated_at`,
|
||||
[data.name, data.code, data.description || null]
|
||||
);
|
||||
|
||||
return result.rows[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a meter type
|
||||
* @param id - Meter type ID
|
||||
* @param data - Fields to update
|
||||
* @returns Promise resolving to updated meter type or null if not found
|
||||
*/
|
||||
export async function update(
|
||||
id: string,
|
||||
data: {
|
||||
name?: string;
|
||||
code?: string;
|
||||
description?: string | null;
|
||||
is_active?: boolean;
|
||||
}
|
||||
): Promise<MeterType | null> {
|
||||
const updates: string[] = [];
|
||||
const params: unknown[] = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
if (data.name !== undefined) {
|
||||
updates.push(`name = $${paramIndex}`);
|
||||
params.push(data.name);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
if (data.code !== undefined) {
|
||||
updates.push(`code = $${paramIndex}`);
|
||||
params.push(data.code);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
if (data.description !== undefined) {
|
||||
updates.push(`description = $${paramIndex}`);
|
||||
params.push(data.description);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
if (data.is_active !== undefined) {
|
||||
updates.push(`is_active = $${paramIndex}`);
|
||||
params.push(data.is_active);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
return getById(id);
|
||||
}
|
||||
|
||||
params.push(id);
|
||||
const result = await query<MeterType>(
|
||||
`UPDATE meter_types
|
||||
SET ${updates.join(', ')}
|
||||
WHERE id = $${paramIndex}
|
||||
RETURNING id, name, code, description, is_active, created_at, updated_at`,
|
||||
params
|
||||
);
|
||||
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a meter type (soft delete by setting is_active to false)
|
||||
* @param id - Meter type ID
|
||||
* @returns Promise resolving to boolean indicating success
|
||||
*/
|
||||
export async function remove(id: string): Promise<boolean> {
|
||||
const result = await query(
|
||||
`UPDATE meter_types SET is_active = false WHERE id = $1`,
|
||||
[id]
|
||||
);
|
||||
|
||||
return result.rowCount ? result.rowCount > 0 : false;
|
||||
}
|
||||
@@ -103,7 +103,7 @@ export async function getAll(
|
||||
|
||||
// Get paginated data
|
||||
const dataQuery = `
|
||||
SELECT id, name, description, area_name, location, status, created_by, created_at, updated_at
|
||||
SELECT id, name, description, area_name, location, status, meter_type_id, created_by, created_at, updated_at
|
||||
FROM projects
|
||||
${whereClause}
|
||||
ORDER BY created_at DESC
|
||||
@@ -131,7 +131,7 @@ export async function getAll(
|
||||
*/
|
||||
export async function getById(id: string): Promise<Project | null> {
|
||||
const result = await query<Project>(
|
||||
`SELECT id, name, description, area_name, location, status, created_by, created_at, updated_at
|
||||
`SELECT id, name, description, area_name, location, status, meter_type_id, created_by, created_at, updated_at
|
||||
FROM projects
|
||||
WHERE id = $1`,
|
||||
[id]
|
||||
@@ -148,15 +148,16 @@ export async function getById(id: string): Promise<Project | null> {
|
||||
*/
|
||||
export async function create(data: CreateProjectInput, userId: string): Promise<Project> {
|
||||
const result = await query<Project>(
|
||||
`INSERT INTO projects (name, description, area_name, location, status, created_by)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING id, name, description, area_name, location, status, created_by, created_at, updated_at`,
|
||||
`INSERT INTO projects (name, description, area_name, location, status, meter_type_id, created_by)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING id, name, description, area_name, location, status, meter_type_id, created_by, created_at, updated_at`,
|
||||
[
|
||||
data.name,
|
||||
data.description || null,
|
||||
data.area_name || null,
|
||||
data.location || null,
|
||||
data.status || 'ACTIVE',
|
||||
data.meter_type_id || null,
|
||||
userId,
|
||||
]
|
||||
);
|
||||
@@ -206,6 +207,12 @@ export async function update(id: string, data: UpdateProjectInput): Promise<Proj
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
if (data.meter_type_id !== undefined) {
|
||||
updates.push(`meter_type_id = $${paramIndex}`);
|
||||
params.push(data.meter_type_id);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
// Always update the updated_at timestamp
|
||||
updates.push(`updated_at = NOW()`);
|
||||
|
||||
@@ -220,7 +227,7 @@ export async function update(id: string, data: UpdateProjectInput): Promise<Proj
|
||||
`UPDATE projects
|
||||
SET ${updates.join(', ')}
|
||||
WHERE id = $${paramIndex}
|
||||
RETURNING id, name, description, area_name, location, status, created_by, created_at, updated_at`,
|
||||
RETURNING id, name, description, area_name, location, status, meter_type_id, created_by, created_at, updated_at`,
|
||||
params
|
||||
);
|
||||
|
||||
|
||||
@@ -70,6 +70,20 @@ export interface TokenPair {
|
||||
refreshToken: string;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Meter Type Types
|
||||
// ============================================
|
||||
|
||||
export interface MeterType {
|
||||
id: string;
|
||||
name: string;
|
||||
code: string;
|
||||
description: string | null;
|
||||
is_active: boolean;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Project Types
|
||||
// ============================================
|
||||
@@ -81,6 +95,7 @@ export interface Project {
|
||||
location: string | null;
|
||||
latitude: number | null;
|
||||
longitude: number | null;
|
||||
meter_type_id: string | null;
|
||||
is_active: boolean;
|
||||
created_by: number;
|
||||
created_at: Date;
|
||||
|
||||
@@ -44,6 +44,11 @@ export const createProjectSchema = z.object({
|
||||
.enum([ProjectStatus.ACTIVE, ProjectStatus.INACTIVE, ProjectStatus.COMPLETED])
|
||||
.default(ProjectStatus.ACTIVE)
|
||||
.optional(),
|
||||
meter_type_id: z
|
||||
.string()
|
||||
.uuid('Meter type ID must be a valid UUID')
|
||||
.optional()
|
||||
.nullable(),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -74,6 +79,11 @@ export const updateProjectSchema = z.object({
|
||||
status: z
|
||||
.enum([ProjectStatus.ACTIVE, ProjectStatus.INACTIVE, ProjectStatus.COMPLETED])
|
||||
.optional(),
|
||||
meter_type_id: z
|
||||
.string()
|
||||
.uuid('Meter type ID must be a valid UUID')
|
||||
.optional()
|
||||
.nullable(),
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user