748 Commits

Author SHA1 Message Date
Ho Ngoc Hai
f0dad8881a Harden staff and voucher isolation with TPOS parity UI fixes 2026-05-29 01:45:32 +07:00
Ho Ngoc Hai
e8951b783a Add TPOS parity admin dashboard and store wizard 2026-05-24 15:39:01 +07:00
Ho Ngoc Hai
34200a0e4e Refine POS history and dashboard layouts 2026-05-24 10:26:51 +07:00
Ho Ngoc Hai
0e99b1d654 Fix shop and POS route parity 2026-05-24 00:48:20 +07:00
Ho Ngoc Hai
481f07b31a Refactor TPOS shop and POS routes for parity 2026-05-24 00:48:06 +07:00
Ho Ngoc Hai
7f9434347f Refine TPOS typography and portal active states 2026-05-24 00:25:22 +07:00
Ho Ngoc Hai
7e647672c1 Implement TPOS parity UI and backend 2026-05-24 00:17:20 +07:00
Ho Ngoc Hai
76d75c753b Migrate 2026-05-23 18:37:02 +07:00
Ho Ngoc Hai
f15d91ee29 docs: dịch các file .claude MD sang tiếng Việt có dấu
All checks were successful
Build & Deploy to K8s / build-and-deploy (push) Successful in 11s
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>
2026-04-19 00:28:07 +07:00
Ho Ngoc Hai
0fdc11de0f fix(blazor): resolve Lucide DOM conflict causing unhandled error banner on login
All checks were successful
Build & Deploy to K8s / build-and-deploy (push) Successful in 8m39s
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>
2026-04-15 13:04:34 +07:00
Ho Ngoc Hai
529c92e0e1 feat(dev): hybrid local dev setup — remote PostgreSQL/MinIO, local Redis/RabbitMQ
Some checks are pending
Build & Deploy to K8s / build-and-deploy (push) Waiting to run
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>
2026-04-15 12:27:01 +07:00
Ho Ngoc Hai
368a660e5f docs: add known issues, review checklist updates, and local dev investigation
All checks were successful
Build & Deploy to K8s / build-and-deploy (push) Successful in 23s
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>
2026-04-15 11:29:54 +07:00
Ho Ngoc Hai
5e2f20967d fix(merchant): wire up merchant registration in onboarding and settings
All checks were successful
Build & Deploy to K8s / build-and-deploy (push) Successful in 9m29s
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>
2026-04-12 20:46:27 +07:00
Ho Ngoc Hai
54fbaeaffe chore: remove obsolete deployment reports, fix trackers, and project planning documents
All checks were successful
Build & Deploy to K8s / build-and-deploy (push) Successful in 25s
2026-04-12 16:00:41 +07:00
Ho Ngoc Hai
b5b717ed4b fix(k8s): add redis label to replication network policy for sentinel
All checks were successful
Build & Deploy to K8s / build-and-deploy (push) Successful in 56s
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>
2026-04-12 02:43:51 +07:00
Ho Ngoc Hai
0f18d9ad9d ci: trigger rebuild after rabbitmq probe fix on cluster
All checks were successful
Build & Deploy to K8s / build-and-deploy (push) Successful in 42s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 02:29:34 +07:00
Ho Ngoc Hai
b768c9dc31 fix(k8s): sync cluster fixes to source — JWT authority, secrets, Redis config
Some checks failed
Build & Deploy to K8s / build-and-deploy (push) Failing after 35s
1. ConfigMap: Jwt__Authority → http://iam-service:8080 (internal K8s DNS)
   Pods cannot reach external HTTPS for OIDC discovery.
   Token issuer remains https://api.techbi.org via IssuerUri.

2. Secrets: Add IdentityServer__ClientId/Secret for pos-web BFF auth.

3. Redis: Add redis-config.yaml ConfigMap with fixed start scripts.
   - start-redis.sh reads from /tmp (init container copies there)
   - start-sentinel.sh reads from /config (directly mounted)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 02:24:03 +07:00
Ho Ngoc Hai
b2a5bde40a fix(auth): allow HTTP OIDC discovery for K8s internal authority
Some checks failed
Build & Deploy to K8s / build-and-deploy (push) Failing after 12m20s
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>
2026-04-12 00:24:48 +07:00
Ho Ngoc Hai
8a5b25936d fix(auth): add bff-client to IdentityServer + fix pos-web auth
Some checks failed
Build & Deploy to K8s / build-and-deploy (push) Failing after 10m14s
Login was failing because:
1. IdentityServer Config.cs had no 'bff-client' client definition
   (pos-web uses bff-client for BFF authentication pattern)
2. pos-web had no IdentityServer__ClientSecret env var configured
3. Network policy blocked pos-web → iam-service egress

Fixes:
- Add bff-client to Config.Clients (ResourceOwnerPassword grant,
  8h access token, 7d refresh token for POS sessions)
- Add IdentityServer client credentials to pos-web.yaml from secrets
- Add pos-web to allow-inter-service-egress network policy

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 22:22:37 +07:00
Ho Ngoc Hai
5ce64b9a1c feat(infra): migrate POS System routing to Traefik v3
Some checks failed
Build & Deploy to K8s / build-and-deploy (push) Failing after 26s
Architecture: Nginx Ingress (TLS) → Traefik (routing) → Services

- Add traefik.yaml: Traefik v3.3 deployment with file provider config
  - 65+ route rules for api.techbi.org (25 backend services)
  - platform.techbi.org → pos-web
  - Middlewares: rate-limit (100/s), retry (3x), compress, secure-headers
  - WebSocket support for SignalR hubs (/hubs/pos, /hubs/kitchen, /hubs/chat)
- Update ingress.yaml: Nginx now proxies POS domains to Traefik ClusterIP
  (Nginx still handles TLS termination via cert-manager/Let's Encrypt)
- Update network-policy.yaml: Add Traefik ingress/egress/DNS policies
- Update deploy.yaml: Add traefik.yaml to CI/CD apply step
- Other services unaffected: Neon-UI, Rancher, Gitea, Harbor, Grafana, MinIO

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 21:40:12 +07:00
Ho Ngoc Hai
084771bfc5 fix(k8s): add inter-service ingress policy + reduce CPU requests
Some checks failed
Build & Deploy to K8s / build-and-deploy (push) Failing after 33s
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>
2026-04-11 20:19:03 +07:00
Ho Ngoc Hai
43a61874d3 docs: add deployment state docs and troubleshooting guide
- Update POS_DEPLOYMENT_STATE.md with live staging status
- Create TROUBLESHOOTING.md with common issues & fixes
- Add architecture visual, quick reference, and analysis docs
- Document Network Policy gap (inter-service ingress)
- Document DNS/ingress routing setup
- Document CI/CD pipeline (Gitea Actions + Kaniko)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 20:14:01 +07:00
Ho Ngoc Hai
5d432145d5 fix(cicd): fix pos-web root context + rebuild remaining 10 services
All checks were successful
Build & Deploy to K8s / build-and-deploy (push) Successful in 29m5s
pos-web Dockerfile uses root context (COPY apps/web-client-tpos-net/...)
so Kaniko needs --context=/workspace/repo --dockerfile=apps/.../Dockerfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:04:02 +07:00
Ho Ngoc Hai
6bd8377c04 build: rebuild remaining 10 services (ads, mkt, pos-web)
Some checks are pending
Build & Deploy to K8s / build-and-deploy (push) Has started running
Batch 1-3 complete (15/26). Building remaining: 5 ads services,
4 marketing services, 1 frontend.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:35:16 +07:00
Ho Ngoc Hai
01b246287e build: full rebuild v4 (Harbor ingress timeout 600s patched)
Some checks failed
Build & Deploy to K8s / build-and-deploy (push) Failing after 44m57s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 22:43:31 +07:00
Ho Ngoc Hai
014c5ee357 build: trigger full rebuild v3 (network policy fix applied on cluster)
Some checks failed
Build & Deploy to K8s / build-and-deploy (push) Failing after 20m38s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 22:14:05 +07:00
Ho Ngoc Hai
8dbf913792 build: trigger full rebuild all 26 services (v2 - with initContainer fix)
Some checks failed
Build & Deploy to K8s / build-and-deploy (push) Failing after 10m27s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 22:03:09 +07:00
Ho Ngoc Hai
19e914b5d8 fix(cicd): use initContainer clone + local Kaniko context
All checks were successful
Build & Deploy to K8s / build-and-deploy (push) Successful in 20s
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>
2026-04-10 21:55:21 +07:00
Ho Ngoc Hai
08c218ac3c build: trigger full rebuild of all 26 services via Kaniko
Some checks failed
Build & Deploy to K8s / build-and-deploy (push) Has been cancelled
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>
2026-04-10 21:51:04 +07:00
Ho Ngoc Hai
e32d13ecbc fix(cicd): trigger rebuild after fixing Gitea URL-encoded password
All checks were successful
Build & Deploy to K8s / build-and-deploy (push) Successful in 13s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 21:48:29 +07:00
Ho Ngoc Hai
84f21a4d1c feat(deploy): full staging deployment - 1 replica, parallel Kaniko, all 26 services
Some checks failed
Build & Deploy to K8s / build-and-deploy (push) Failing after 6s
- 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>
2026-04-10 21:44:46 +07:00
Ho Ngoc Hai
31d24c8c4d fix(k8s): switch domains from goodgo.vn to techbi.org
All checks were successful
Build & Deploy to K8s / build-and-deploy (push) Successful in 33s
- api.techbi.org (backend API)
- platform.techbi.org (frontend POS)
- Update JWT Authority, CORS, IdentityServer IssuerUri
- Update TLS secret names

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 20:30:08 +07:00
Ho Ngoc Hai
b885da7cdb fix(cicd): skip namespace apply (already exists) + add patch permission
All checks were successful
Build & Deploy to K8s / build-and-deploy (push) Successful in 32s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 20:18:02 +07:00
Ho Ngoc Hai
43f0c79478 fix(cicd): use Kaniko Jobs for building Docker images in Gitea Actions
Some checks failed
Build & Deploy to K8s / build-and-deploy (push) Failing after 10s
- 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>
2026-04-10 20:15:20 +07:00
Ho Ngoc Hai
48bb30b009 feat(cicd): switch CI/CD from GitHub Actions to Gitea Actions
Some checks failed
Build & Deploy to K8s / build-and-deploy (push) Failing after 15s
- Add .gitea/workflows/deploy.yaml (detect changes → docker build → Harbor push → kubectl deploy)
- Add gitea-sync-cronjob.yaml (GitHub → Gitea mirror sync every 5 min)
- Add act-runner-rbac.yaml (RBAC for act_runner to deploy to staging namespace)
- Add setup-secrets.sh (one-time cluster secret setup script)
- Disable GitHub Actions deploy-staging.yml (CI/CD now via Gitea)

Flow: GitHub push → Gitea sync (5min) → Gitea Actions → Docker build → Harbor → K8s

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 20:03:19 +07:00
Ho Ngoc Hai
966f5412bd feat(k8s): add full K8s staging deployment for all 25 services
- 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>
2026-04-10 19:53:09 +07:00
Ho Ngoc Hai
6aa52cdb19 fix(auth): preserve staff role across session restore
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>
2026-03-30 12:15:22 +07:00
Ho Ngoc Hai
46402f3e67 fix(pos): route settings button by user role (admin vs staff)
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>
2026-03-30 12:00:34 +07:00
Ho Ngoc Hai
1b90b0119d fix(staff): fix staff/me profile resolution and add POS access
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>
2026-03-30 11:41:40 +07:00
Ho Ngoc Hai
8f39570407 fix(merchant): allow owner to update staff without ManageStaff permission
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>
2026-03-30 11:21:08 +07:00
Ho Ngoc Hai
9f8bdfd9d3 fix(staff): include employeeCode, phone, address in IAM staff creation
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>
2026-03-30 11:06:18 +07:00
Ho Ngoc Hai
6256db44b7 fix(staff): resolve password reset failures and validation issues
- 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>
2026-03-30 10:55:50 +07:00
Ho Ngoc Hai
b537cea290 feat(iam): add admin reset password endpoint for staff management
- 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>
2026-03-30 10:32:39 +07:00
Ho Ngoc Hai
ccb7716ba1 feat(staff): add password change for existing staff members
- 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>
2026-03-30 10:22:18 +07:00
Ho Ngoc Hai
420100309b fix(ui): move receipt templates menu item above settings in sidebar
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 11:15:22 +07:00
Ho Ngoc Hai
acc19977ae fix(receipt): use div layout instead of table for receipt items
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>
2026-03-29 10:46:21 +07:00
Ho Ngoc Hai
c31881f7b6 feat(pos): integrate receipt templates into POS printing flow
- 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>
2026-03-29 10:08:28 +07:00
Ho Ngoc Hai
b666d2f68d feat(pos): fix settings navigation and add receipt template management
- 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>
2026-03-29 03:43:55 +07:00
Ho Ngoc Hai
c8a70f8d80 fix(order): include payment_method in order list API response
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>
2026-03-29 03:25:00 +07:00
Ho Ngoc Hai
b81c6ac176 fix(pos): prevent duplicate orders by checking payment API result
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>
2026-03-29 02:12:57 +07:00