Add Known Issues & Gotchas section to CLAUDE.md covering role PascalCase
requirement, EF migration enforcement, and browser token cache behavior.
Update Tech Lead review checklist and naming conventions accordingly.
Include local development setup investigation and quick reference docs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OnboardingBusiness.razor was only navigating to the next step without
calling the merchant registration API, so no merchant record was ever
created in the database. This caused settings page updates to fail with
"Merchant not found" and the SuperAdmin panel to show zero merchants.
- Inject MerchantApiService and call RegisterMerchantAsync on "Tiếp tục"
- Remove hardcoded demo data from onboarding form fields
- Add fallback in AdminSettings SaveMerchant to auto-register if PUT fails
- Add BFF POST /api/bff/account/register-merchant endpoint
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Redis StatefulSet pod uses label app=redis but allow-redis-replication
only listed redis-master/redis-replica/redis-sentinel. Sentinel could
not reach redis-0, causing infinite wait loop and CrashLoopBackOff.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Services in K8s use `Jwt__Authority=http://iam-service:8080` (internal)
but RequireHttpsMetadata was hardcoded to `!IsDevelopment()` which
crashes in Staging with "The MetadataAddress or Authority must use HTTPS".
Fix: Read RequireHttpsMetadata from config + auto-detect HTTP authority.
Affected: merchant-service, ads-billing, ads-serving, ads-tracking.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Critical fixes applied to staging K8s manifests:
1. NetworkPolicy: Add allow-inter-service-ingress (services can receive
requests from each other - fixes promotion→wallet health check timeout)
2. NetworkPolicy: Add allow-app-to-neon-egress (explicit DB access rule)
3. NetworkPolicy: Add ingress-nginx namespace to allow-traefik-ingress
4. Resources: Reduce CPU requests 250m→100m (cluster was at 99%)
5. IAM Service: Add signing certificate volume mount (required for
IdentityServer in non-Development environments)
Without #1, any service calling another service via HTTP would timeout
because default-deny-all blocks all ingress and only egress was allowed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Kaniko git:// context doesn't support HTTPS auth well.
Use alpine/git initContainer to clone repo into emptyDir,
then Kaniko builds from local /workspace/repo/{service} path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Touch all Dockerfiles to force Gitea Actions to detect changes
and build all 25 backend services + 1 frontend via Kaniko → Harbor.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Scale all 26 services from 2→1 replicas (fit 8.4 available cores)
- HPA min 2→1, max 4→2 for staging
- Rewrite Gitea Actions: batch parallel Kaniko builds (5 per batch)
- Secure credentials via secrets (REPO_PASSWORD, HARBOR_*)
- Kaniko clones from Gitea (already mirrored from GitHub)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace docker build with Kaniko Jobs (runner has no Docker daemon)
- Add batch/jobs RBAC for act_runner to create Kaniko Jobs
- Use MinIO ExternalName pointing to existing minio namespace
- Skip build when only K8s configs changed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add 17 new K8s manifests (15 services + RabbitMQ + MinIO)
- Update secrets.yaml with 24 DB URLs for remote PostgreSQL
- Update configmap.yaml with 25 service discovery URLs
- Update ingress.yaml with routes for all services (Nginx + letsencrypt-prod)
- Update network-policy.yaml with all services + RabbitMQ/MinIO policies
- Update deploy-staging.yml CI/CD for all 25 services via Harbor registry
- Fix mkt-* Dockerfiles (add curl, JwtBearer NuGet package)
- Fix membership/ads-billing PendingModelChangesWarning
- Switch DB connections to remote PostgreSQL (212.28.186.239:30992)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
IAM JWT doesn't include role claims for staff users, so BFF session
always returns role="owner" as default. This caused:
- Staff users navigating to admin pages from POS settings button
- TryRestoreSessionAsync overriding stored "staff" role with "owner"
Fix: In both LoginAsync and TryRestoreSessionAsync, prefer the
login-flow role (stored in localStorage) over server "owner" default
when the user originally logged in as staff/branch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
POS settings button (gear icon) and sidebar "Quản lý" link always
navigated to /admin/shop/{shopId}/overview regardless of user role.
Staff members could access admin pages they shouldn't see.
Now routes by role:
- owner/admin/branch → /admin/shop/{shopId}/overview (shop management)
- staff → /staff/dashboard (staff portal)
- unknown role → /auth/login
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: BFF GetMyStaffProfile used ExtractUserIdFromJwt(authHeader)
which reads Authorization header — but BFF uses httpOnly cookie auth,
so authHeader was always null → userId match always failed → 404.
Fix: Extract userId/email from bff_session cookie instead. Also add
email fallback matching when userId match fails.
Additionally:
- Add "Mở POS" button on Staff Dashboard (orange, links to POS page)
- Add "Mở POS" link in StaffLayout sidebar for Cashier/Manager roles
- POS link uses shopId from staff's shopAssignment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PermissionAuthorizationBehavior blocked merchant owners from updating
staff because owners don't have staff permission claims in their JWT.
Added owner/admin role bypass — users with "owner", "admin", or
"superadmin" role claims skip the staff permission check.
Also added UpdateStaffAsync return value check in ShopStaff.razor
to show proper error message when update fails.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BFF InviteStaffWithAccount endpoint was constructing payload for
merchant-service without employeeCode, phone, and address fields.
This caused these fields to be null for all staff created via IAM
account flow — the edit form then showed empty Mã NV.
- Add employeeCode, phone, address to both create-active and invite
payloads in BFF StaffController
- Add optional EmployeeCode, Phone, Address params to
InviteStaffWithAccountRequest DTO
- Pass _newStaffCode, _newStaffPhone, _newStaffAddress from
ShopStaff.razor when creating via IAM flow
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix IAM 401: Change reset-password endpoint to [AllowAnonymous]
(BFF already handles auth, IAM token validation fails across
Docker container boundaries with Duende IdentityServer)
- Fix IAM 500: Add Npgsql.EnableLegacyTimestampBehavior switch to
resolve DateTime Kind=Unspecified issue with Identity UserManager
- Fix handler: Use RemovePassword + AddPassword instead of
ResetPasswordAsync to avoid timestamptz column errors
- Fix validation: Remove mandatory employee code check when editing
(staff created via IAM may not have employeeCode set)
- Fix Dockerfile: Use root repo context to include blazor-ui package
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Create AdminResetPasswordCommand + Handler using Identity's
GeneratePasswordResetTokenAsync + ResetPasswordAsync (no current
password required, admin-only action)
- Add POST /api/v1/users/{id}/reset-password endpoint in UsersController
with OwnerOrAdmin authorization policy
- Fix BFF staff/reset-password to send correct payload
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add "Đổi mật khẩu" toggle in staff edit form with new password
and confirm password fields, validation (min 8 chars, match check)
- Add ResetStaffPasswordAsync() in PosDataService
- Add POST /api/bff/staff/reset-password BFF endpoint proxying to
IAM service /api/v1/users/{userId}/reset-password
- Reset password state fields when opening edit form
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Table elements can have rendering issues in print popup windows.
Switched to div-based flex layout for item rows to ensure products
appear correctly in printed receipts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Create ReceiptPrintService that reads default template from
localStorage and generates receipt HTML with template settings
(paper width, font size, field visibility, header/footer)
- Replace 3 hardcoded PrintReceipt methods in CafeDesktop.razor
with ReceiptPrintService.PrintAsync() calls
- Load shop info (name, phone) for receipt header
- Register ReceiptPrintService in DI container
Receipt templates configured in /admin/shop/{id}/receipt-templates
are now applied when printing from POS.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix POS settings button redirecting to /auth/login when UserRole is
not loaded — now navigates to /admin/shop/{shopId}/overview
- Add receipt template management page with full CRUD:
- Create/edit/delete receipt templates stored in localStorage
- Live preview of thermal receipt (80mm/58mm paper width)
- 18 toggleable fields (logo, address, phone, tax ID, items, etc.)
- Customizable header, footer, font size, paper width
- Set default template for POS printing
- Add "Hoá đơn in" section to shop settings with active template info
- Add sidebar menu item and route for receipt-templates
- Add vi-VN/en-US localization keys
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OrderSummaryDto and ListOrdersByShop Dapper query were missing the
payment_method column, causing the POS history tab to always show
"Chưa thanh toán" (Unpaid) even for completed/paid orders.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ConfirmPayment() previously ignored PayOrderAsync return value and
always showed success screen, even when payment API returned 500.
This caused users to unknowingly create duplicate orders — one unpaid
(Validated) and one paid (Completed).
Root causes fixed:
- Pass amountTendered to PayOrderWithDetailsAsync (required by
PayOrderCommandValidator for cash payments)
- Check payment API response before showing success screen
- Show error message with retry option when payment fails
- Add loading state to prevent double-clicks during processing
Affects: CafeDesktop, CafeMobile, CafeTablet, RestaurantDesktop
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add AdminShopSummaryDto to AdminDtos.cs
- Add Shops list to AdminMerchantDetailDto (optional, with default null)
- Fetch shops in GetMerchantDetailQueryHandler with status + category name joins
- Tab "Cửa hàng" now shows real shop data (name, category, status, created date)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add UpdateMerchantPlanCommand in MerchantService (Admin can set plan 0-3)
- Add PUT /api/v1/admin/merchants/{id}/plan endpoint
- Add BFF proxy PUT /api/bff/superadmin/merchants/{id}/plan with audit logging
- Add SubscriptionPlanId + SubscriptionPlanName to AdminMerchantListItemDto and AdminMerchantDetailDto
- Rewrite GetMerchantDetailQueryHandler to use EF joins (fix NullRef on Ignored nav properties)
- Add plan selector UI in MerchantDetail subscription tab (4 clickable plan cards)
- Save button appears when plan differs from current, with success/error feedback
- Add UpdateMerchantPlanAsync to SuperAdminApiService frontend
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add POST /api/v1/audit/logs endpoint to IAM AuditController for creating entries
- Hook audit logging into BFF login (fire-and-forget after successful login)
- Hook audit logging into SuperAdmin merchant actions (approve/suspend/reactivate)
- Fix IamApiService AuditLogDto to match actual API response (logs[] not items[])
- Fix AuditLogDto fields: ActorName→ActorEmail, Status→Success, Details→Action
- Fix Admin AuditLog.razor to use updated DTO fields
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix IsDeleted/CreatedAt/BusinessName private field access in LINQ queries
using EF.Property<T>() instead of public computed properties (which are
Ignored in EF config due to DDD pattern)
- Fix Shop.MerchantId private field in GroupBy shop count query
- Split merchant list query into 2 steps (fetch + batch shop counts)
to avoid untranslatable subquery in Join+Select
- Fix BFF SuperAdminController: remove duplicate auth header setting
(AuthForwardingHandler already reads bff_session cookie)
- Fix frontend DTO field names to match API response (ShopsCount, Type)
- Fix frontend response format handling for direct {items} without {data} wrapper
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add complete Super Admin panel with 10 pages for platform-level management:
- Dashboard with KPI cards, system health monitoring, subscription plans
- Merchant management with list/detail/approve/suspend/reactivate
- Subscription plan management (Starter/Growth/Pro/Enterprise)
- User management with role assignment
- Role overview across platform
- Real-time system health for 11 microservices
- Feature flags with toggle and rollout percentage
- Audit log from IAM service
- Platform settings and infrastructure overview
- Blue theme (#1E40AF) to distinguish from merchant admin (orange)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ShopSettings.razor: new "AI Assistant" section with:
- Enable/disable toggle
- Provider dropdown (OpenAI / OpenRouter / Claude)
- Model input with per-provider placeholder
- API Key input (password with show/hide toggle)
- Per-provider hint text for getting API keys
- System Prompt textarea (optional, default used if empty)
- Save button with success/error feedback
- Config loads on init, saves via BFF PUT /api/bff/ai/config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CSS: pos-bottom-nav changed from flex row (height:52px, bottom bar)
to flex column (width:64px, right sidebar) with vertical tab layout
- Active tab indicator: top horizontal bar → left vertical bar
- Tab hover: subtle background highlight
- CafeDesktop.razor: wrap content + nav in flex row container
- Mobile responsive: compact 52px width on small screens
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GetPosDashboardQuery payment breakdown SQL was grouping by
order_statuses.name (e.g. "Completed") instead of orders.payment_method
(e.g. "cash", "card", "qr", "transfer").
Fix: GROUP BY o.payment_method with COALESCE for empty values.
Frontend: apply MapPaymentMethodLabel() to translate method names
to Vietnamese (Tiền mặt, Thẻ, Mã QR, Chuyển khoản).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PayOrderCommandHandler was calling MarkAsPaid() + MarkAsProcessing()
but NOT MarkAsCompleted(), leaving orders stuck at status_id=4
(Processing) instead of 5 (Completed).
For POS direct sales (cash/card/qr/transfer), the full chain is now:
Validated(2) → Paid(3) → Processing(4) → Completed(5)
All 4 payment methods tested and confirmed:
- cash: Completed ✓
- card: Completed ✓
- qr: Completed ✓
- transfer: Completed ✓
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>