This commit is contained in:
2025-10-16 19:49:50 +00:00
parent ba6b4fef4f
commit c4097bc737
119 changed files with 3765 additions and 14390 deletions
@@ -0,0 +1,111 @@
// packages/api/v1/repositories/comandasRepo.mjs
import { withTenantClient } from './db.mjs';
import { loadColumns, loadPrimaryKey } from '../routes/utils/schemaInspector.mjs';
const TABLE = 'comandas';
const VALID_IDENT = /^[a-z_][a-z0-9_]*$/i;
export async function listComandas({ schema, abierta, limit }) {
return withTenantClient(schema, async (db) => {
const max = Math.min(parseInt(limit || 200, 10), 1000);
const { rows } = await db.query(
`SELECT * FROM public.f_comandas_resumen($1, $2)`,
[abierta, max]
);
return rows;
});
}
export async function getDetalleItems({ schema, id }) {
return withTenantClient(schema, async (db) => {
const { rows } = await db.query(
`SELECT id_det_comanda, id_producto, producto_nombre,
cantidad, pre_unitario, subtotal, observaciones
FROM public.v_comandas_detalle_items
WHERE id_comanda = $1::int
ORDER BY id_det_comanda`,
[id]
);
return rows;
});
}
export async function abrirComanda({ schema, id }) {
return withTenantClient(schema, async (db) => {
const st = await db.query(`SELECT eliminada FROM public.${q(TABLE)} WHERE id_comanda = $1`, [id]);
if (!st.rowCount) return null;
if (st.rows[0].eliminada === true) {
const err = new Error('Comanda eliminada. Debe restaurarse antes de abrir.');
err.http = { status: 409 };
throw err;
}
const { rows } = await db.query(`SELECT public.f_abrir_comanda($1) AS data`, [id]);
return rows[0]?.data || null;
});
}
export async function cerrarComanda({ schema, id }) {
return withTenantClient(schema, async (db) => {
const { rows } = await db.query(`SELECT public.f_cerrar_comanda($1) AS data`, [id]);
return rows[0]?.data || null;
});
}
export async function restaurarComanda({ schema, id }) {
return withTenantClient(schema, async (db) => {
const { rows } = await db.query(`SELECT public.f_restaurar_comanda($1) AS data`, [id]);
return rows[0]?.data || null;
});
}
export async function eliminarComanda({ schema, id }) {
return withTenantClient(schema, async (db) => {
const { rows } = await db.query(`SELECT public.f_eliminar_comanda($1) AS data`, [id]);
return rows[0]?.data || null;
});
}
export async function patchComanda({ schema, id, payload }) {
return withTenantClient(schema, async (db) => {
const columns = await loadColumns(db, TABLE);
const updatable = new Set(
columns
.filter(c =>
!c.is_primary &&
!c.is_identity &&
!(String(c.column_default || '').startsWith('nextval('))
)
.map(c => c.column_name)
);
const sets = [];
const params = [];
let idx = 1;
for (const [k, v] of Object.entries(payload || {})) {
if (!VALID_IDENT.test(k)) continue;
if (!updatable.has(k)) continue;
sets.push(`${q(k)} = $${idx++}`);
params.push(v);
}
if (!sets.length) return { error: 'Nada para actualizar' };
const pks = await loadPrimaryKey(db, TABLE);
if (pks.length !== 1) {
const err = new Error('PK compuesta no soportada');
err.http = { status: 400 };
throw err;
}
params.push(id);
const { rows } = await db.query(
`UPDATE ${q(TABLE)} SET ${sets.join(', ')} WHERE ${q(pks[0])} = $${idx} RETURNING *`,
params
);
return rows[0] || null;
});
}
function q(ident) {
return `"${String(ident).replace(/"/g, '""')}"`;
}
+29
View File
@@ -0,0 +1,29 @@
// packages/api/v1/repositories/db.mjs
import { poolTenants } from '@suitecoffee/db';
const VALID_IDENT = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
export async function withTenantClient(req, fn, { trx = false } = {}) {
const schema = req?.tenant?.schema;
if (!schema || !VALID_IDENT.test(schema)) {
throw new Error('Schema de tenant no resuelto/ inválido');
}
const client = await poolTenants.connect();
try {
if (trx) await client.query('BEGIN');
await client.query(`SET LOCAL search_path = "${schema}", public`);
const result = await fn(client);
if (trx) await client.query('COMMIT');
return result;
} catch (e) {
if (trx) await client.query('ROLLBACK');
throw e;
} finally {
client.release();
}
}
export async function tquery(req, sql, params = [], opts = {}) {
return withTenantClient(req, (c) => c.query(sql, params), opts);
}