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",
|
invoice: "Faktura",
|
||||||
quotation: "Nabídka",
|
quotation: "Nabídka",
|
||||||
order: "Objednávka",
|
order: "Objednávka",
|
||||||
|
issued_order: "Objednávka vydaná",
|
||||||
project: "Projekt",
|
project: "Projekt",
|
||||||
project_file: "Soubor projektu",
|
project_file: "Soubor projektu",
|
||||||
customer: "Zákazník",
|
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 attendanceRoutes from "./routes/admin/attendance";
|
||||||
import customersRoutes from "./routes/admin/customers";
|
import customersRoutes from "./routes/admin/customers";
|
||||||
import invoicesRoutes from "./routes/admin/invoices";
|
import invoicesRoutes from "./routes/admin/invoices";
|
||||||
|
import issuedOrdersRoutes from "./routes/admin/issued-orders";
|
||||||
import quotationsRoutes from "./routes/admin/quotations";
|
import quotationsRoutes from "./routes/admin/quotations";
|
||||||
import ordersRoutes from "./routes/admin/orders";
|
import ordersRoutes from "./routes/admin/orders";
|
||||||
import projectsRoutes from "./routes/admin/projects";
|
import projectsRoutes from "./routes/admin/projects";
|
||||||
@@ -120,6 +121,9 @@ async function start() {
|
|||||||
await app.register(attendanceRoutes, { prefix: "/api/admin/attendance" });
|
await app.register(attendanceRoutes, { prefix: "/api/admin/attendance" });
|
||||||
await app.register(customersRoutes, { prefix: "/api/admin/customers" });
|
await app.register(customersRoutes, { prefix: "/api/admin/customers" });
|
||||||
await app.register(invoicesRoutes, { prefix: "/api/admin/invoices" });
|
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(quotationsRoutes, { prefix: "/api/admin/offers" });
|
||||||
await app.register(ordersRoutes, { prefix: "/api/admin/orders" });
|
await app.register(ordersRoutes, { prefix: "/api/admin/orders" });
|
||||||
await app.register(projectsRoutes, { prefix: "/api/admin/projects" });
|
await app.register(projectsRoutes, { prefix: "/api/admin/projects" });
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ export type EntityType =
|
|||||||
| "invoice"
|
| "invoice"
|
||||||
| "quotation"
|
| "quotation"
|
||||||
| "order"
|
| "order"
|
||||||
|
| "issued_order"
|
||||||
| "project"
|
| "project"
|
||||||
| "customer"
|
| "customer"
|
||||||
| "trip"
|
| "trip"
|
||||||
|
|||||||
Reference in New Issue
Block a user