feat(berths): bulk price update + per-berth price API

Two new endpoints lift price editing out of the full berth-update form:

- `PATCH /api/v1/berths/[id]/price` — single-berth price edit triggered
  inline from the berth list / detail (no need to open the heavy edit
  modal just to retag a price).
- `POST /api/v1/berths/bulk-update-prices` — multi-row update from a
  selection in the berth list; transactional, audit-logged per row.

Berth list column gets an inline price-edit affordance backed by the
single-berth endpoint; the bulk action lives in the row-selection
toolbar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 15:54:27 +02:00
parent b4bf9cca3f
commit 8c669e2918
5 changed files with 330 additions and 2 deletions

View File

@@ -0,0 +1,34 @@
import { NextResponse } from 'next/server';
import { withAuth, withPermission } from '@/lib/api/helpers';
import { parseBody } from '@/lib/api/route-helpers';
import { updateBerthPriceSchema } from '@/lib/validators/berths';
import { updateBerthPrice } from '@/lib/services/berths.service';
import { errorResponse } from '@/lib/errors';
/**
* PATCH /api/v1/berths/[id]/price
*
* Focused price-update endpoint gated by the dedicated
* `berths.update_prices` permission. Lets a role mutate berth pricing
* without granting the full `berths.edit` surface.
*
* Always audited (one `audit_log` row per call with
* `field_changed='price'` and the before/after values).
*/
export const PATCH = withAuth(
withPermission('berths', 'update_prices', async (req, ctx, params) => {
try {
const body = await parseBody(req, updateBerthPriceSchema);
const updated = await updateBerthPrice(params.id!, ctx.portId, body, {
userId: ctx.userId,
portId: ctx.portId,
ipAddress: ctx.ipAddress,
userAgent: ctx.userAgent,
});
return NextResponse.json({ data: updated });
} catch (error) {
return errorResponse(error);
}
}),
);

View File

@@ -0,0 +1,35 @@
import { NextResponse } from 'next/server';
import { withAuth, withPermission } from '@/lib/api/helpers';
import { parseBody } from '@/lib/api/route-helpers';
import { bulkUpdateBerthPricesSchema } from '@/lib/validators/berths';
import { bulkUpdateBerthPrices } from '@/lib/services/berths.service';
import { errorResponse } from '@/lib/errors';
/**
* POST /api/v1/berths/bulk-update-prices
*
* Bulk update berth prices in a single transaction (up to 500 per call).
* Gated by `berths.update_prices`. Returns counts so the UI can present
* "Updated N · Unchanged M · Missing K" feedback.
*
* Audit: one `audit_log` row per actually-updated berth (idempotent —
* berths whose new price matches the existing value are skipped and
* counted as `unchanged`).
*/
export const POST = withAuth(
withPermission('berths', 'update_prices', async (req, ctx) => {
try {
const body = await parseBody(req, bulkUpdateBerthPricesSchema);
const result = await bulkUpdateBerthPrices(ctx.portId, body, {
userId: ctx.userId,
portId: ctx.portId,
ipAddress: ctx.ipAddress,
userAgent: ctx.userAgent,
});
return NextResponse.json({ data: result });
} catch (error) {
return errorResponse(error);
}
}),
);