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.