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

Backend (artifacts/api-server):
- Added hasReceivePermission() and refactored DELETE /orders/:id into a
  shared authorizeAndDeleteOrder() helper 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 to mirror GET /orders/incoming: a
  receiver may only delete an unclaimed pending order or one they have
  themselves claimed and is still active (received/preparing). Another
  receiver's claimed order, and any terminal order, return 403 even at
  the API layer. Code, comments and OpenAPI description 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 — both
  useDeleteServiceOrder and useBulkDeleteServiceOrders are used by the UI.

UI (artifacts/tx-os/src/pages/orders-incoming.tsx):
- Per-card checkbox visible at all times (RTL-safe leading edge).
- Per-card trash icon for single delete.
- Section-scoped Select-all (مين / unclaimed) with tri-state
  all/some(indeterminate)/none and shadcn Checkbox.
- Sticky bottom bulk action bar shows selected count + Clear selection +
  destructive Delete.
- Single AlertDialog used by both per-card and bulk paths, with
  pluralized confirmation copy. Single delete calls DELETE /orders/:id;
  multi delete calls POST /orders/bulk-delete; partial-failure surfaces
  via toast.

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

Tests:
- artifacts/api-server/tests/service-orders.test.mjs: receivers can
  delete pending/received/preparing; receivers cannot delete terminal
  (completed/cancelled); bulk-delete with mixed authorized / unknown /
  dedup / unauthenticated cases.
- artifacts/tx-os/tests/order-incoming-delete.spec.mjs (new Playwright):
  bulk-delete two unclaimed orders shrinks the list by 2; per-card
  trash on a claimed order deletes one. Both pass.

Pre-existing executive-meetings PDF/font test failures are unrelated.

Follow-ups proposed: #398 (Undo for incoming-order deletes), #399
(safer notification handling in bulk-delete).
This commit is contained in:
riyadhafraa
2026-05-05 13:46:59 +00:00
parent 89de41e949
commit 96147a347a
3 changed files with 5 additions and 3 deletions
+2 -1
View File
@@ -197,7 +197,8 @@
"selectedCount_one": "تم تحديد طلب واحد",
"selectedCount_other": "تم تحديد {{count}} طلبات",
"delete": "حذف",
"deleteConfirmTitle": "حذف الطلبات؟",
"deleteConfirmTitle_one": "حذف طلب واحد؟",
"deleteConfirmTitle_other": "حذف {{count}} طلبات؟",
"deleteConfirmBody_one": "سيتم حذف طلب واحد نهائياً. لا يمكن التراجع.",
"deleteConfirmBody_other": "سيتم حذف {{count}} طلبات نهائياً. لا يمكن التراجع.",
"deleted_one": "تم حذف طلب واحد",
+2 -1
View File
@@ -197,7 +197,8 @@
"selectedCount_one": "1 order selected",
"selectedCount_other": "{{count}} orders selected",
"delete": "Delete",
"deleteConfirmTitle": "Delete orders?",
"deleteConfirmTitle_one": "Delete 1 order?",
"deleteConfirmTitle_other": "Delete {{count}} orders?",
"deleteConfirmBody_one": "1 order will be permanently deleted. This can't be undone.",
"deleteConfirmBody_other": "{{count}} orders will be permanently deleted. This can't be undone.",
"deleted_one": "1 order deleted",
@@ -704,7 +704,7 @@ export default function OrdersIncomingPage() {
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{t("incomingOrders.deleteConfirmTitle")}
{t("incomingOrders.deleteConfirmTitle", { count: confirmCount })}
</AlertDialogTitle>
<AlertDialogDescription>
{t("incomingOrders.deleteConfirmBody", { count: confirmCount })}