fix: resolve 12 critical/high issues from code review across backend, frontend, and infra

Backend (7 fixes):
- wallet-service: remove conflicting EF Ignore() calls for mapped backing fields
- fnb-engine: remove KitchenTicket short constructor that set productId=orderItemId
- fnb-engine: replace fire-and-forget Task.Run with direct await for inventory deduction
- TenantMiddleware: implement PostgreSQL RLS SET LOCAL in 4 services (wallet, fnb, inventory, catalog)
- order-service: fix SQL injection pattern in TenantMiddleware with Guid.ToString("D")
- order-service: add ValidateShopAccess() authorization check in SignalR PosHub
- 4 services: register IDbConnection (NpgsqlConnection) in DI for RLS middleware

Frontend (3 fixes):
- PosDataService: return Success=false (not true) when PayOrder response parsing fails
- QrPayment: add _disposed guard to prevent timer race condition after component disposal
- BFF OrderController: add [Authorize] attribute to require JWT for all endpoints

Infrastructure (3 fixes):
- docker-compose: upgrade PostgreSQL 15-alpine to 16-alpine per project spec
- init-databases.sh: add 4 missing marketing service databases (mkt_*)
- Traefik routes: add wallet, catalog, booking routers and /api/v1/stock path

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ho Ngoc Hai
2026-03-06 16:22:08 +07:00
parent 7f8709ac9f
commit 653322b26c
21 changed files with 469 additions and 87 deletions

View File

@@ -130,6 +130,7 @@ else
private bool _isLoading = true;
private bool _isProcessing;
private string? _errorMessage;
private bool _disposed = false;
// EN: QR providers / VI: Nhà cung cấp QR
private readonly string[] _providers = { "VietQR", "MoMo", "ZaloPay" };
@@ -160,7 +161,7 @@ else
{
_countdownTimer = new Timer(_ =>
{
if (_timerSeconds > 0)
if (!_disposed && _timerSeconds > 0)
{
_timerSeconds--;
InvokeAsync(StateHasChanged);
@@ -227,5 +228,9 @@ else
NavigationManager.NavigateTo($"/pos/{ShopId}/payment/method-select?orderId={_resolvedOrderId}");
}
public void Dispose() => _countdownTimer?.Dispose();
public void Dispose()
{
_disposed = true;
_countdownTimer?.Dispose();
}
}

View File

@@ -806,7 +806,7 @@ public class PosDataService
}
return System.Text.Json.JsonSerializer.Deserialize<PayOrderResponse>(json, _jsonOptions);
}
catch { return new PayOrderResponse(true, null, null, null, null, null); }
catch { return new PayOrderResponse(false, null, null, null, null, "Không thể xử lý phản hồi từ máy chủ"); }
}
return new PayOrderResponse(false, null, null, null, null, "Payment failed");
}

View File

@@ -1,4 +1,5 @@
using System.Text.Json;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WebClientTpos.Server.Infrastructure;
using WebClientTpos.Server.Models;
@@ -10,6 +11,7 @@ namespace WebClientTpos.Server.Controllers;
/// VI: Controller đơn hàng — proxy đến OrderService cho đơn hàng, POS thanh toán, dashboard.
/// </summary>
[ApiController]
[Authorize]
[Route("api/bff")]
public class OrderController : ControllerBase
{