feat(orders): issued-orders CRUD routes + audit entity type
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ export const ENTITY_TYPE_LABELS: Record<string, string> = {
|
||||
invoice: "Faktura",
|
||||
quotation: "Nabídka",
|
||||
order: "Objednávka",
|
||||
issued_order: "Objednávka vydaná",
|
||||
project: "Projekt",
|
||||
project_file: "Soubor projektu",
|
||||
customer: "Zákazník",
|
||||
|
||||
146
src/routes/admin/issued-orders.ts
Normal file
146
src/routes/admin/issued-orders.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { FastifyInstance } from "fastify";
|
||||
import { requirePermission } from "../../middleware/auth";
|
||||
import { logAudit } from "../../services/audit";
|
||||
import { success, error, parseId, paginated } from "../../utils/response";
|
||||
import { parsePagination, buildPaginationMeta } from "../../utils/pagination";
|
||||
import { parseBody } from "../../schemas/common";
|
||||
import {
|
||||
CreateIssuedOrderSchema,
|
||||
UpdateIssuedOrderSchema,
|
||||
} from "../../schemas/issued-orders.schema";
|
||||
import {
|
||||
listIssuedOrders,
|
||||
getIssuedOrder,
|
||||
createIssuedOrder,
|
||||
updateIssuedOrder,
|
||||
deleteIssuedOrder,
|
||||
getNextIssuedOrderNumberPreview,
|
||||
} from "../../services/issued-orders.service";
|
||||
|
||||
export default async function issuedOrdersRoutes(fastify: FastifyInstance) {
|
||||
// GET /api/admin/issued-orders
|
||||
fastify.get(
|
||||
"/",
|
||||
{ preHandler: requirePermission("orders.view") },
|
||||
async (request, reply) => {
|
||||
const query = request.query as Record<string, unknown>;
|
||||
const { page, limit, skip, order, search } = parsePagination(query);
|
||||
const result = await listIssuedOrders({
|
||||
page,
|
||||
limit,
|
||||
skip,
|
||||
sort: String(query.sort || ""),
|
||||
order,
|
||||
search,
|
||||
status: query.status ? String(query.status) : undefined,
|
||||
customer_id: query.customer_id ? Number(query.customer_id) : undefined,
|
||||
});
|
||||
return paginated(
|
||||
reply,
|
||||
result.data,
|
||||
buildPaginationMeta(result.total, page, limit),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// GET /api/admin/issued-orders/next-number
|
||||
fastify.get(
|
||||
"/next-number",
|
||||
{ preHandler: requirePermission("orders.create") },
|
||||
async (_request, reply) => {
|
||||
return success(reply, await getNextIssuedOrderNumberPreview());
|
||||
},
|
||||
);
|
||||
|
||||
// GET /api/admin/issued-orders/:id
|
||||
fastify.get<{ Params: { id: string } }>(
|
||||
"/:id",
|
||||
{ preHandler: requirePermission("orders.view") },
|
||||
async (request, reply) => {
|
||||
const id = parseId(request.params.id, reply);
|
||||
if (id === null) return;
|
||||
const order = await getIssuedOrder(id);
|
||||
if (!order) return error(reply, "Objednávka nenalezena", 404);
|
||||
return success(reply, order);
|
||||
},
|
||||
);
|
||||
|
||||
// POST /api/admin/issued-orders
|
||||
fastify.post(
|
||||
"/",
|
||||
{ preHandler: requirePermission("orders.create") },
|
||||
async (request, reply) => {
|
||||
const parsed = parseBody(CreateIssuedOrderSchema, request.body);
|
||||
if ("error" in parsed) return error(reply, parsed.error, 400);
|
||||
const order = await createIssuedOrder(parsed.data);
|
||||
await logAudit({
|
||||
request,
|
||||
authData: request.authData,
|
||||
action: "create",
|
||||
entityType: "issued_order",
|
||||
entityId: order.id,
|
||||
description: `Vytvořena vydaná objednávka ${order.po_number}`,
|
||||
});
|
||||
return success(
|
||||
reply,
|
||||
{ id: order.id, po_number: order.po_number },
|
||||
201,
|
||||
"Objednávka byla vytvořena",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// PUT /api/admin/issued-orders/:id
|
||||
fastify.put<{ Params: { id: string } }>(
|
||||
"/:id",
|
||||
{ preHandler: requirePermission("orders.edit") },
|
||||
async (request, reply) => {
|
||||
const id = parseId(request.params.id, reply);
|
||||
if (id === null) return;
|
||||
const parsed = parseBody(UpdateIssuedOrderSchema, request.body);
|
||||
if ("error" in parsed) return error(reply, parsed.error, 400);
|
||||
const result = await updateIssuedOrder(id, parsed.data);
|
||||
if ("error" in result) {
|
||||
if (result.error === "not_found")
|
||||
return error(reply, "Objednávka nenalezena", 404);
|
||||
if (result.error === "invalid_transition")
|
||||
return error(
|
||||
reply,
|
||||
`Neplatný přechod stavu z "${result.currentStatus}" na "${result.newStatus}"`,
|
||||
400,
|
||||
);
|
||||
return error(reply, "Neznámá chyba", 500);
|
||||
}
|
||||
await logAudit({
|
||||
request,
|
||||
authData: request.authData,
|
||||
action: "update",
|
||||
entityType: "issued_order",
|
||||
entityId: id,
|
||||
description: `Upravena vydaná objednávka ${result.po_number}`,
|
||||
});
|
||||
return success(reply, { id }, 200, "Objednávka byla aktualizována");
|
||||
},
|
||||
);
|
||||
|
||||
// DELETE /api/admin/issued-orders/:id
|
||||
fastify.delete<{ Params: { id: string } }>(
|
||||
"/:id",
|
||||
{ preHandler: requirePermission("orders.delete") },
|
||||
async (request, reply) => {
|
||||
const id = parseId(request.params.id, reply);
|
||||
if (id === null) return;
|
||||
const existing = await deleteIssuedOrder(id);
|
||||
if (!existing) return error(reply, "Objednávka nenalezena", 404);
|
||||
await logAudit({
|
||||
request,
|
||||
authData: request.authData,
|
||||
action: "delete",
|
||||
entityType: "issued_order",
|
||||
entityId: id,
|
||||
description: `Smazána vydaná objednávka ${existing.po_number}`,
|
||||
});
|
||||
return success(reply, null, 200, "Objednávka smazána");
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import rolesRoutes from "./routes/admin/roles";
|
||||
import attendanceRoutes from "./routes/admin/attendance";
|
||||
import customersRoutes from "./routes/admin/customers";
|
||||
import invoicesRoutes from "./routes/admin/invoices";
|
||||
import issuedOrdersRoutes from "./routes/admin/issued-orders";
|
||||
import quotationsRoutes from "./routes/admin/quotations";
|
||||
import ordersRoutes from "./routes/admin/orders";
|
||||
import projectsRoutes from "./routes/admin/projects";
|
||||
@@ -120,6 +121,9 @@ async function start() {
|
||||
await app.register(attendanceRoutes, { prefix: "/api/admin/attendance" });
|
||||
await app.register(customersRoutes, { prefix: "/api/admin/customers" });
|
||||
await app.register(invoicesRoutes, { prefix: "/api/admin/invoices" });
|
||||
await app.register(issuedOrdersRoutes, {
|
||||
prefix: "/api/admin/issued-orders",
|
||||
});
|
||||
await app.register(quotationsRoutes, { prefix: "/api/admin/offers" });
|
||||
await app.register(ordersRoutes, { prefix: "/api/admin/orders" });
|
||||
await app.register(projectsRoutes, { prefix: "/api/admin/projects" });
|
||||
|
||||
@@ -129,6 +129,7 @@ export type EntityType =
|
||||
| "invoice"
|
||||
| "quotation"
|
||||
| "order"
|
||||
| "issued_order"
|
||||
| "project"
|
||||
| "customer"
|
||||
| "trip"
|
||||
|
||||
Reference in New Issue
Block a user