- 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>
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>
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>
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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
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>
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>
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>