From ccb7716ba1d0876aa088f0aec2267457196ace72 Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Mon, 30 Mar 2026 10:22:18 +0700 Subject: [PATCH] feat(staff): add password change for existing staff members MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- .../Pages/Admin/Shop/ShopStaff.razor | 62 ++++++++++++++++++- .../Services/PosDataService.cs | 13 ++++ .../Controllers/StaffController.cs | 16 +++++ 3 files changed, 90 insertions(+), 1 deletion(-) 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 1b0ff226..8031b034 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 @@ -67,6 +67,22 @@ } } + else + { + @* EN: Password change section for existing staff / VI: Phần đổi mật khẩu cho nhân viên đã có *@ +
+ + @if (_changePassword) + { +
+
+
+
+ } +
+ }
@@ -147,6 +163,8 @@ else if (_staff.Any()) private string _newStaffFirstName = ""; private string _newStaffLastName = ""; private string _newStaffPassword = ""; + private bool _changePassword; + private string _confirmPassword = ""; // Staff extended fields state private string _newStaffAddress = ""; // Image upload state for staff docs @@ -222,6 +240,9 @@ else if (_staff.Any()) _staffDocFrontPreview = s.DocumentFrontUrl; _staffDocBackPreview = s.DocumentBackUrl; _staffFormMessage = null; + _changePassword = false; + _newStaffPassword = ""; + _confirmPassword = ""; _showStaffForm = true; } @@ -232,6 +253,18 @@ else if (_staff.Any()) { _staffFormMessage = "Vui lòng nhập Mã NV."; _staffFormSuccess = false; return; } + // EN: Validate password change if requested / VI: Validate đổi mật khẩu nếu yêu cầu + if (_changePassword) + { + if (string.IsNullOrWhiteSpace(_newStaffPassword) || _newStaffPassword.Length < 8) + { + _staffFormMessage = "Mật khẩu mới phải có ít nhất 8 ký tự."; _staffFormSuccess = false; return; + } + if (_newStaffPassword != _confirmPassword) + { + _staffFormMessage = "Mật khẩu xác nhận không khớp."; _staffFormSuccess = false; return; + } + } try { var docFrontUrl = await UploadFileIfNeeded(_staffDocFrontFile) ?? (_staffDocFrontPreview?.StartsWith("data:") == true ? null : _staffDocFrontPreview); @@ -239,8 +272,35 @@ else if (_staff.Any()) await DataService.UpdateStaffAsync(_editingStaffId.Value, new PosDataService.CreateStaffRequest( _merchantId.Value, _newStaffCode, _newStaffPhone, _newStaffEmail, _newStaffRole, _newStaffFirstName, _newStaffLastName, _newStaffAddress, null, docFrontUrl, docBackUrl)); - _staffFormMessage = $"Đã cập nhật NV '{_newStaffCode}' thành công!"; _staffFormSuccess = true; + + // EN: Change password if requested / VI: Đổi mật khẩu nếu yêu cầu + if (_changePassword && !string.IsNullOrWhiteSpace(_newStaffPassword)) + { + var editingStaff = _staff.FirstOrDefault(s => s.Id == _editingStaffId); + if (editingStaff?.UserId != null) + { + var (pwOk, pwErr) = await DataService.ResetStaffPasswordAsync(editingStaff.UserId.Value, _newStaffPassword); + if (!pwOk) + { + _staffFormMessage = $"Đã cập nhật thông tin NV, nhưng đổi mật khẩu thất bại: {pwErr}"; + _staffFormSuccess = false; + _staff = await DataService.GetStaffForShopAsync(ShopId); + return; + } + } + else + { + _staffFormMessage = "Đã cập nhật thông tin NV, nhưng nhân viên chưa có tài khoản IAM để đổi mật khẩu."; + _staffFormSuccess = false; + _staff = await DataService.GetStaffForShopAsync(ShopId); + return; + } + } + + _staffFormMessage = $"Đã cập nhật NV '{_newStaffCode}' thành công!{(_changePassword ? " Mật khẩu đã được đổi." : "")}"; + _staffFormSuccess = true; _editingStaffId = null; + _changePassword = false; _newStaffPassword = ""; _confirmPassword = ""; _staffDocFrontFile = null; _staffDocBackFile = null; _staffDocFrontPreview = null; _staffDocBackPreview = null; _staff = await DataService.GetStaffForShopAsync(ShopId); } diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Services/PosDataService.cs b/apps/web-client-tpos-net/src/WebClientTpos.Client/Services/PosDataService.cs index c2064487..73de71dc 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Services/PosDataService.cs +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Services/PosDataService.cs @@ -1191,6 +1191,19 @@ public class PosDataService return (false, err); } + /// + /// EN: Reset a staff member's password via IAM service (admin action). + /// VI: Đặt lại mật khẩu nhân viên qua IAM service (hành động admin). + /// + public async Task<(bool Ok, string? Error)> ResetStaffPasswordAsync(Guid userId, string newPassword) + { + var resp = await _http.PostAsJsonAsync("api/bff/staff/reset-password", + new { userId, newPassword }, _writeOptions); + if (resp.IsSuccessStatusCode) return (true, null); + var err = await TryExtractError(resp); + return (false, err); + } + // ═══ STORAGE / DRIVE ═══ public record StorageFileInfo(Guid Id, string FileName, string? ContentType, long FileSizeBytes, string? AccessLevel, DateTime UploadedAt); diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/StaffController.cs b/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/StaffController.cs index ce476b5d..e57695d9 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/StaffController.cs +++ b/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/StaffController.cs @@ -124,6 +124,22 @@ public class StaffController : ControllerBase } } + /// + /// EN: Reset a staff member's password via IAM service (admin action). + /// VI: Đặt lại mật khẩu nhân viên qua IAM service (hành động admin). + /// + [HttpPost("staff/reset-password")] + public async Task ResetStaffPassword([FromBody] JsonElement body) + { + var userId = body.TryGetProperty("userId", out var uid) ? uid.GetString() : null; + var newPassword = body.TryGetProperty("newPassword", out var np) ? np.GetString() : null; + if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(newPassword)) + return BadRequest(new { success = false, message = "userId và newPassword là bắt buộc." }); + + var payload = new { userId, newPassword }; + return await _iam.PostAsJsonAsync($"/api/v1/users/{userId}/reset-password", payload).ProxyAsync(); + } + /// /// EN: Update a staff member. /// VI: Cập nhật nhân viên.