Task #397: Per-card and bulk select+delete on Incoming Orders

Backend (artifacts/api-server):
- Added hasReceivePermission() helper and refactored DELETE /orders/:id
  into shared authorizeAndDeleteOrder() so single and bulk paths use the
  same authorization rules.
- Added POST /orders/bulk-delete returning { deletedIds, failedIds } with
  per-id authorization, dedup, and best-effort partial success.
- Tightened receiver authorization (per code review): receivers may only
  delete pending/received/preparing orders; terminal (completed/cancelled)
  orders remain the owner's cleanup responsibility. OpenAPI description
  and the implementation now agree.

API spec (lib/api-spec/openapi.yaml):
- New /orders/bulk-delete operation with BulkDeleteServiceOrdersBody and
  BulkDeleteServiceOrdersResponse schemas; updated DELETE /orders/{id}
  description. Regenerated react-query hooks via codegen — useBulkDelete
  ServiceOrders is now available.

UI (artifacts/tx-os/src/pages/orders-incoming.tsx):
- Selection mode toggle in the page header (RTL-safe).
- Per-card Checkbox plus a Select-all control.
- Sticky bottom action bar with destructive Delete and selected count.
- AlertDialog confirmation using pluralized i18n keys; toast surfaces
  partial-failure results when some ids couldn't be deleted.

i18n: New incomingOrders keys in ar.json and en.json (select, selectAll,
clearSelection, selectedCount, delete, deleteConfirmTitle/Body, deleted,
deleteFailed, deletePartial) with pluralization.

Tests: Added two new tests in service-orders.test.mjs covering receiver
delete on incoming queue, receiver forbidden on terminal orders, and
bulk-delete with mixed authorized / unknown / dedup / unauthenticated
cases. Both pass. Pre-existing executive-meetings PDF/font test failures
are unrelated.
This commit is contained in:
riyadhafraa
2026-05-05 13:35:21 +00:00
parent 19fb83b389
commit d62e67af96
9 changed files with 764 additions and 98 deletions
+29 -3
View File
@@ -2000,9 +2000,35 @@ export const DeleteUserQueryParams = zod.object({
});
/**
* Permanently deletes the order. Allowed when the caller owns the order
AND it is in a terminal status (completed/cancelled), OR the caller is
an admin (any status).
* Deletes several orders in one round-trip. Authorization is still
enforced per id using the same rules as DELETE /orders/{id}, so the
client cannot bypass the permission check by batching. Returns a
summary of which ids were deleted and which were rejected.
* @summary Delete multiple service orders in one request
*/
export const bulkDeleteServiceOrdersBodyIdsMax = 200;
export const BulkDeleteServiceOrdersBody = zod.object({
ids: zod.array(zod.number()).min(1).max(bulkDeleteServiceOrdersBodyIdsMax),
});
export const BulkDeleteServiceOrdersResponse = zod.object({
deletedIds: zod
.array(zod.number())
.describe("Ids that were successfully deleted."),
failedIds: zod
.array(zod.number())
.describe(
"Ids that were skipped (not found, or caller is not authorized).",
),
});
/**
* Permanently deletes the order. Allowed when the caller is an admin,
when the caller owns the order AND it is in a terminal status
(completed/cancelled), OR when the caller holds the `orders.receive`
permission (i.e. a receiver clearing items from their incoming queue).
* @summary Delete a service order
*/