- 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>
BACK-W-02: Replace string-interpolated SET LOCAL SQL with parameterized
set_config() calls in TenantMiddleware across 5 services (order, wallet,
inventory, catalog, fnb-engine). Eliminates SQL injection pattern;
set_config(key, $1, true) is local-to-transaction, same semantics as SET LOCAL.
BACK-C-01: Remove AllowAnyOrigin() from all 26 services. Switch to
WithOrigins() reading AllowedOrigins config array, with dev-only fallback
to localhost. In production, set AllowedOrigins=["https://goodgo.vn",
"https://admin.goodgo.vn"] via environment config.
BACK-C-03: Standardize OrdersController GET /orders/{id} 404 response
from {Message:...} to {success:false, error:{code,message}} per API contract.
BACK-C-04: Add complete ProblemDetails exception mappings to _template_dot_net:
ValidationException -> 400, DomainException -> 422, with TODO comments
for service-specific types (EntityNotFoundException -> 404, etc.).
BACK-C-02: wallet-service and booking-service already have full
IRequestManager idempotency implementation — no changes needed.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
SEC-C-01: Replace Neon PostgreSQL credentials (npg_Ssfy6HKO0cXI) with local
dev connection strings in all 19 appsettings.json files. Production credentials
must be injected via ConnectionStrings__DefaultConnection env var. Add
appsettings.Production.json and appsettings.Staging.json to .gitignore.
SEC-C-02: Add services/goodgo-mcp-server/.env to root .gitignore. Create
.env.example with safe placeholder values documenting required variables.
SEC-C-03: Wrap AddDeveloperSigningCredential() in env check — development only.
Non-development environments must provide X.509 certificate via
IdentityServer:SigningCertificatePath and IdentityServer:SigningCertificatePassword.
SEC-C-04: Remove 4 unauthenticated debug endpoints from StaffController:
GET debug/all, POST debug/seed, POST debug/update-userid, POST debug/update-merchant.
These endpoints allowed privilege escalation and data exfiltration without auth.
SEC-C-05: Removed endpoints containing SQL injection via string interpolation
(lines 307, 367 in StaffController). Also removed [AllowAnonymous] from
GET lookup endpoint — inherits class-level [Authorize].
BREAKING: debug/* endpoints are permanently removed. BFF lookup endpoint now
requires authentication.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
DEVOPS-C-01: Replace hardcoded :latest with IMAGE_TAG placeholder in all 8
production K8s manifests. Update deploy-production.yml to sed-replace
IMAGE_TAG with commit SHA before kubectl apply (remove now-redundant
kubectl set image step).
DEVOPS-C-02: Configure Alertmanager — create alertmanager.yml with Slack +
email receivers (critical/warning/infra routes, inhibition rules). Add
alertmanager:v0.27.0 service to both docker-compose.observability.yml and
deployments/local/docker-compose.yml. Enable prometheus.yml target
(alertmanager:9093).
DEVOPS-C-03: Remove :latest from docker-build.yml main branch push. Now
only SHA tag is pushed for main; :staging+SHA for develop.
DEVOPS-C-04: Add 4 mkt-* services to deployments/local/docker-compose.yml
with unique host ports (facebook:5021, whatsapp:5022, x:5023, zalo:5024)
to eliminate port 5000 conflicts. Add corresponding Traefik routers and
load-balancer entries in infra/traefik/dynamic/routes.yml
(/api/v1/mkt/{facebook,whatsapp,x,zalo}).
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Redesign all 6 onboarding steps with inline step indicators replacing
the fixed sidebar layout. Simplified structure with admin-content/admin-panel
pattern for consistency with other admin pages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CRITICAL fixes:
- update_product: fetch current product first, include productId in body (was 400)
- period enum: "week"/"month" → "7d"/"30d" to match backend handler
- Token leakage: add axios response interceptor to strip Authorization from errors
- Token expiry: add 401 detection with clear user-facing message
HIGH fixes:
- create_product: handle raw Guid response (was returning "unknown")
- update_product: merge with existing values to avoid overwriting with defaults
- Startup validation: warn if API_TOKEN is not set
- Graceful shutdown: handle SIGINT/SIGTERM with server.close()
- Error handler: shared module with structured API error extraction
- Type safety: replace `any` with proper DTO interfaces across all tools
- Promise.allSettled in cost_analysis for partial failure resilience
- Timeout increased 15s → 30s for analytics queries
MEDIUM fixes:
- amount fields use .int() (inventory backend expects int, not float)
- ingredients array requires .min(1) (prevent empty recipes)
- isActive default removed (show all products by default)
- pageSize default aligned to 20 (matches backend)
- String length limits on name/description fields
- Locale-explicit formatting (vi-VN)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CTO Audit findings and fixes:
- Port config: all 4 services were using wrong localhost ports (5002/5003/5004/5019).
All services run behind Traefik on port 80 — consolidated to single gateway client.
- Route fix: /shops/{shopId}/products → /products?shopId= (Traefik routes /shops to merchant-service)
- Response parsing: dashboard API uses "revenue"/"popularItems" (not "totalRevenue"/"topItems")
- Added .gitignore to prevent .env with JWT tokens from being committed
Verified all 12 tools against live Docker services via Traefik gateway.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MCP server with 12 tools across 4 groups:
- Catalog: list/create/update/delete products
- Inventory: check stock, record intake/usage, low stock alerts
- Recipes: list and create recipes with ingredients
- Analytics: popular items, cost analysis
Uses @modelcontextprotocol/sdk with stdio transport for Claude Code integration.
Connects to catalog-service, inventory-service, fnb-engine via REST APIs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add "Danger Zone" section to ShopSettings with deactivate/close actions
- CloseShopConfirmDialog: type shop name to confirm (GitHub-style)
- BFF: proxy endpoints POST /shops/{id}/deactivate and /close
- MerchantApiService: DeactivateShopAsync(), CloseShopAsync()
- CTO report documenting the gap and implementation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PosLayout.razor hardcoded navigation to /admin for the settings button
and sidebar link, causing staff users to land on the admin page.
Now uses AuthStateService.GetPortalUrl() for role-aware routing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added T39-T41: Staff POS tested with only aPOS_token_staff
(admin token removed). Bank transfer 120k, dashboard 358k,
order history all verified in staff-only context.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added T35-T38: Staff POS order creation (238k cash), pending orders
(6 orders with status filters), POS dashboard (real revenue), and
order history (4 orders across 7 days). All Staff POS features working.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added T28-T34: full POS order flow tests (create order, cash payment
with change, bank transfer, order history, dashboard, finance, reports).
Updated CTO report with P0 bug fixes and deployment readiness.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BFF server forwards JWT via AuthForwardingHandler to downstream services.
Adding [Authorize] on BFF controllers causes "No authenticationScheme was specified"
error since the BFF server itself has no JWT middleware configured.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
P2: POS duplicates were DB seed issue (9 records deleted), now 9 unique products.
P3: Settings shop name now shows "Cobic Coffee" correctly.
Both verified visually on Chrome.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both P2 (duplicate POS products) and P3 (settings shop name) resolved
in commit 344be33. Updated checklist and next steps.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
P2: Products appeared 2x in POS grid — BFF now filters isActive=true
by default, plus client-side dedup by product ID as safety net.
P3: Admin Settings showed "--" for shop name — parent ShopPage now
passes ShopName and VerticalLabel parameters to ShopSettings component.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
27 test cases covering all admin (15 pages) and staff (7 pages) features.
Zero regressions from Wave 1-3 fixes. 2 bugs found: duplicate POS products (P2),
settings shop name display (P3). Recommendation: ready for staging.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wave 2 — 3 parallel agents fixing P1 issues:
Validators (57 new FluentValidation validators):
- ads-manager: 10 validators for all commands
- ads-billing: 3 validators for all commands
- ads-tracking: 2 validators for missing commands
- ads-analytics: 1 validator for CreateReport
- social: 8 validators for all commands
- mining: 16 validators for all commands
- mission: 4 validators for all commands
- promotion: 13 validators for all commands
Missing handlers (10 implemented):
- promotion: ExchangeVoucher, PurchaseVoucher, SearchVouchers,
GetCampaignStatistics, GetCampaignVouchers
- mission: GetUserMissionProgress
- mkt-facebook: GetConversations, GetCustomers
- ads-manager: ListAudiences, GetAudienceById
All validators use bilingual messages (EN/VI) and are auto-registered
via MediatR ValidatorBehavior pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>