fix(security,pos): close payment/push/PII gaps from app review
CI/CD / CI · API (dotnet build + test) (push) Successful in 59s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 33s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m10s
CI/CD / CI · Admin Web (tsc) (push) Successful in 40s
CI/CD / CI · Website (tsc) (push) Successful in 48s
CI/CD / CI · Koja (tsc) (push) Successful in 52s
CI/CD / Deploy · all services (push) Successful in 2m13s

- Payments: reject RecordPaymentsAsync when the order is already Delivered/
  Cancelled (ORDER_ALREADY_CLOSED) — prevents duplicate payments, double loyalty
  earn, and overstated cash drawer from a double-tap or paying a reopened order.
- Push broadcast: POST /api/push/broadcast was [Authorize]-only (any user → any
  topic, platform-wide). Now requires SendSms + café context and is forced to the
  caller's own topic (cafe-{slug}); arbitrary/cross-café topics rejected.
- HR reads: GetEmployees/GetAttendance/GetShifts now require ViewStaff/
  ViewAttendance/ViewSchedules (were café-access-only, leaking roster PII the UI
  already hid). Expenses list now requires ViewExpenses.
- Receipt: removed the auto-print on full payment so the POS success sheet is the
  single print path (no more double receipt).

Local build blocked by NU1301 (NuGet network unreachable); CI builds via mirror.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-22 15:40:20 +03:30
parent c360fbb068
commit 63e3cb6962
4 changed files with 37 additions and 12 deletions
+8 -1
View File
@@ -1039,6 +1039,12 @@ public class OrderService : IOrderService
if (order is null)
return new OrderServiceResult<IReadOnlyList<PaymentDto>>(false, null, "ORDER_NOT_FOUND");
// Never take payment on an already-closed order — a double-tap on Pay, or
// paying a closed order reopened from the board, would otherwise record
// duplicate payments, re-earn loyalty, reprint, and overstate the drawer.
if (order.Status is OrderStatus.Delivered or OrderStatus.Cancelled)
return new OrderServiceResult<IReadOnlyList<PaymentDto>>(false, null, "ORDER_ALREADY_CLOSED");
var branchId = await ResolveOrderBranchIdAsync(order, cafeId, cancellationToken);
if (string.IsNullOrEmpty(branchId))
return new OrderServiceResult<IReadOnlyList<PaymentDto>>(false, null, "NO_OPEN_SHIFT", "branchId");
@@ -1125,7 +1131,8 @@ public class OrderService : IOrderService
if (paidTotal >= order.Total)
{
PrinterBackgroundJobs.QueueReceiptPrint(_scopeFactory, cafeId, orderId);
// Receipt is printed explicitly from the POS success sheet (single
// print path) — no auto-print here, to avoid a duplicate receipt.
await _loyalty.ApplyEarnOnOrderPaidAsync(cafeId, order.CustomerId, paidTotal, cancellationToken);
await _deliverySync.SyncInternalStatusAsync(cafeId, orderId, OrderStatus.Delivered, cancellationToken);
}