Initial commit - Horux Despachos NL
This commit is contained in:
160
apps/api/src/services/cartera.service.ts
Normal file
160
apps/api/src/services/cartera.service.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import type { Pool } from 'pg';
|
||||
import { prisma } from '../config/database.js';
|
||||
|
||||
export interface CarteraRow {
|
||||
id: string;
|
||||
supervisorUserId: string | null;
|
||||
auxiliarUserId: string | null;
|
||||
parentId: string | null;
|
||||
nombre: string;
|
||||
descripcion: string | null;
|
||||
createdAt: string;
|
||||
entidadesCount?: number;
|
||||
subcarterasCount?: number;
|
||||
}
|
||||
|
||||
const BASE_SELECT = `
|
||||
SELECT c.id, c.supervisor_user_id AS "supervisorUserId",
|
||||
c.auxiliar_user_id AS "auxiliarUserId", c.parent_id AS "parentId",
|
||||
c.nombre, c.descripcion, c.created_at AS "createdAt",
|
||||
(SELECT count(*) FROM cartera_entidades ce WHERE ce.cartera_id = c.id)::int AS "entidadesCount",
|
||||
(SELECT count(*) FROM carteras sc WHERE sc.parent_id = c.id)::int AS "subcarterasCount"
|
||||
FROM carteras c
|
||||
`;
|
||||
|
||||
/**
|
||||
* List top-level carteras (parent_id IS NULL).
|
||||
* If supervisorUserId is provided, filter by that supervisor.
|
||||
*/
|
||||
export async function listCarteras(pool: Pool, supervisorUserId?: string): Promise<CarteraRow[]> {
|
||||
const conditions = ['c.parent_id IS NULL'];
|
||||
const params: unknown[] = [];
|
||||
if (supervisorUserId) {
|
||||
params.push(supervisorUserId);
|
||||
conditions.push(`c.supervisor_user_id = $${params.length}`);
|
||||
}
|
||||
const { rows } = await pool.query(
|
||||
`${BASE_SELECT} WHERE ${conditions.join(' AND ')} ORDER BY c.created_at DESC`,
|
||||
params,
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* List subcarteras of a parent cartera.
|
||||
*/
|
||||
export async function listSubcarteras(pool: Pool, parentId: string): Promise<CarteraRow[]> {
|
||||
const { rows } = await pool.query(
|
||||
`${BASE_SELECT} WHERE c.parent_id = $1 ORDER BY c.nombre`,
|
||||
[parentId],
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
|
||||
export async function getCarteraById(pool: Pool, id: string): Promise<CarteraRow | null> {
|
||||
const { rows } = await pool.query(`${BASE_SELECT} WHERE c.id = $1`, [id]);
|
||||
return rows[0] ?? null;
|
||||
}
|
||||
|
||||
export async function createCartera(pool: Pool, data: {
|
||||
supervisorUserId: string;
|
||||
nombre: string;
|
||||
descripcion?: string;
|
||||
}): Promise<CarteraRow> {
|
||||
const { rows: [row] } = await pool.query(`
|
||||
INSERT INTO carteras (supervisor_user_id, nombre, descripcion)
|
||||
VALUES ($1, $2, $3) RETURNING id
|
||||
`, [data.supervisorUserId, data.nombre, data.descripcion ?? null]);
|
||||
return (await getCarteraById(pool, row.id))!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a subcartera within a parent cartera, assigned to an auxiliar.
|
||||
*/
|
||||
export async function createSubcartera(pool: Pool, data: {
|
||||
parentId: string;
|
||||
auxiliarUserId: string;
|
||||
nombre: string;
|
||||
descripcion?: string;
|
||||
}): Promise<CarteraRow> {
|
||||
const { rows: [row] } = await pool.query(`
|
||||
INSERT INTO carteras (parent_id, auxiliar_user_id, nombre, descripcion)
|
||||
VALUES ($1, $2, $3, $4) RETURNING id
|
||||
`, [data.parentId, data.auxiliarUserId, data.nombre, data.descripcion ?? null]);
|
||||
return (await getCarteraById(pool, row.id))!;
|
||||
}
|
||||
|
||||
export async function updateCartera(pool: Pool, id: string, data: {
|
||||
nombre?: string;
|
||||
descripcion?: string;
|
||||
supervisorUserId?: string;
|
||||
}): Promise<CarteraRow | null> {
|
||||
const existing = await getCarteraById(pool, id);
|
||||
if (!existing) return null;
|
||||
const sets: string[] = [];
|
||||
const vals: unknown[] = [];
|
||||
let idx = 1;
|
||||
if (data.nombre !== undefined) { sets.push(`nombre = $${idx}`); vals.push(data.nombre); idx++; }
|
||||
if (data.descripcion !== undefined) { sets.push(`descripcion = $${idx}`); vals.push(data.descripcion); idx++; }
|
||||
if (data.supervisorUserId !== undefined) { sets.push(`supervisor_user_id = $${idx}`); vals.push(data.supervisorUserId); idx++; }
|
||||
if (sets.length === 0) return existing;
|
||||
vals.push(id);
|
||||
await pool.query(`UPDATE carteras SET ${sets.join(', ')} WHERE id = $${idx}`, vals);
|
||||
return (await getCarteraById(pool, id))!;
|
||||
}
|
||||
|
||||
export async function deleteCartera(pool: Pool, id: string): Promise<boolean> {
|
||||
const { rowCount } = await pool.query('DELETE FROM carteras WHERE id = $1', [id]);
|
||||
return (rowCount ?? 0) > 0;
|
||||
}
|
||||
|
||||
// Entidades in cartera
|
||||
export async function addEntidadToCartera(pool: Pool, carteraId: string, entidadId: string): Promise<void> {
|
||||
await pool.query('INSERT INTO cartera_entidades (cartera_id, entidad_id) VALUES ($1, $2) ON CONFLICT DO NOTHING', [carteraId, entidadId]);
|
||||
}
|
||||
|
||||
export async function removeEntidadFromCartera(pool: Pool, carteraId: string, entidadId: string): Promise<void> {
|
||||
await pool.query('DELETE FROM cartera_entidades WHERE cartera_id = $1 AND entidad_id = $2', [carteraId, entidadId]);
|
||||
}
|
||||
|
||||
export async function getCarteraEntidades(pool: Pool, carteraId: string): Promise<string[]> {
|
||||
const { rows } = await pool.query('SELECT entidad_id AS "entidadId" FROM cartera_entidades WHERE cartera_id = $1', [carteraId]);
|
||||
return rows.map(r => r.entidadId);
|
||||
}
|
||||
|
||||
// Auxiliares assigned to a supervisor
|
||||
export async function getAuxiliaresDelSupervisor(pool: Pool, supervisorUserId: string): Promise<Array<{ auxiliarUserId: string }>> {
|
||||
const { rows } = await pool.query(
|
||||
'SELECT auxiliar_user_id AS "auxiliarUserId" FROM auxiliar_supervisores WHERE supervisor_user_id = $1',
|
||||
[supervisorUserId],
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
|
||||
// Legacy auxiliares in cartera (backward compat)
|
||||
export async function addAuxiliarToCartera(pool: Pool, carteraId: string, auxiliarUserId: string): Promise<void> {
|
||||
await pool.query('INSERT INTO cartera_auxiliares (cartera_id, auxiliar_user_id) VALUES ($1, $2) ON CONFLICT DO NOTHING', [carteraId, auxiliarUserId]);
|
||||
}
|
||||
|
||||
export async function removeAuxiliarFromCartera(pool: Pool, carteraId: string, auxiliarUserId: string): Promise<void> {
|
||||
await pool.query('DELETE FROM cartera_auxiliares WHERE cartera_id = $1 AND auxiliar_user_id = $2', [carteraId, auxiliarUserId]);
|
||||
}
|
||||
|
||||
export async function getCarteraAuxiliares(pool: Pool, carteraId: string): Promise<string[]> {
|
||||
const { rows } = await pool.query('SELECT auxiliar_user_id AS "auxiliarUserId" FROM cartera_auxiliares WHERE cartera_id = $1', [carteraId]);
|
||||
return rows.map(r => r.auxiliarUserId);
|
||||
}
|
||||
|
||||
// Supervisors list (for the invite form dropdown)
|
||||
export async function getSupervisores(pool: Pool, tenantId: string): Promise<Array<{ userId: string; nombre: string; email: string }>> {
|
||||
// Query tenant_memberships joined with users for supervisor role (rolId=9)
|
||||
const memberships = await prisma.tenantMembership.findMany({
|
||||
where: { tenantId, rolId: 9, active: true },
|
||||
include: { user: { select: { id: true, nombre: true, email: true } } },
|
||||
});
|
||||
return memberships.map(m => ({
|
||||
userId: m.user.id,
|
||||
nombre: m.user.nombre,
|
||||
email: m.user.email,
|
||||
}));
|
||||
}
|
||||
Reference in New Issue
Block a user