seahaven-ap/packages/api/src/routes/gl-accounts.ts

78 lines
2.9 KiB
TypeScript

import { eq } from "drizzle-orm";
import { Hono } from "hono";
import type { Db } from "../db/client.js";
import { glAccounts } from "../db/schema/index.js";
import type { AppBindings } from "../auth/middleware.js";
import { errorJson } from "../http.js";
import { asString, iso, isUuid, parseJsonBody, requireCan } from "./helpers.js";
function toGlAccount(row: typeof glAccounts.$inferSelect) {
return {
id: row.id,
code: row.code,
name: row.name,
createdAt: iso(row.createdAt),
updatedAt: iso(row.updatedAt),
};
}
export function createGlAccountRoutes(handle: Db) {
const routes = new Hono<AppBindings>();
routes.get("/gl-accounts", async (c) => {
const denied = requireCan(c, "read:invoices");
if (denied) return denied;
const rows = await handle.db.select().from(glAccounts);
return c.json({ items: rows.map(toGlAccount) });
});
routes.get("/gl-accounts/:id", async (c) => {
const denied = requireCan(c, "read:invoices");
if (denied) return denied;
const id = c.req.param("id");
if (!isUuid(id)) return errorJson(c, 400, "VALIDATION_ERROR", "Invalid GL account id.");
const row = await handle.db.query.glAccounts.findFirst({ where: eq(glAccounts.id, id) });
if (!row) return errorJson(c, 404, "NOT_FOUND", "GL account not found.");
return c.json(toGlAccount(row));
});
routes.post("/gl-accounts", async (c) => {
const denied = requireCan(c, "admin:settings");
if (denied) return denied;
const body = await parseJsonBody(c);
const code = asString(body.code).trim();
const name = asString(body.name).trim();
if (!code || !name) return errorJson(c, 400, "VALIDATION_ERROR", "Code and name are required.");
const [row] = await handle.db.insert(glAccounts).values({ code, name }).returning();
return c.json(toGlAccount(row), 201);
});
routes.patch("/gl-accounts/:id", async (c) => {
const denied = requireCan(c, "admin:settings");
if (denied) return denied;
const id = c.req.param("id");
if (!isUuid(id)) return errorJson(c, 400, "VALIDATION_ERROR", "Invalid GL account id.");
const existing = await handle.db.query.glAccounts.findFirst({ where: eq(glAccounts.id, id) });
if (!existing) return errorJson(c, 404, "NOT_FOUND", "GL account not found.");
const body = await parseJsonBody(c);
const patch: Partial<typeof glAccounts.$inferInsert> = { updatedAt: new Date() };
if (body.code !== undefined) {
const code = asString(body.code).trim();
if (!code) return errorJson(c, 400, "VALIDATION_ERROR", "Code is required.");
patch.code = code;
}
if (body.name !== undefined) {
const name = asString(body.name).trim();
if (!name) return errorJson(c, 400, "VALIDATION_ERROR", "Name is required.");
patch.name = name;
}
const [row] = await handle.db
.update(glAccounts)
.set(patch)
.where(eq(glAccounts.id, id))
.returning();
return c.json(toGlAccount(row));
});
return routes;
}