diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Systems/UserRequest.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Systems/UserRequest.cs
index 2a0fc26..de5bbc6 100644
--- a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Systems/UserRequest.cs
+++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Systems/UserRequest.cs
@@ -1,4 +1,5 @@
-using System;
+using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Setups;
+using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -71,6 +72,14 @@ public class ResetPasswordRequest : ByUserIdRequest
public string ConfirmPassword { get; set; }
}
+public class BulkPasswordUploadRequest : FileUploadRequest
+{
+ ///
+ /// When true, uploaded users are forced to change their password at next login.
+ ///
+ public bool ForcePasswordChange { get; set; }
+}
+
public class PasswordChangeRequest : ResetPasswordRequest
{
[Required, NotNull, StringLength(maximumLength: 30, MinimumLength = 1, ErrorMessage = "Old Password must be between 1 and 30 characters.")]
diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/Systems/UserResponse.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/Systems/UserResponse.cs
index ee38085..3f8ba3b 100644
--- a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/Systems/UserResponse.cs
+++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/Systems/UserResponse.cs
@@ -124,4 +124,22 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses.Systems
public string LoginId { 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 Items { get; set; } = [];
+ }
}
\ No newline at end of file
diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/Systems/IUserService.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/Systems/IUserService.cs
index 02ca341..b5304cf 100644
--- a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/Systems/IUserService.cs
+++ b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/Systems/IUserService.cs
@@ -32,6 +32,7 @@ namespace OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems
Task AddUserAsync(NewUserRequest user, string ipAddress, int createdBy);
Task UpdateMyInfoAsync(string address, string contactNo, int modifiedBy, int emplyeeId);
Task ResetPasswordAsync(int userId, string newPassword, string ipAddress, int changedBy);
+ Task UpdatePasswordsFromExcelAsync(string fileSpec, string ipAddress, int changedBy, bool forcePasswordChange);
Task UpdateMyThemeAsync(int userId, string menuLayout, string themeName, string schemeName);
Task SaveAuthorizeLimitAsync(decimal maxAuthLimit, int userId, string ipAddress, string savedBy);
Task UploadDocumentAsync(int userId, int id, int documentOf, string orgFileName, string fileName);
diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Integrations/IntegrationService.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Integrations/IntegrationService.cs
index da6abe9..56b3ec3 100644
--- a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Integrations/IntegrationService.cs
+++ b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Integrations/IntegrationService.cs
@@ -292,7 +292,6 @@ public class IntegrationService : IIntegrationService
{
var materialPriceType = await GetMaterialPriceTypeAsync(tc, request.ConditionType) ;
-
var validationMessages = Validate(request, materialPriceType.PriceType);
if(validationMessages.Any())
diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Systems/UserService.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Systems/UserService.cs
index 15eaf22..5640ed6 100644
--- a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Systems/UserService.cs
+++ b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Systems/UserService.cs
@@ -1170,7 +1170,194 @@ namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.Systems
}
///
- ///
+ /// Updates password of multiple users from an uploaded excel file having EmployeeCode and Password columns.
+ ///
+ /// Full path of the uploaded excel file.
+ ///
+ ///
+ /// When true, user must change the password at next login.
+ ///
+ public async Task 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;
+ }
+
+ ///
+ /// Returns the actual column name of the DataTable ignoring case and spaces, null when not found.
+ ///
+ ///
+ ///
+ ///
+ 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;
+ }
+
+ ///
+ ///
///
///
///
diff --git a/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Web/UserController.cs b/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Web/UserController.cs
index 9be295e..19b093e 100644
--- a/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Web/UserController.cs
+++ b/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Web/UserController.cs
@@ -605,7 +605,95 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers.Web
}
///
- ///
+ /// Updates password of multiple users from an excel file having EmployeeCode and Password columns.
+ ///
+ ///
+ ///
+ [ValidateSession]
+ [HttpPost("uploadPasswords")]
+ [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(BulkPasswordUploadResponse))]
+ public async Task 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(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);
+ }
+ }
+
+ ///
+ ///
///
///
///