Commit Graph

717 Commits

Author SHA1 Message Date
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
Ho Ngoc Hai
1256ea0c00 fix(superadmin): add shops list to merchant detail view
- 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>
2026-03-29 00:15:21 +07:00
Ho Ngoc Hai
e3893efa56 feat(superadmin): implement subscription plan management for merchants
- 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>
2026-03-29 00:07:49 +07:00
Ho Ngoc Hai
b378f39872 fix(audit): implement audit logging pipeline and fix response format
- 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>
2026-03-28 23:54:02 +07:00
Ho Ngoc Hai
90debb3e94 fix(superadmin): resolve merchant admin query EF Core translation errors
- 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>
2026-03-28 23:39:26 +07:00
Ho Ngoc Hai
89cf4e8879 feat(superadmin): implement full Super Admin platform management panel
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>
2026-03-28 22:46:47 +07:00
Ho Ngoc Hai
04738248f2 rebrand: rename GoodGo → aPOS across all UI and display text
Replaced all user-facing "GoodGo" brand references with "aPOS"
across 35 files (53 occurrences):
- Layout headers: "aPOS Admin", "aPOS POS"
- Page titles: "— aPOS Admin", "— aPOS POS"
- Locale files: brand name keys
- Onboarding: welcome text, descriptions
- AI Chat: system prompt, provider headers
- Auth pages: login titles

Note: Internal namespace GoodGo.BlazorUi.Components.* preserved
(assembly reference, not user-facing brand text).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 21:25:19 +07:00
Ho Ngoc Hai
dae8aef31f feat(settings): add AI Assistant configuration panel
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>
2026-03-26 18:12:11 +07:00
Ho Ngoc Hai
b589752b51 feat(ai-chat): implement AI Chat Assistant with MCP tool integration
Full-stack AI Chat feature for shop management:

Backend (BFF Server - 6 new files):
- IAiChatProvider: provider interface + shared DTOs for messages/tools
- OpenAiChatProvider: handles OpenAI + OpenRouter APIs (function calling)
- ClaudeChatProvider: handles Anthropic Claude Messages API (tool_use)
- McpToolRegistry: 12 MCP tool definitions as LLM function schemas
  (list_products, check_inventory, popular_items, etc.)
- McpToolExecutor: routes tool calls to microservices via named HttpClients
- AiChatController: POST /api/bff/ai/chat with tool execution loop
  (max 5 iterations), GET/PUT /api/bff/ai/config for settings
- Supports 3 providers: OpenAI, OpenRouter, Claude (Anthropic)
- API keys server-side only, in-memory config store (v1)

Frontend (Blazor WASM - 1 new + 6 modified):
- ShopAiChat.razor: chat UI with message bubbles, suggested prompts,
  tool usage badges, typing indicator, Enter key support
- ShopSidebarConfig: added "AI Assistant" menu item (bot icon)
- ShopPage: added "ai-chat" route case
- PosDataService: AI chat DTOs + SendAiChatAsync/GetConfig/UpdateConfig
- Locale files: added Shop_Menu_AiChat key (vi-VN + en-US)
- Program.cs: registered McpToolExecutor + LLM provider HttpClients

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 17:54:40 +07:00
Ho Ngoc Hai
65c80c9fb1 refactor(pos): move nav from horizontal bottom bar to vertical right sidebar
- 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>
2026-03-26 17:25:16 +07:00
Ho Ngoc Hai
9f52c27f56 fix(pos-dashboard): show payment method names instead of order status
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>
2026-03-26 17:08:50 +07:00
Ho Ngoc Hai
ee8f057d67 fix(order): complete order after POS payment instead of stopping at Processing
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>
2026-03-26 10:34:51 +07:00
Ho Ngoc Hai
bd3a23b03d fix(pos): add Lucide icon re-init observer for Blazor WASM compatibility
Lucide JS replaces <i data-lucide> elements with <svg>, breaking
Blazor WASM's DOM diffing algorithm (removeChild null error).
Added MutationObserver that safely re-initializes Lucide icons
after Blazor renders, and dismiss handler for error banner.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 10:17:16 +07:00
Ho Ngoc Hai
6bdf0390ba fix(pos): pass selected payment method to PayOrderAsync
CafeDesktop.ConfirmPayment() was calling PayOrderAsync without
_selectedMethod, defaulting to "cash" regardless of user selection.
Now passes _selectedMethod (cash/card/qr/transfer) correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 10:04:28 +07:00
Ho Ngoc Hai
c708bda364 fix(ui): translate order status and payment method to Vietnamese
- ShopHelpers: add OrderStatusLabel(), OrderStatusBadge(),
  PaymentMethodLabel() — translate raw status/method strings
  to Vietnamese with correct badge colors
- ShopFinance.razor: "Validated" → "Chờ thanh toán" (yellow badge)
- ShopOverview.razor: same status translation
- ShopReports.razor: same status translation
- CafeDesktop.razor: update MapApiStatus() to include
  Draft/Validated/Paid/PaymentPending mappings;
  update MapPaymentMethodLabel() to include qr→"Mã QR",
  transfer→"Chuyển khoản", vnpay/momo, empty→"Chưa thanh toán"

4 payment methods supported: Tiền mặt, Thẻ, Mã QR, Chuyển khoản

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 09:56:14 +07:00
Ho Ngoc Hai
2d738aeefa feat(enforcement): add MediatR permission authorization behavior
Phase 2 of permission management — enforcement in MerchantService:

- PermissionConstants: maps JWT "permission" claim strings to
  StaffPermissions bitmask via FromClaims() method
- IRequirePermission: marker interface for commands needing permission
  check (StaffPermissions RequiredPermission property)
- PermissionAuthorizationBehavior: MediatR pipeline behavior that reads
  permission claims from HttpContext.User, converts to bitmask, validates
  against IRequirePermission.RequiredPermission. Skips non-annotated commands.
- Registered in MediatR pipeline after Validator, before Transaction
- Annotated 3 staff commands with ManageStaff permission:
  InviteStaffCommand, CreateActiveStaffCommand, UpdateStaffCommand
- Added HttpContextAccessor DI registration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 06:47:01 +07:00
Ho Ngoc Hai
4849b7b6fc feat(permissions): implement full-stack role permission management
Backend (IAM Service):
- New GetRolePermissionsQuery + Handler: reads permissions from role_claims
- New UpdateRolePermissionsCommand + Handler: validates permission names
  against StaffPermissions enum, replaces role_claims, blocks system roles
- New endpoints: GET/PUT /api/v1/roles/{id}/permissions
- GetRolesQuery: batch-fetch permissions per role via role_claims join
- RoleResponse: add Permissions field to API response
- Seeded role_claims for Admin (7), Merchant (7), MerchantAdmin (6),
  MerchantStaff (2), SuperAdmin (All), Support (2)

Frontend (Blazor WASM):
- IamApiService: add Permissions to RoleDto, UpdateRolePermissionsAsync()
- RolePermissions.razor: replace hardcoded GetPermissionsForRole() with
  API-driven permission toggles from role_claims data
- Editable toggles for non-system roles, disabled for system roles
- Save/Cancel buttons appear when permissions modified
- 7 permission types matching StaffPermissions enum

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 19:50:06 +07:00
Ho Ngoc Hai
52f77c0878 fix(roles): replace hardcoded permission toggles with real role-based permissions
- Remove placeholder _defaultPermissions (same 5 toggles for every role)
- Add GetPermissionsForRole() mapping each role to its actual backend
  authorization capabilities:
  - SuperAdmin: full platform access (6 permissions)
  - Admin: user/shop/report/audit management (5 permissions)
  - Merchant: full shop owner access (6 permissions)
  - MerchantAdmin: shop admin without settings (6 permissions)
  - MerchantStaff: POS + payment only (6 permissions, 4 disabled)
  - Support: read-only system access (5 permissions)
  - PremiumUser/User: customer-level access (4-5 permissions)
- Toggles are now read-only (disabled) reflecting enforced policies
- No conflict between system roles and shop roles confirmed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 19:08:25 +07:00
Ho Ngoc Hai
2ce17f0940 fix(iam): return real role data — description, isSystemRole, createdAt, userCount
- IamServiceContext: add EF column mappings for ApplicationRole private
  fields (description, is_system_role, created_at) matching new DB columns
- GetRolesQueryHandler: JOIN with UserRoles to compute real user count
  per role instead of returning 0
- RoleDto/RoleResponse: add UserCount field
- DB: added columns description, is_system_role, created_at to roles table
  with correct data for all 8 roles

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 18:53:05 +07:00
Ho Ngoc Hai
f3217ab270 fix(dashboard): use ShopVerticalHelper for case-insensitive status checks
- Replace hardcoded s.Status == "active" with ShopVerticalHelper.IsActive()
  (handles "Active", "Published", "active" etc.)
- Replace shop.Status != "active" with ShopVerticalHelper.IsSetup()
  to correctly show "Hoàn thành thiết lập" only for Draft shops
- KPIs and shop card badges now reflect actual shop status correctly

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 18:44:06 +07:00
Ho Ngoc Hai
aeb55072cc fix(merchant): fix NullReferenceException in Shop.Publish() and build errors
- Shop.Publish(): use StatusId (persisted int) instead of _status
  (null when EF Core loads entity without navigation property hydration)
- Shop.SetInactive(): same fix for _status null check
- SubscribeCommand: fix 'userId' → 'request.UserId' build error
- StaffQueries: fix 'userId' → 'request.UserId' build error

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 18:16:55 +07:00
Ho Ngoc Hai
43d334ca7d feat(auth): add YARP auth forwarding and dashboard search filtering
- Add YARP proxy middleware to attach Bearer token from bff_session
  cookie to all YARP-proxied requests (users, roles, audit pages)
- Dashboard search: bind input with oninput and filter shops by
  name/slug/category client-side via FilteredShops property

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 16:47:28 +07:00
Ho Ngoc Hai
6fbc475fdd fix(ui): fix vertical/status labels and add shop activation button
- ShopVerticalHelper.GetLabel: return Vietnamese text directly instead of
  localization keys (Vertical_Cafe → Café, etc.)
- ShopVerticalHelper.GetStatusLabel: return Vietnamese text directly
  (Status_Setup → Thiết lập, Status_Active → Đang mở, etc.)
- ShopSettings: add "Kích hoạt cửa hàng" section with publish button
  when shop is in Draft status, with setup checklist indicators
- ShopPage: pass ShopStatus parameter to ShopSettings component

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 15:35:38 +07:00
Ho Ngoc Hai
6a9aa0d46f fix(overview): load all orders and add date range selector to shop overview
- Change orders fetch from "today" filter to "all" so KPIs show actual data
- Add date range presets (Hôm nay / 7 ngày / 30 ngày / Tất cả)
- Add weekly period tab to revenue chart
- Display filtered recent orders based on selected period

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 15:27:46 +07:00
Ho Ngoc Hai
af1b1fb101 feat: Implement date range filtering, CSV export, and enhanced revenue report display in shop reports. 2026-03-25 15:20:56 +07:00
Ho Ngoc Hai
36a0a9c256 feat: Add functional tests for MktZaloService, new contract and load tests, and audit documentation, while removing a legacy infrastructure project and updating service configurations. 2026-03-25 15:00:05 +07:00
Ho Ngoc Hai
7a752f4a82 fix(qa): resolve build failures and test issues found in QA verification
- packages/logger: upgrade tsconfig target to ES2022 to support Array.at()
- packages/http-client: exclude test files from tsc build to prevent noUnusedLocals errors
- packages/http-client/test: use vi.hoisted() for mock functions (vi.mock hoisting fix)
- services/goodgo-mcp-server/tests: use vi.hoisted() for all 4 test files (catalog, inventory, analytics, recipe)
- web-client-tpos: remove stale @using WebClientTpos.Client.Components.Auth from 10 auth pages (moved to blazor-ui RCL)
- web-client-tpos: remove AttachToken() calls in PosDataService (auth via BFF httpOnly cookie)
- web-client-tpos: fix IamApiService.SetAuthHeader() and MerchantApiService.AttachTokenAsync() — make no-op, remove _auth dependency
- web-client-tpos: fix Profile.razor — remove AttachToken() method and calls
- web-client-tpos: fix OnboardingReady.razor — escape @keyframes → @@keyframes in Razor style block
- web-client-tpos: fix PosDataService.GetListFromApiAsync() — check array before property lookup to fix plain array deserialization
- web-client-tpos/tests: update AuthStateServiceTests to new AuthStateService.Login(email, role) signature (no token param)
- web-client-tpos/tests: update PosDataServiceTests to new PosDataService(http) constructor (no authState param)

All 113 Node.js tests pass. All 30 .NET component tests pass. All .NET builds succeed.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-23 12:07:58 +07:00
Ho Ngoc Hai
a8edfd1597 fix(p2): Wave 3 — fix 4 P2 backend architecture issues (TEC-261)
BACK-I-01: Add CI steps to generate openapi.yaml for all 24 .NET services
- Add .config/dotnet-tools.json with swashbuckle.aspnetcore.cli 7.2.0
- Add scripts/ci/generate-openapi.sh reusable script
- Update all 24 service CI workflows with dotnet tool restore + swagger tofile + artifact upload

BACK-I-02: Add OpenTelemetry Metrics + Prometheus /metrics to _template_dot_net
- Add OTel packages (Extensions.Hosting, Instrumentation.AspNetCore, Runtime, Prometheus)
- Register AddOpenTelemetry().WithMetrics() with ASPNetCore + Runtime instrumentation
- Map MapPrometheusScrapingEndpoint("/metrics") in middleware pipeline

BACK-W-01: Remove IHttpContextAccessor from all 18 handler files in merchant-service-net
- Create MerchantBaseController abstract base with GetCurrentUserId() helper
- Add Guid UserId to 11 Commands and 7 Queries
- Remove IHttpContextAccessor injection from all handlers, use request.UserId instead
- Update 7 controllers to inherit MerchantBaseController and extract userId from JWT claims
- Remove AddHttpContextAccessor() registration from Program.cs

BACK-W-03: Add explicit commandTimeout:5 to all Dapper queries in order-service-net
- 14 files updated: QueryAsync, ExecuteScalarAsync, QueryFirstOrDefaultAsync all get commandTimeout: 5

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-23 10:09:45 +07:00
Ho Ngoc Hai
90434acbde fix(security): Wave 2 — fix 8 P1 frontend security & reliability issues
SEC-W-11: Remove hardcoded OAuth2 client_id/client_secret from Blazor WASM.
  - Create BffAuthController (POST /api/bff/auth/login|logout, GET /api/bff/auth/session)
  - BFF exchanges credentials with IS4 using server-side config (IdentityServer:ClientId/Secret)
  - Add IdentityServer config block to appsettings.json / appsettings.Development.json

SEC-W-12: Migrate password grant — token exchange now happens server-side in BFF, not WASM.
  - AuthService.LoginAsync() POSTs to /api/bff/auth/login (no IS4 call from WASM)

SEC-W-01: JWT in localStorage — migrate to httpOnly SameSite=Strict BFF session cookie.
  - BffAuthController sets cookie on login, clears on logout
  - AuthStateService no longer stores raw token (Token property removed)
  - AuthService only stores non-sensitive metadata (email, role) in localStorage
  - TryRestoreSessionAsync now calls GET /api/bff/auth/session instead of localStorage
  - AuthForwardingHandler reads token from bff_session cookie (legacy header fallback kept)

FRONT-W-01: Token refresh not implemented — add TokenExpiry tracking + proactive refresh timer.
  - AuthStateService: add TokenExpiry, OnTokenExpiring event, IDisposable Timer
  - Login() schedules a Timer that fires OnTokenExpiring 2 min before expiry

FRONT-W-02: DefaultRequestHeaders race condition — use per-request HttpRequestMessage.
  - PosDataService: remove AttachToken() (mutated shared DefaultRequestHeaders)
  - All HTTP helpers (PostAsync, PutAsync, PostAndGetAsync, GetListFromApiAsync,
    GetObjectFromApiAsync) now use HttpRequestMessage per-request
  - Auth handled automatically by browser cookie (same-origin, httpOnly BFF cookie)

FRONT-C-04: No route guard on AdminLayout — add auth redirect.
  - AdminLayout.OnAfterRenderAsync: after TryRestoreSessionAsync, redirect to /auth/login
    if still unauthenticated (with returnUrl param)

FRONT-C-05: shopId not validated against user permissions — add BFF verification.
  - AdminLayout: call PosData.GetShopByIdAsync(shopId) after detecting shop context
  - Redirect to /admin if BFF returns null (403/404 = no access, prevents IDOR)
  - Populate _shopName/_shopCategory from verified backend data (not just URL)

SEC-W-13: No CDN SRI for Lucide icons — add integrity hash + crossorigin attribute.
  - index.html: add integrity="sha256-NBFpKCDLjUdUP2lJaqJf1gOjWPRJgEb0HFCKWjNCIQ4="
    crossorigin="anonymous" to lucide@0.468.0 script tag

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-23 09:57:11 +07:00
Ho Ngoc Hai
619a06fafe fix(security): remove external Redis/MinIO/SMTP credentials from base appsettings.json
SEC-C-01 extended gap: 3 base appsettings.json files still referenced external
infrastructure (167.114.174.113) with Velik@2026 credentials and real SMTP
password — missed by the Wave 1 security fix which targeted DB credentials only.

Changes:
- iam-service-net/appsettings.json: Redis localhost (removed Velik@2026),
  SMTP localhost:1025 (removed Mailgun credentials)
- membership-service-net/appsettings.json: Redis localhost (removed Velik@2026)
- storage-service-net/appsettings.json: MinIO→localhost:9000 minioadmin/minioadmin,
  Redis→localhost (removed Velik@2026)

All production credentials (Redis, MinIO, SMTP) must be injected via
environment variables. Base appsettings.json targets docker-compose local stack.

CTO review finding: Redis__Password, MinIO:SecretKey, Email:SmtpPassword
must never appear in committed config files.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-23 09:54:59 +07:00
Ho Ngoc Hai
7b92332710 fix(devops): resolve 4 P2 DevOps improvements (Wave 3 — TEC-263)
- DEVOPS-W-01: Add oliver006/redis_exporter to docker-compose.yml so
  the existing prometheus.yml scrape job (redis-exporter:9121) resolves
- DEVOPS-W-04: Add redis-sentinel.yaml with Redis Sentinel HA setup
  (1 master StatefulSet + 2 replica StatefulSet + 3 sentinel pods)
  replacing the single-instance SPOF redis.yaml in staging K8s
- DEVOPS-W-05: Add network-policy.yaml with default-deny-all NetworkPolicy
  + explicit allow rules for inter-service, Traefik ingress, Redis access,
  Prometheus scrape, and external egress (Neon PostgreSQL, AMQP)
- DEVOPS-M-01: Add aquasecurity/trivy-action to docker-build.yml to scan
  every built image for CRITICAL/HIGH CVEs; results uploaded to GitHub
  Security tab via SARIF

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-23 09:54:32 +07:00
Ho Ngoc Hai
dd57cff6b1 fix(security): remove residual Neon/external credentials from appsettings.Development.json
SEC-C-01 gap: Security engineer's Wave 1 fix replaced Neon credentials in
appsettings.json (19 files) but missed 4 appsettings.Development.json files
that still pointed to cloud infrastructure with production credentials.

Changes per service:
- iam-service-net: DB→localhost, Redis→localhost (removed Velik@2026),
  Email SMTP→localhost:1025 (removed Mailgun password)
- membership-service-net: DB→localhost, Redis→localhost
- promotion-service-net: DB→localhost
- storage-service-net: DB→localhost, MinIO→localhost:9000 (removed Velik@2026),
  Redis→localhost

All four files now point exclusively to local Docker Compose services
(postgres-local:5432, redis-local:6379, minio-local:9000).
Production/staging credentials must be injected via environment variables.

CTO review finding: appsettings.Development.json must not contain cloud
credentials. Local dev should always use docker-compose services.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-23 09:52:20 +07:00