Dịch headings, section titles, và thuật ngữ chính trong 15 file
markdown (.claude/agents/ và .claude/*.md) sang tiếng Việt có dấu.
Giữ nguyên format markdown, code blocks, tên kỹ thuật và commands.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: Lucide JS replaces <i data-lucide> with <svg>, breaking
Blazor's virtual DOM diffing (insertBefore/removeChild on null).
23 components + 2 MutationObservers were calling lucide.createIcons()
concurrently, amplifying the race condition.
Changes:
- Remove all 23 direct lucide.createIcons() JS interop calls from
layouts and components (AdminLayout, AuthLayout, StaffLayout, etc.)
- Replace dual MutationObserver with single requestIdleCallback poller
that only runs when browser is idle (after Blazor finishes rendering)
- Auto-suppress Blazor error banner for known harmless Lucide DOM
mismatch errors (insertBefore/removeChild on null)
- Change login NavigateTo from forceLoad:true to forceLoad:false
to avoid full WASM runtime reload after successful login
- Simplify launch.json to pos-web only for Claude Code preview
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Configure all 24 services to connect to remote staging PostgreSQL
(212.28.186.239:30992) and MinIO (minio.techbi.org) while running
Redis and RabbitMQ locally on non-standard ports (16379, 25672)
to avoid conflicts with other projects.
- Add .env.remote with hybrid connection strings
- Add docker-compose.dev.yml (lightweight Redis + RabbitMQ only)
- Add scripts/dev/start-dev.sh for one-command infra startup
- Update all appsettings.Development.json with remote DB + timeout
- Add .claude/launch.json for Claude Code preview (pos-web only)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>