mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 12:03:17 +00:00
El árbol de Almacén deja de listar todas las obras. El almacén global siempre está disponible y consolida el stock de los almacenes abiertos. Las obras abren almacén a demanda y al cerrarlo pueden devolver el remanente al global. Entradas y traspasos usan el catálogo de materiales con unidades de medida y uno o varios proveedores. Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
224 lines
9.6 KiB
TypeScript
224 lines
9.6 KiB
TypeScript
import type { Context, Hono } from "hono";
|
|
import type { AuthUser } from "./auth.ts";
|
|
import { tenantScope } from "./auth.ts";
|
|
import { requireCoreAuth } from "./scope.ts";
|
|
import type { Db } from "./db.ts";
|
|
import { callCoreFn } from "./rpc.ts";
|
|
import { respondApiError, respondRpc, routeLabel } from "./http_errors.ts";
|
|
import { requirePermission } from "./permissions.ts";
|
|
import { denyUnlessProjectScope, denyUnlessWarehouseCentral } from "./scope_enforcement.ts";
|
|
import { getUserScope } from "./user_scope.ts";
|
|
|
|
type App = Hono<{ Variables: { user: AuthUser; db: Db } }>;
|
|
|
|
function tid(c: { get: (k: "user") => AuthUser }): number {
|
|
return tenantScope(c.get("user")) ?? 0;
|
|
}
|
|
|
|
function actor(c: Context): { id: number; name: string } {
|
|
const u = c.get("user");
|
|
return { id: u.id, name: u.display_name };
|
|
}
|
|
|
|
async function filterWarehouseList(c: Context, env: Awaited<ReturnType<typeof callCoreFn>>) {
|
|
const user = c.get("user") as AuthUser;
|
|
if (!env.ok || !env.data || user.realm === "platform" || user.is_owner) return env;
|
|
const scope = await getUserScope(user);
|
|
const warehouses = (env.data as { warehouses?: Array<{ id: number; kind?: string; project_id?: number | null }> }).warehouses ?? [];
|
|
const filtered = warehouses.filter((wh) => {
|
|
if (wh.kind === "central") return scope.warehouseCentral;
|
|
if (wh.kind === "project") {
|
|
if (!scope.warehouseProjects) return false;
|
|
if (scope.allProjects) return true;
|
|
return scope.projectIds.includes(Number(wh.project_id));
|
|
}
|
|
return true;
|
|
});
|
|
return { ...env, data: { ...(env.data as object), warehouses: filtered } };
|
|
}
|
|
|
|
export function registerWarehouseRoutes(app: App) {
|
|
app.get("/v1/warehouses", ...requireCoreAuth, requirePermission("warehouse.view"), async (c) => {
|
|
const db = c.get("db");
|
|
const env = await callCoreFn(db, "core.fn_warehouse_list", {
|
|
tenant_id: tid(c),
|
|
include_closed: c.req.query("include_closed") === "true",
|
|
}, { route: routeLabel(c) });
|
|
return respondRpc(c, await filterWarehouseList(c, env));
|
|
});
|
|
|
|
app.post("/v1/warehouses/central/ensure", ...requireCoreAuth, requirePermission("warehouse.update"), async (c) => {
|
|
if (!await denyUnlessWarehouseCentral(c)) return;
|
|
const db = c.get("db");
|
|
const env = await callCoreFn(db, "core.fn_warehouse_ensure_central", { tenant_id: tid(c) }, { route: routeLabel(c) });
|
|
return respondRpc(c, env);
|
|
});
|
|
|
|
app.post("/v1/projects/:id/warehouse/open", ...requireCoreAuth, requirePermission("warehouse.update"), async (c) => {
|
|
const projectId = Number(c.req.param("id"));
|
|
if (!await denyUnlessProjectScope(c, projectId)) return;
|
|
const db = c.get("db");
|
|
const env = await callCoreFn(db, "core.fn_warehouse_open_project", {
|
|
tenant_id: tid(c),
|
|
project_id: projectId,
|
|
}, { route: routeLabel(c) });
|
|
return respondRpc(c, env);
|
|
});
|
|
|
|
app.post("/v1/projects/:id/warehouse/close", ...requireCoreAuth, requirePermission("warehouse.update"), async (c) => {
|
|
const projectId = Number(c.req.param("id"));
|
|
if (!await denyUnlessProjectScope(c, projectId)) return;
|
|
const body = await c.req.json<{ force?: boolean; close_notes?: string; transfer_remaining?: boolean }>().catch(
|
|
() => ({} as { force?: boolean; close_notes?: string; transfer_remaining?: boolean }),
|
|
);
|
|
const db = c.get("db");
|
|
const a = actor(c);
|
|
const env = await callCoreFn(db, "core.fn_warehouse_close_project", {
|
|
project_id: projectId,
|
|
force: body.force,
|
|
transfer_remaining: body.transfer_remaining,
|
|
closed_by_name: a.name,
|
|
close_notes: body.close_notes,
|
|
}, { route: routeLabel(c) });
|
|
return respondRpc(c, env);
|
|
});
|
|
|
|
app.get("/v1/warehouses/:id/stock", ...requireCoreAuth, requirePermission("warehouse.view"), async (c) => {
|
|
const db = c.get("db");
|
|
const whId = Number(c.req.param("id"));
|
|
const wh = await db.prepare("SELECT kind, project_id FROM warehouses WHERE id = ?").get(whId) as
|
|
| { kind: string; project_id: number | null }
|
|
| undefined;
|
|
if (!wh) return respondApiError(c, "NOT_FOUND", "Almacén no encontrado", { route: routeLabel(c), warehouse_id: whId });
|
|
if (wh.kind === "central") {
|
|
if (!await denyUnlessWarehouseCentral(c)) return;
|
|
} else if (!await denyUnlessProjectScope(c, wh.project_id)) return;
|
|
const env = await callCoreFn(db, "core.fn_warehouse_stock_list", {
|
|
tenant_id: tid(c),
|
|
warehouse_id: whId,
|
|
global: false,
|
|
}, { route: routeLabel(c) });
|
|
return respondRpc(c, env);
|
|
});
|
|
|
|
app.get("/v1/warehouses/stock/global", ...requireCoreAuth, requirePermission("warehouse.view"), async (c) => {
|
|
if (!await denyUnlessWarehouseCentral(c)) return;
|
|
const db = c.get("db");
|
|
const env = await callCoreFn(db, "core.fn_warehouse_stock_list", {
|
|
tenant_id: tid(c),
|
|
global: true,
|
|
}, { route: routeLabel(c) });
|
|
return respondRpc(c, env);
|
|
});
|
|
|
|
app.get("/v1/warehouses/:id/movements", ...requireCoreAuth, requirePermission("warehouse.view"), async (c) => {
|
|
const db = c.get("db");
|
|
const whId = Number(c.req.param("id"));
|
|
const wh = await db.prepare("SELECT kind, project_id FROM warehouses WHERE id = ?").get(whId) as
|
|
| { kind: string; project_id: number | null }
|
|
| undefined;
|
|
if (!wh) return respondApiError(c, "NOT_FOUND", "Almacén no encontrado", { route: routeLabel(c), warehouse_id: whId });
|
|
if (wh.kind === "central") {
|
|
if (!await denyUnlessWarehouseCentral(c)) return;
|
|
} else if (!await denyUnlessProjectScope(c, wh.project_id)) return;
|
|
const env = await callCoreFn(db, "core.fn_warehouse_movement_list", {
|
|
tenant_id: tid(c),
|
|
warehouse_id: whId,
|
|
material_id: c.req.query("material_id"),
|
|
}, { route: routeLabel(c) });
|
|
return respondRpc(c, env);
|
|
});
|
|
|
|
app.get("/v1/warehouses/movements/global", ...requireCoreAuth, requirePermission("warehouse.view"), async (c) => {
|
|
if (!await denyUnlessWarehouseCentral(c)) return;
|
|
const db = c.get("db");
|
|
const env = await callCoreFn(db, "core.fn_warehouse_movement_list", {
|
|
tenant_id: tid(c),
|
|
global: true,
|
|
material_id: c.req.query("material_id"),
|
|
}, { route: routeLabel(c) });
|
|
return respondRpc(c, env);
|
|
});
|
|
|
|
app.get("/v1/warehouse-materials", ...requireCoreAuth, requirePermission("warehouse.view"), async (c) => {
|
|
const db = c.get("db");
|
|
const env = await callCoreFn(db, "core.fn_warehouse_material_list", {
|
|
tenant_id: tid(c),
|
|
}, { route: routeLabel(c) });
|
|
return respondRpc(c, env);
|
|
});
|
|
|
|
app.post("/v1/warehouse-materials", ...requireCoreAuth, requirePermission("warehouse.create"), async (c) => {
|
|
const body = await c.req.json<Record<string, unknown>>();
|
|
const db = c.get("db");
|
|
const env = await callCoreFn(db, "core.fn_warehouse_material_upsert", { ...body, tenant_id: tid(c) }, { route: routeLabel(c) });
|
|
return respondRpc(c, env);
|
|
});
|
|
|
|
app.patch("/v1/warehouse-materials/:id", ...requireCoreAuth, requirePermission("warehouse.update"), async (c) => {
|
|
const body = await c.req.json<Record<string, unknown>>();
|
|
const db = c.get("db");
|
|
const env = await callCoreFn(db, "core.fn_warehouse_material_upsert", {
|
|
...body,
|
|
id: Number(c.req.param("id")),
|
|
tenant_id: tid(c),
|
|
}, { route: routeLabel(c) });
|
|
return respondRpc(c, env);
|
|
});
|
|
|
|
app.post("/v1/warehouses/:id/entries", ...requireCoreAuth, requirePermission("warehouse.create"), async (c) => {
|
|
const whId = Number(c.req.param("id"));
|
|
const db = c.get("db");
|
|
const wh = await db.prepare("SELECT kind, project_id FROM warehouses WHERE id = ?").get(whId) as
|
|
| { kind: string; project_id: number | null }
|
|
| undefined;
|
|
if (!wh) return respondApiError(c, "NOT_FOUND", "Almacén no encontrado", { route: routeLabel(c), warehouse_id: whId });
|
|
if (wh.kind === "central") {
|
|
if (!await denyUnlessWarehouseCentral(c)) return;
|
|
} else if (!await denyUnlessProjectScope(c, wh.project_id)) return;
|
|
const body = await c.req.json<Record<string, unknown>>();
|
|
const a = actor(c);
|
|
const env = await callCoreFn(db, "core.fn_warehouse_entry_create", {
|
|
...body,
|
|
tenant_id: tid(c),
|
|
warehouse_id: whId,
|
|
created_by_id: a.id,
|
|
created_by_name: a.name,
|
|
}, { route: routeLabel(c) });
|
|
return respondRpc(c, env);
|
|
});
|
|
|
|
app.post("/v1/warehouses/:id/exits", ...requireCoreAuth, requirePermission("warehouse.update"), async (c) => {
|
|
const whId = Number(c.req.param("id"));
|
|
const db = c.get("db");
|
|
const wh = await db.prepare("SELECT kind, project_id FROM warehouses WHERE id = ?").get(whId) as
|
|
| { kind: string; project_id: number | null }
|
|
| undefined;
|
|
if (!wh) return respondApiError(c, "NOT_FOUND", "Almacén no encontrado", { route: routeLabel(c), warehouse_id: whId });
|
|
if (wh.kind === "central") {
|
|
if (!await denyUnlessWarehouseCentral(c)) return;
|
|
} else if (!await denyUnlessProjectScope(c, wh.project_id)) return;
|
|
const body = await c.req.json<Record<string, unknown>>();
|
|
const a = actor(c);
|
|
const env = await callCoreFn(db, "core.fn_warehouse_exit_create", {
|
|
...body,
|
|
tenant_id: tid(c),
|
|
warehouse_id: whId,
|
|
created_by_id: a.id,
|
|
created_by_name: a.name,
|
|
}, { route: routeLabel(c) });
|
|
return respondRpc(c, env);
|
|
});
|
|
|
|
app.post("/v1/warehouse-transfers", ...requireCoreAuth, requirePermission("warehouse.update"), async (c) => {
|
|
const body = await c.req.json<Record<string, unknown>>();
|
|
const db = c.get("db");
|
|
const a = actor(c);
|
|
const env = await callCoreFn(db, "core.fn_warehouse_transfer_create", {
|
|
...body,
|
|
tenant_id: tid(c),
|
|
created_by_name: a.name,
|
|
}, { route: routeLabel(c) });
|
|
return respondRpc(c, env);
|
|
});
|
|
}
|