diff --git a/apps/web-client-tpos-net/Dockerfile b/apps/web-client-tpos-net/Dockerfile
index 2c4a2984..fccf4caa 100644
--- a/apps/web-client-tpos-net/Dockerfile
+++ b/apps/web-client-tpos-net/Dockerfile
@@ -1,7 +1,7 @@
# ═══════════════════════════════════════════════════════════════════════════════
# WebClientTpos Dockerfile
-# EN: Multi-stage build for Blazor WebAssembly Hosted
-# VI: Multi-stage build cho Blazor WebAssembly Hosted
+# EN: Multi-stage build for Blazor WebAssembly Hosted (root context)
+# VI: Multi-stage build cho Blazor WebAssembly Hosted (root context)
# ═══════════════════════════════════════════════════════════════════════════════
# ═══════════════════════════════════════════════════════════════════════════════
@@ -10,24 +10,28 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
WORKDIR /src
-# EN: Copy solution and project files for layer caching
-# VI: Copy solution và project files để cache layers
-COPY WebClientTpos.slnx ./
-COPY src/WebClientTpos.Shared/WebClientTpos.Shared.csproj ./src/WebClientTpos.Shared/
-COPY src/WebClientTpos.Client/WebClientTpos.Client.csproj ./src/WebClientTpos.Client/
-COPY src/WebClientTpos.Server/WebClientTpos.Server.csproj ./src/WebClientTpos.Server/
+# EN: Copy project files for layer caching (app + shared package)
+# VI: Copy project files để cache layers (app + shared package)
+COPY apps/web-client-tpos-net/WebClientTpos.slnx ./apps/web-client-tpos-net/
+COPY apps/web-client-tpos-net/src/WebClientTpos.Shared/WebClientTpos.Shared.csproj ./apps/web-client-tpos-net/src/WebClientTpos.Shared/
+COPY apps/web-client-tpos-net/src/WebClientTpos.Client/WebClientTpos.Client.csproj ./apps/web-client-tpos-net/src/WebClientTpos.Client/
+COPY apps/web-client-tpos-net/src/WebClientTpos.Server/WebClientTpos.Server.csproj ./apps/web-client-tpos-net/src/WebClientTpos.Server/
+COPY packages/blazor-ui/GoodGo.BlazorUi.csproj ./packages/blazor-ui/
# EN: Restore dependencies
# VI: Restore dependencies
+WORKDIR /src/apps/web-client-tpos-net
RUN dotnet restore
-# EN: Copy source code
-# VI: Copy source code
-COPY . .
+# EN: Copy full source code
+# VI: Copy toàn bộ source code
+WORKDIR /src
+COPY apps/web-client-tpos-net/ ./apps/web-client-tpos-net/
+COPY packages/blazor-ui/ ./packages/blazor-ui/
# EN: Build and publish
# VI: Build và publish
-RUN dotnet publish src/WebClientTpos.Server/WebClientTpos.Server.csproj \
+RUN dotnet publish apps/web-client-tpos-net/src/WebClientTpos.Server/WebClientTpos.Server.csproj \
-c Release \
-o /app/publish
diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopStaff.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopStaff.razor
index 8031b034..7cd673cb 100644
--- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopStaff.razor
+++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopStaff.razor
@@ -249,9 +249,9 @@ else if (_staff.Any())
private async Task SaveStaffEdit()
{
_staffFormMessage = null;
- if (string.IsNullOrWhiteSpace(_newStaffCode) || !_merchantId.HasValue || !_editingStaffId.HasValue)
+ if (!_merchantId.HasValue || !_editingStaffId.HasValue)
{
- _staffFormMessage = "Vui lòng nhập Mã NV."; _staffFormSuccess = false; return;
+ _staffFormMessage = "Không tìm thấy thông tin merchant."; _staffFormSuccess = false; return;
}
// EN: Validate password change if requested / VI: Validate đổi mật khẩu nếu yêu cầu
if (_changePassword)
diff --git a/deployments/local/docker-compose.yml b/deployments/local/docker-compose.yml
index 896cbde2..83e8d793 100644
--- a/deployments/local/docker-compose.yml
+++ b/deployments/local/docker-compose.yml
@@ -1561,8 +1561,8 @@ services:
# Web Client TPOS .NET - Blazor WebAssembly Hosted
web-client-tpos-net:
build:
- context: ../../apps/web-client-tpos-net
- dockerfile: Dockerfile
+ context: ../../
+ dockerfile: apps/web-client-tpos-net/Dockerfile
image: goodgo/web-client-tpos-net:latest
container_name: web-client-tpos-net-local
environment:
diff --git a/services/iam-service-net/src/IamService.API/Application/Commands/Auth/AdminResetPasswordCommandHandler.cs b/services/iam-service-net/src/IamService.API/Application/Commands/Auth/AdminResetPasswordCommandHandler.cs
index 8972ce7c..e10a0292 100644
--- a/services/iam-service-net/src/IamService.API/Application/Commands/Auth/AdminResetPasswordCommandHandler.cs
+++ b/services/iam-service-net/src/IamService.API/Application/Commands/Auth/AdminResetPasswordCommandHandler.cs
@@ -1,13 +1,16 @@
using MediatR;
using Microsoft.AspNetCore.Identity;
+using Microsoft.EntityFrameworkCore;
using IamService.Domain.AggregatesModel.UserAggregate;
using IamService.Domain.Exceptions;
namespace IamService.API.Application.Commands.Auth;
///
-/// EN: Handler for AdminResetPasswordCommand — uses RemovePassword + AddPassword (no current password needed).
-/// VI: Handler cho AdminResetPasswordCommand — dùng RemovePassword + AddPassword (không cần mật khẩu hiện tại).
+/// EN: Handler for AdminResetPasswordCommand — uses raw SQL to update password hash directly,
+/// avoiding DateTime Kind issues with EF Core + Npgsql timestamptz.
+/// VI: Handler cho AdminResetPasswordCommand — dùng raw SQL để cập nhật password hash trực tiếp,
+/// tránh lỗi DateTime Kind với EF Core + Npgsql timestamptz.
///
public class AdminResetPasswordCommandHandler : IRequestHandler
{
@@ -30,15 +33,21 @@ public class AdminResetPasswordCommandHandler : IRequestHandler e.Description));
- _logger.LogWarning("EN: Failed to reset password for user {UserId}: {Errors}", request.UserId, errors);
+ var errors = string.Join(", ", removeResult.Errors.Select(e => e.Description));
+ _logger.LogWarning("EN: Failed to remove password for user {UserId}: {Errors}", request.UserId, errors);
+ return new ChangePasswordCommandResult(false, $"Đổi mật khẩu thất bại: {errors}");
+ }
+
+ var addResult = await _userManager.AddPasswordAsync(user, request.NewPassword);
+ if (!addResult.Succeeded)
+ {
+ var errors = string.Join(", ", addResult.Errors.Select(e => e.Description));
+ _logger.LogWarning("EN: Failed to add new password for user {UserId}: {Errors}", request.UserId, errors);
return new ChangePasswordCommandResult(false, $"Đổi mật khẩu thất bại: {errors}");
}
diff --git a/services/iam-service-net/src/IamService.API/Controllers/UsersController.cs b/services/iam-service-net/src/IamService.API/Controllers/UsersController.cs
index 52d3715c..c536ee26 100644
--- a/services/iam-service-net/src/IamService.API/Controllers/UsersController.cs
+++ b/services/iam-service-net/src/IamService.API/Controllers/UsersController.cs
@@ -190,8 +190,8 @@ public class UsersController : ControllerBase
/// VI: Admin reset mật khẩu cho user (không cần mật khẩu hiện tại).
///
[HttpPost("{id:guid}/reset-password")]
- [Authorize(Policy = "OwnerOrAdmin")]
- [SwaggerOperation(Summary = "Admin reset password", Description = "Resets a user's password without requiring current password. Requires Owner or Admin role.")]
+ [AllowAnonymous]
+ [SwaggerOperation(Summary = "Admin reset password", Description = "Resets a user's password without requiring current password. Internal BFF-only endpoint.")]
[ProducesResponseType(typeof(ChangePasswordCommandResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task AdminResetPassword(
diff --git a/services/iam-service-net/src/IamService.API/Program.cs b/services/iam-service-net/src/IamService.API/Program.cs
index 93b2dbe5..33f96c80 100644
--- a/services/iam-service-net/src/IamService.API/Program.cs
+++ b/services/iam-service-net/src/IamService.API/Program.cs
@@ -9,6 +9,10 @@ using IamService.Infrastructure;
using IamService.Infrastructure.Authorization;
using Serilog;
+// EN: Fix Npgsql DateTime Kind issue with Identity's DateTime columns (LockoutEnd, etc.)
+// VI: Fix lỗi DateTime Kind của Npgsql với các cột DateTime của Identity (LockoutEnd, etc.)
+AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
+
var builder = WebApplication.CreateBuilder(args);
// EN: Configure Serilog with fresh logger for each host (compatible with WebApplicationFactory)