mirror of
https://github.com/Sea-Haven-Industries/seahaven-ap.git
synced 2026-10-04 19:31:59 +00:00
78 lines
2.9 KiB
TypeScript
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;
|
|
}
|