update user password via excel

This commit is contained in:
dibakor 2026-08-10 18:13:54 +06:00
parent b93734f888
commit 68cf33d5a6
6 changed files with 306 additions and 4 deletions

View File

@ -1,4 +1,5 @@
using System; using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Setups;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@ -71,6 +72,14 @@ public class ResetPasswordRequest : ByUserIdRequest
public string ConfirmPassword { get; set; } public string ConfirmPassword { get; set; }
} }
public class BulkPasswordUploadRequest : FileUploadRequest
{
/// <summary>
/// When true, uploaded users are forced to change their password at next login.
/// </summary>
public bool ForcePasswordChange { get; set; }
}
public class PasswordChangeRequest : ResetPasswordRequest public class PasswordChangeRequest : ResetPasswordRequest
{ {
[Required, NotNull, StringLength(maximumLength: 30, MinimumLength = 1, ErrorMessage = "Old Password must be between 1 and 30 characters.")] [Required, NotNull, StringLength(maximumLength: 30, MinimumLength = 1, ErrorMessage = "Old Password must be between 1 and 30 characters.")]

View File

@ -124,4 +124,22 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses.Systems
public string LoginId { get; set; } public string LoginId { get; set; }
public decimal Value { get; set; } public decimal Value { get; set; }
} }
public class BulkPasswordUploadItem
{
public int RowNo { get; set; }
public int UserId { get; set; }
public string EmployeeCode { get; set; }
public string LoginId { get; set; }
public bool Updated { get; set; }
public string Message { get; set; }
}
public class BulkPasswordUploadResponse : ResponseBase
{
public int TotalRows { get; set; }
public int SuccessCount { get; set; }
public int FailedCount { get; set; }
public List<BulkPasswordUploadItem> Items { get; set; } = [];
}
} }

View File

@ -32,6 +32,7 @@ namespace OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems
Task<bool> AddUserAsync(NewUserRequest user, string ipAddress, int createdBy); Task<bool> AddUserAsync(NewUserRequest user, string ipAddress, int createdBy);
Task<bool> UpdateMyInfoAsync(string address, string contactNo, int modifiedBy, int emplyeeId); Task<bool> UpdateMyInfoAsync(string address, string contactNo, int modifiedBy, int emplyeeId);
Task<bool> ResetPasswordAsync(int userId, string newPassword, string ipAddress, int changedBy); Task<bool> ResetPasswordAsync(int userId, string newPassword, string ipAddress, int changedBy);
Task<BulkPasswordUploadResponse> UpdatePasswordsFromExcelAsync(string fileSpec, string ipAddress, int changedBy, bool forcePasswordChange);
Task<bool> UpdateMyThemeAsync(int userId, string menuLayout, string themeName, string schemeName); Task<bool> UpdateMyThemeAsync(int userId, string menuLayout, string themeName, string schemeName);
Task<bool> SaveAuthorizeLimitAsync(decimal maxAuthLimit, int userId, string ipAddress, string savedBy); Task<bool> SaveAuthorizeLimitAsync(decimal maxAuthLimit, int userId, string ipAddress, string savedBy);
Task<bool> UploadDocumentAsync(int userId, int id, int documentOf, string orgFileName, string fileName); Task<bool> UploadDocumentAsync(int userId, int id, int documentOf, string orgFileName, string fileName);

View File

@ -292,7 +292,6 @@ public class IntegrationService : IIntegrationService
{ {
var materialPriceType = await GetMaterialPriceTypeAsync(tc, request.ConditionType) ; var materialPriceType = await GetMaterialPriceTypeAsync(tc, request.ConditionType) ;
var validationMessages = Validate(request, materialPriceType.PriceType); var validationMessages = Validate(request, materialPriceType.PriceType);
if(validationMessages.Any()) if(validationMessages.Any())

View File

@ -1170,7 +1170,194 @@ namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.Systems
} }
/// <summary> /// <summary>
/// /// Updates password of multiple users from an uploaded excel file having EmployeeCode and Password columns.
/// </summary>
/// <param name="fileSpec">Full path of the uploaded excel file.</param>
/// <param name="ipAddress"></param>
/// <param name="changedBy"></param>
/// <param name="forcePasswordChange">When true, user must change the password at next login.</param>
/// <returns></returns>
public async Task<BulkPasswordUploadResponse> UpdatePasswordsFromExcelAsync(string fileSpec, string ipAddress, int changedBy, bool forcePasswordChange)
{
BulkPasswordUploadResponse response = new() { ReturnStatus = 200 };
try
{
if (string.IsNullOrEmpty(fileSpec) || !System.IO.File.Exists(fileSpec))
{
response.ReturnStatus = 417;
response.ReturnMessage.Add("Uploaded excel file was not found.");
return response;
}
#region Read data from Excel
DataTable table = null;
DataSet ds = ExcelReader.GetDataSetFromFile(fileSpec: fileSpec, firstRowColumnHeader: true);
if (ds != null && ds.Tables.Count > 0)
table = ds.Tables[0];
if (table == null || table.Rows.Count <= 0)
{
response.ReturnStatus = 417;
response.ReturnMessage.Add("There is no data in the excel file to process.");
return response;
}
string empCodeColumn = GetColumnName(table: table, columnName: "EmployeeCode");
string passwordColumn = GetColumnName(table: table, columnName: "Password");
if (string.IsNullOrEmpty(empCodeColumn) || string.IsNullOrEmpty(passwordColumn))
{
response.ReturnStatus = 417;
response.ReturnMessage.Add("Excel file must have [EmployeeCode] and [Password] columns in the first row.");
return response;
}
#endregion
using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
try
{
int rowNo = 1; //First row of the excel file is the column header
foreach (DataRow row in table.Rows)
{
rowNo++;
string employeeCode = $"{row[empCodeColumn]}".Trim();
string password = $"{row[passwordColumn]}".Trim();
if (string.IsNullOrEmpty(employeeCode) && string.IsNullOrEmpty(password))
continue; //Skip completely blank row
response.TotalRows++;
BulkPasswordUploadItem item = new() { RowNo = rowNo, EmployeeCode = employeeCode };
if (string.IsNullOrEmpty(employeeCode))
{
item.Message = "Employee Code is empty.";
response.FailedCount++;
response.Items.Add(item);
continue;
}
if (string.IsNullOrEmpty(password))
{
item.Message = "Password is empty.";
response.FailedCount++;
response.Items.Add(item);
continue;
}
#region Find the user by Employee Code
int userId = 0;
int matchCount = 0;
string loginId = string.Empty;
using (IDataReader dr = tc.ExecuteReader("SELECT UserId, LoginId FROM Users WHERE EmployeeCode=%s OR EmployeeNumber=%s", employeeCode, employeeCode))
{
while (dr.Read())
{
matchCount++;
if (matchCount == 1)
{
userId = dr.GetInt32(0);
loginId = dr.IsDBNull(1) ? string.Empty : dr.GetString(1);
}
}
dr.Close();
}
if (matchCount <= 0)
{
item.Message = $"No user found against Employee Code [{employeeCode}].";
response.FailedCount++;
response.Items.Add(item);
continue;
}
if (matchCount > 1)
{
item.Message = $"More than one user found against Employee Code [{employeeCode}].";
response.FailedCount++;
response.Items.Add(item);
continue;
}
#endregion
SqlParameter[] p =
[
SqlHelperExtension.CreateInParam(pName: "@NewPwd", pType: SqlDbType.VarChar, pValue: EncryptPassword(password: password), size: 75),
SqlHelperExtension.CreateInParam(pName: "@LastPwds", pType: SqlDbType.VarChar, pValue: string.Empty, size: 700),
SqlHelperExtension.CreateInParam(pName: "@LastPwdChgDate", pType: SqlDbType.DateTime, pValue: DateTime.Now),
SqlHelperExtension.CreateInParam(pName: "@PwdExpireDate", pType: SqlDbType.DateTime, pValue: DateTime.Now),
SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20),
SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId),
SqlHelperExtension.CreateInParam(pName: "@ChangedBy", pType: SqlDbType.Int, pValue: changedBy),
SqlHelperExtension.CreateInParam(pName: "@ResetPwd", pType: SqlDbType.SmallInt, pValue: (short)(forcePasswordChange ? 1 : 0))
];
_ = tc.ExecuteNonQuerySp(spName: "dbo.UpdateUserPassword", parameterValues: p);
item.Updated = true;
item.UserId = userId;
item.LoginId = loginId;
item.Message = "Password updated successfully.";
response.SuccessCount++;
response.Items.Add(item);
}
tc.End();
}
catch (Exception ie)
{
tc?.HandleError();
throw DBCustomError.GenerateCustomError(ie);
}
if (response.TotalRows <= 0)
{
response.ReturnStatus = 417;
response.ReturnMessage.Add("There is no data in the excel file to process.");
}
else
{
response.ReturnMessage.Add($"{response.SuccessCount} of {response.TotalRows} user password(s) updated successfully.");
}
}
catch (Exception e)
{
throw new InvalidOperationException(e.Message, e);
}
return response;
}
/// <summary>
/// Returns the actual column name of the DataTable ignoring case and spaces, null when not found.
/// </summary>
/// <param name="table"></param>
/// <param name="columnName"></param>
/// <returns></returns>
private static string GetColumnName(DataTable table, string columnName)
{
foreach (DataColumn column in table.Columns)
{
if (string.Equals(column.ColumnName.Replace(" ", string.Empty).Trim(), columnName, StringComparison.OrdinalIgnoreCase))
return column.ColumnName;
}
return null;
}
/// <summary>
///
/// </summary> /// </summary>
/// <param name="userId"></param> /// <param name="userId"></param>
/// <param name="newPassword"></param> /// <param name="newPassword"></param>

View File

@ -605,7 +605,95 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers.Web
} }
/// <summary> /// <summary>
/// /// Updates password of multiple users from an excel file having EmployeeCode and Password columns.
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[ValidateSession]
[HttpPost("uploadPasswords")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(BulkPasswordUploadResponse))]
public async Task<IActionResult> UploadPasswords([FromForm] BulkPasswordUploadRequest request)
{
ArgumentNullException.ThrowIfNull(request);
BulkPasswordUploadResponse response = new() { ReturnStatus = StatusCodes.Status200OK };
if (string.IsNullOrEmpty(request.FileName))
{
response.ReturnStatus = StatusCodes.Status417ExpectationFailed;
response.ReturnMessage.Add("There is no valid file to process.");
return StatusCode(StatusCodes.Status417ExpectationFailed, response);
}
if (request.FileData == null || request.FileData.Length <= 0)
{
response.ReturnStatus = StatusCodes.Status417ExpectationFailed;
response.ReturnMessage.Add("There is no valid data to process.");
return StatusCode(StatusCodes.Status417ExpectationFailed, response);
}
string[] allowedExtensions = [".xlsx", ".xls"];
string fileExtension = Path.GetExtension(request.FileName).ToLowerInvariant();
if (!allowedExtensions.Contains(fileExtension))
{
response.ReturnStatus = StatusCodes.Status417ExpectationFailed;
response.ReturnMessage.Add("Only excel file is allowed to process.");
return BadRequest(response);
}
Result result = fileExtension.Equals(".xls") ? ExcelFileValidator.Validate(request.FileData) : ExcelxFileValidator.Validate(request.FileData);
if (!result.Acceptable)
{
response.ReturnStatus = StatusCodes.Status417ExpectationFailed;
response.ReturnMessage.Add("This is not a valid Excel file.");
return BadRequest(response);
}
long maxSz = 10 * 1024 * 1024;
if (request.FileData.Length > maxSz)
{
response.ReturnStatus = StatusCodes.Status417ExpectationFailed;
response.ReturnMessage.Add("Maximum allowable size is 10 MB");
return StatusCode(StatusCodes.Status417ExpectationFailed, response);
}
bool permitted = await HttpContext.IsPermitted("ELIT.1.2.3_2");
if (!permitted)
{
response.ReturnStatus = StatusCodes.Status403Forbidden;
response.ReturnMessage.Add("You are not authorize to Reset Password.");
return StatusCode(StatusCodes.Status417ExpectationFailed, response);
}
string fileSpec = Path.Combine(_appSettings.UploadFolder, $"PWD_{DateTime.Now:yyyyMMddHHmmssfff}{fileExtension}");
try
{
using (var stream = new FileStream(fileSpec, FileMode.Create))
{
await request.FileData.CopyToAsync(stream);
}
string ipAddress = Request.HttpContext.GetIpAddress();
int changedBy = HttpContext.User.GetClaimValue<int>(Constants.UserId);
response = await _service.UpdatePasswordsFromExcelAsync(fileSpec: fileSpec, ipAddress: ipAddress, changedBy: changedBy, forcePasswordChange: request.ForcePasswordChange);
return Ok(response);
}
catch (Exception ex)
{
_logger.LogError(ex);
response.ReturnStatus = StatusCodes.Status500InternalServerError;
response.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, response);
}
finally
{
if (System.IO.File.Exists(fileSpec))
System.IO.File.Delete(fileSpec);
}
}
/// <summary>
///
/// </summary> /// </summary>
/// <param name="request"></param> /// <param name="request"></param>
/// <returns></returns> /// <returns></returns>