108 lines
3.0 KiB
TypeScript
108 lines
3.0 KiB
TypeScript
|
|
import { NextResponse } from 'next/server';
|
||
|
|
import { z } from 'zod';
|
||
|
|
|
||
|
|
import { type RouteHandler } from '@/lib/api/helpers';
|
||
|
|
import { parseBody } from '@/lib/api/route-helpers';
|
||
|
|
import { requirePermission } from '@/lib/auth/permissions';
|
||
|
|
import { errorResponse } from '@/lib/errors';
|
||
|
|
import {
|
||
|
|
activate,
|
||
|
|
cancel,
|
||
|
|
endReservation,
|
||
|
|
getById,
|
||
|
|
} from '@/lib/services/berth-reservations.service';
|
||
|
|
|
||
|
|
// ─── PATCH body schema (action-based discriminated union) ────────────────────
|
||
|
|
|
||
|
|
const patchBodySchema = z.discriminatedUnion('action', [
|
||
|
|
z.object({
|
||
|
|
action: z.literal('activate'),
|
||
|
|
contractFileId: z.string().optional(),
|
||
|
|
effectiveDate: z.coerce.date().optional(),
|
||
|
|
}),
|
||
|
|
z.object({
|
||
|
|
action: z.literal('end'),
|
||
|
|
endDate: z.coerce.date(),
|
||
|
|
notes: z.string().optional(),
|
||
|
|
}),
|
||
|
|
z.object({
|
||
|
|
action: z.literal('cancel'),
|
||
|
|
reason: z.string().optional(),
|
||
|
|
}),
|
||
|
|
]);
|
||
|
|
|
||
|
|
// ─── Handlers ────────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
export const getHandler: RouteHandler = async (_req, ctx, params) => {
|
||
|
|
try {
|
||
|
|
const reservation = await getById(params.id!, ctx.portId);
|
||
|
|
return NextResponse.json({ data: reservation });
|
||
|
|
} catch (error) {
|
||
|
|
return errorResponse(error);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
export const patchHandler: RouteHandler = async (req, ctx, params) => {
|
||
|
|
try {
|
||
|
|
const body = await parseBody(req, patchBodySchema);
|
||
|
|
const meta = {
|
||
|
|
userId: ctx.userId,
|
||
|
|
portId: ctx.portId,
|
||
|
|
ipAddress: ctx.ipAddress,
|
||
|
|
userAgent: ctx.userAgent,
|
||
|
|
};
|
||
|
|
|
||
|
|
if (body.action === 'activate') {
|
||
|
|
requirePermission(ctx, 'reservations', 'activate');
|
||
|
|
const result = await activate(
|
||
|
|
params.id!,
|
||
|
|
ctx.portId,
|
||
|
|
{
|
||
|
|
contractFileId: body.contractFileId,
|
||
|
|
effectiveDate: body.effectiveDate,
|
||
|
|
},
|
||
|
|
meta,
|
||
|
|
);
|
||
|
|
return NextResponse.json({ data: result });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (body.action === 'end') {
|
||
|
|
// `end` is lifecycle progression; same privilege as activate.
|
||
|
|
requirePermission(ctx, 'reservations', 'activate');
|
||
|
|
const result = await endReservation(
|
||
|
|
params.id!,
|
||
|
|
ctx.portId,
|
||
|
|
{ endDate: body.endDate, notes: body.notes },
|
||
|
|
meta,
|
||
|
|
);
|
||
|
|
return NextResponse.json({ data: result });
|
||
|
|
}
|
||
|
|
|
||
|
|
// action === 'cancel'
|
||
|
|
requirePermission(ctx, 'reservations', 'cancel');
|
||
|
|
const result = await cancel(params.id!, ctx.portId, { reason: body.reason }, meta);
|
||
|
|
return NextResponse.json({ data: result });
|
||
|
|
} catch (error) {
|
||
|
|
return errorResponse(error);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
export const deleteHandler: RouteHandler = async (_req, ctx, params) => {
|
||
|
|
try {
|
||
|
|
await cancel(
|
||
|
|
params.id!,
|
||
|
|
ctx.portId,
|
||
|
|
{},
|
||
|
|
{
|
||
|
|
userId: ctx.userId,
|
||
|
|
portId: ctx.portId,
|
||
|
|
ipAddress: ctx.ipAddress,
|
||
|
|
userAgent: ctx.userAgent,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
return new NextResponse(null, { status: 204 });
|
||
|
|
} catch (error) {
|
||
|
|
return errorResponse(error);
|
||
|
|
}
|
||
|
|
};
|