diff --git a/.gitignore b/.gitignore index fccb11b..bc53bdd 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ obj /App/ClientApp/node_modules /Api/OnlineSalesAutoCrop.CoreAPI/.config/dotnet-tools.json /App/ClientApp/.angular/cache/21.2.13/OnlineSalesAutoCropApp +/Deployment/App/browser diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Common/LoginRequest.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Common/LoginRequest.cs index 2a579c5..3341489 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Common/LoginRequest.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Common/LoginRequest.cs @@ -6,7 +6,7 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.Common; public class AuthLoginRequest { - [Required, NotNull, StringLength(maximumLength: 10, MinimumLength = 3, ErrorMessage = "Login Id must be between 3 and 10 characters.")] + [Required, NotNull, StringLength(maximumLength: 15, MinimumLength = 3, ErrorMessage = "Login Id must be between 3 and 15 characters.")] public string LoginId { get; set; } [Required, NotNull, StringLength(maximumLength: 20, MinimumLength = 5, ErrorMessage = "Password must be between 5 and 20 characters.")] diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Integrations/IntegrationEmployeeRequest.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Integrations/IntegrationEmployeeRequest.cs index d0ed6cc..0f8e731 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Integrations/IntegrationEmployeeRequest.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Integrations/IntegrationEmployeeRequest.cs @@ -25,7 +25,6 @@ public class IntegrationEmployeeRequest [Phone(ErrorMessage = "Mobile number is not a valid phone number.")] public string MobileNo { get; set; } - [Required(ErrorMessage = "Email address is required.")] [StringLength(241, ErrorMessage = "Email address cannot exceed 241 characters.")] [EmailAddress(ErrorMessage = "Email address is not valid.")] public string Mail { get; set; } diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/MobileApp/MobileMasterDataRequest.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/MobileApp/MobileMasterDataRequest.cs index 9d0620c..9a103c4 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/MobileApp/MobileMasterDataRequest.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/MobileApp/MobileMasterDataRequest.cs @@ -8,85 +8,85 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp; public class GetMarketHeirarchyByEmpRequest { - [Required, NotNull, StringLength(maximumLength: 10, MinimumLength = 3, ErrorMessage = "EmployeeNumber must be between 3 and 10 characters.")] + [Required, NotNull, StringLength(maximumLength: 12, MinimumLength = 3, ErrorMessage = "EmployeeNumber must be between 3 and 12 characters.")] public string EmployeeNumber { get; set; } } public class GetBrandsRequest : PagedRequest { - [StringLength(6, MinimumLength = 1, ErrorMessage = "Brand code must be between 1 and 6 characters.")] + [StringLength(15, MinimumLength = 1, ErrorMessage = "Brand code must be between 1 and 15 characters.")] public string? BrandCode { get; set; } - [StringLength(10, MinimumLength = 1, ErrorMessage = "Sales organization code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 1, ErrorMessage = "Sales organization code must be between 1 and 15 characters.")] public string? SalesOrgCode { get; set; } - [StringLength(10, MinimumLength = 1, ErrorMessage = "Employee number must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 1, ErrorMessage = "Employee number must be between 1 and 15 characters.")] public string? EmployeeNumber { get; set; } } public class GetMaterialsRequest : PagedRequest { - [StringLength(10, MinimumLength = 1, ErrorMessage = "Sales organization code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 1, ErrorMessage = "Sales organization code must be between 1 and 15 characters.")] public string SalesOrgCode { get; set; } [StringLength(6, MinimumLength = 1, ErrorMessage = "Brand code must be between 1 and 6 characters.")] public string? BrandCode { get; set; } - [StringLength(10, MinimumLength = 1, ErrorMessage = "Material Code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 1, ErrorMessage = "Material Code must be between 1 and 15 characters.")] public string? MaterialCode { get; set; } - [StringLength(10, MinimumLength = 1, ErrorMessage = "Employee number must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 1, ErrorMessage = "Employee number must be between 1 and 15 characters.")] public string? EmployeeNumber { get; set; } } public class GetMaterialStockRequest : PagedRequest { - [StringLength(10, MinimumLength = 1, ErrorMessage = "Plant Code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 1, ErrorMessage = "Plant Code must be between 1 and 15 characters.")] public string PlantCode { get; set; } } public class GetMaterialPriceRequest : PagedRequest { - [StringLength(10, MinimumLength = 1, ErrorMessage = "Sales organization code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 1, ErrorMessage = "Sales organization code must be between 1 and 15 characters.")] public string SalesOrgCode { get; set; } - [StringLength(6, MinimumLength = 1, ErrorMessage = "Brand code must be between 1 and 6 characters.")] + [StringLength(15, MinimumLength = 1, ErrorMessage = "Brand code must be between 1 and 15 characters.")] public string? BrandCode { get; set; } - [StringLength(10, MinimumLength = 1, ErrorMessage = "Material Code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 1, ErrorMessage = "Material Code must be between 1 and 15 characters.")] public string? MaterialCode { get; set; } - [StringLength(10, MinimumLength = 1, ErrorMessage = "Employee number must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 1, ErrorMessage = "Employee number must be between 1 and 15 characters.")] public string? EmployeeNumber { get; set; } } public class GetCustomersRequest : PagedRequest { - [StringLength(10, MinimumLength = 3, ErrorMessage = "Sales organization code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 3, ErrorMessage = "Sales organization code must be between 1 and 15 characters.")] public string SalesOrgCode { get; set; } [StringLength(5, MinimumLength = 3, ErrorMessage = "Teritory Code must be between 1 and 5 characters.")] public string? TeritoryCode { get; set; } - [StringLength(10, MinimumLength = 5, ErrorMessage = "Customer Code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 5, ErrorMessage = "Customer Code must be between 1 and 15 characters.")] public string? CustomerCode { get; set; } - [StringLength(10, MinimumLength = 5, ErrorMessage = "Employee number must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 5, ErrorMessage = "Employee number must be between 1 and 15 characters.")] public string? EmployeeNumber { get; set; } } public class GetAttendanceByEmpRequest { - [Required, NotNull, StringLength(maximumLength: 10, MinimumLength = 3, ErrorMessage = "EmployeeNumber must be between 3 and 10 characters.")] + [Required, NotNull, StringLength(maximumLength: 15, MinimumLength = 3, ErrorMessage = "EmployeeNumber must be between 3 and 15 characters.")] public string EmployeeNumber { get; set; } public DateTime? AttendanceDate { get; set; } } public class UpsertAttendanceByEmpRequest { - [Required, NotNull, StringLength(maximumLength: 10, MinimumLength = 3, ErrorMessage = "EmployeeNumber must be between 3 and 10 characters.")] + [Required, NotNull, StringLength(maximumLength: 15, MinimumLength = 3, ErrorMessage = "EmployeeNumber must be between 3 and 15 characters.")] public string EmployeeNumber { get; set; } public TimeSpan? CheckInTime { get; set; } public string? CheckInLatitude { get; set; } @@ -102,37 +102,37 @@ public class UpsertAttendanceByEmpRequest public class GetDiscountRequest : PagedRequest { - [StringLength(10, MinimumLength = 3, ErrorMessage = "Sales organization code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 3, ErrorMessage = "Sales organization code must be between 1 and 15 characters.")] public string SalesOrgCode { get; set; } - [StringLength(10, MinimumLength = 5, ErrorMessage = "Customer Code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 5, ErrorMessage = "Customer Code must be between 1 and 15 characters.")] public string? CustomerCode { get; set; } - [StringLength(10, MinimumLength = 5, ErrorMessage = "Employee number must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 5, ErrorMessage = "Employee number must be between 1 and 15 characters.")] public string? EmployeeNumber { get; set; } - [StringLength(10, MinimumLength = 3, ErrorMessage = "Material Code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 3, ErrorMessage = "Material Code must be between 1 and 15 characters.")] public string? MaterialCode { get; set; } - [StringLength(10, MinimumLength = 1, ErrorMessage = "Discount Code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 1, ErrorMessage = "Discount Code must be between 1 and 15 characters.")] public string? DiscountCode { get; set; } } public class GetDiscountMaterialRequest : PagedRequest { - [StringLength(10, MinimumLength = 3, ErrorMessage = "Sales organization code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 3, ErrorMessage = "Sales organization code must be between 1 and 15 characters.")] public string SalesOrgCode { get; set; } - [StringLength(10, MinimumLength = 5, ErrorMessage = "Customer Code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 5, ErrorMessage = "Customer Code must be between 1 and 15 characters.")] public string? CustomerCode { get; set; } - [StringLength(10, MinimumLength = 5, ErrorMessage = "Employee number must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 5, ErrorMessage = "Employee number must be between 1 and 15 characters.")] public string? EmployeeNumber { get; set; } - [StringLength(10, MinimumLength = 3, ErrorMessage = "Material Code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 3, ErrorMessage = "Material Code must be between 1 and 15 characters.")] public string? MaterialCode { get; set; } - [StringLength(10, MinimumLength = 1, ErrorMessage = "Discount Code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 1, ErrorMessage = "Discount Code must be between 1 and 15 characters.")] public string? DiscountCode { get; set; } } @@ -140,46 +140,46 @@ public class GetDiscountMaterialRequest : PagedRequest public class GetFOCRequest : PagedRequest { - [StringLength(10, MinimumLength = 3, ErrorMessage = "Sales organization code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 3, ErrorMessage = "Sales organization code must be between 1 and 15 characters.")] public string SalesOrgCode { get; set; } - [StringLength(10, MinimumLength = 5, ErrorMessage = "Customer Code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 5, ErrorMessage = "Customer Code must be between 1 and 15 characters.")] public string? CustomerCode { get; set; } - [StringLength(10, MinimumLength = 5, ErrorMessage = "Employee number must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 5, ErrorMessage = "Employee number must be between 1 and 15 characters.")] public string? EmployeeNumber { get; set; } - [StringLength(10, MinimumLength = 3, ErrorMessage = "Material Code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 3, ErrorMessage = "Material Code must be between 1 and 15 characters.")] public string? MaterialCode { get; set; } - [StringLength(10, MinimumLength = 1, ErrorMessage = "Discount Code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 1, ErrorMessage = "Discount Code must be between 1 and 15 characters.")] public string? FOCCode { get; set; } } public class GetFOCMaterialRequest : PagedRequest { - [StringLength(10, MinimumLength = 3, ErrorMessage = "Sales organization code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 3, ErrorMessage = "Sales organization code must be between 1 and 15 characters.")] public string SalesOrgCode { get; set; } - [StringLength(10, MinimumLength = 5, ErrorMessage = "Customer Code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 5, ErrorMessage = "Customer Code must be between 1 and 15 characters.")] public string? CustomerCode { get; set; } - [StringLength(10, MinimumLength = 5, ErrorMessage = "Employee number must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 5, ErrorMessage = "Employee number must be between 1 and 15 characters.")] public string? EmployeeNumber { get; set; } - [StringLength(10, MinimumLength = 3, ErrorMessage = "Material Code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 3, ErrorMessage = "Material Code must be between 1 and 15 characters.")] public string? MaterialCode { get; set; } - [StringLength(10, MinimumLength = 1, ErrorMessage = "Discount Code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 1, ErrorMessage = "Discount Code must be between 1 and 15 characters.")] public string? FOCCode { get; set; } } public class GetPaymentTermRequest : PagedRequest { - [StringLength(10, MinimumLength = 3, ErrorMessage = "Sales organization code must be between 1 and 10 characters.")] + [StringLength(15, MinimumLength = 3, ErrorMessage = "Sales organization code must be between 1 and 15 characters.")] public string SalesOrgCode { get; set; } - [StringLength(10, MinimumLength = 3, ErrorMessage = "PayementTerm Code must be between 3 and 10 characters.")] + [StringLength(15, MinimumLength = 3, ErrorMessage = "PayementTerm Code must be between 3 and 15 characters.")] public string? PaymentTermCode { get; set; } } \ No newline at end of file diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/MobileApp/OrderResponse.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/MobileApp/OrderResponse.cs index 42eafab..8539be6 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/MobileApp/OrderResponse.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/MobileApp/OrderResponse.cs @@ -41,6 +41,8 @@ public class GetSalesOrderResponse public decimal? TotalVatValue { get; set; } public decimal NetValue { get; set; } public int OrderStatus { get; set; } + public string BillingNumber { get; set; } + public DateTime? BillingDate { get; set; } public List Items { get; set; } = new(); } @@ -91,6 +93,9 @@ public sealed class CustomerLookupDto public int CustomerId { get; set; } public string CustomerCode { get; set; } = string.Empty; public string CustomerName { get; set; } = string.Empty; + public bool IsCreditCustomer { get; set; } = false; + public string CompanyCode { get; set; } + public decimal CreditLimit { get; set; } } public sealed class PaymentTermLookupDto diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Integrations/IntegrationService.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Integrations/IntegrationService.cs index 56b3ec3..7bfa4a1 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Integrations/IntegrationService.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Integrations/IntegrationService.cs @@ -171,7 +171,18 @@ public class IntegrationService : IIntegrationService await AddUserAsync(tc, appUser); } + else + { + AppUserCreateRequest appUser = new() + { + MobileNo = request.MobileNo, + EmailAddress = request.Mail, + UserRoleType = roleTypeId, + EmployeeNumber = request.EmployeeVendorCode + }; + await UpdateUserRoleAsync(tc, appUser); + } response.EmployeeVendorCode = request.EmployeeVendorCode; response.CompanyCode = request.CompanyCode; tc.End(); @@ -820,6 +831,39 @@ public class IntegrationService : IIntegrationService } + public async Task UpdateUserRoleAsync(TransactionContext tc, AppUserCreateRequest user) + { + bool returnValue; + try + { + int userId = 0; + user.Password = EncryptPassword(password: user.Password); + + if (!string.IsNullOrEmpty(user.EmailAddress)) + user.EmailAddress = user.EmailAddress.Replace(" ", ""); + + + SqlParameter[] p = + [ + SqlHelperExtension.CreateInParam(pName: "@MobileNo", pType: SqlDbType.VarChar, pValue: DataReader.GetNullValue(user.MobileNo), size: 20), + SqlHelperExtension.CreateInParam(pName: "@EmailAddress", pType: SqlDbType.VarChar, pValue: user.EmailAddress, size: 200), + SqlHelperExtension.CreateInParam(pName: "@UserRoleType", pType: SqlDbType.Int, pValue: DataReader.GetNullValue(user.UserRoleType)), + SqlHelperExtension.CreateInParam(pName: "@EmployeeNumber", pType: SqlDbType.VarChar, pValue: DataReader.GetNullValue(user.EmployeeNumber), size: 10), + SqlHelperExtension.CreateOutParam(pName:"@NewUserId", pType: SqlDbType.Int) + ]; + + _ = tc.ExecuteNonQuerySp(spName: "dbo.UpdateUserRole", parameterValues: p); + + returnValue = true; + } + catch (Exception e) + { + throw new InvalidOperationException(e.Message, e); + } + + return returnValue; + } + private string EncryptPassword(string password) { try diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/MobileApp/MobileMasterDataService.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/MobileApp/MobileMasterDataService.cs index 9d5c8c0..7b26076 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/MobileApp/MobileMasterDataService.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/MobileApp/MobileMasterDataService.cs @@ -275,6 +275,7 @@ public class MobileMasterDataService : IMobileMasterDataService public async Task> GetCustomersAsync(GetCustomersRequest request) { string companyCode = string.Empty; + string salesOrgCode = string.Empty; decimal usedCreditBalance = 0; PagedResult response = new(); try @@ -307,7 +308,7 @@ public class MobileMasterDataService : IMobileMasterDataService DivisionCode = dr.GetString(6), DivisionName = dr.GetString(7), MobileNumber = dr.GetString(8), - EmailAddress = dr.GetString(9), + EmailAddress = dr.IsDBNull(9) ? null : dr.GetString(9), TaxNumber = dr.GetString(10), SalesGroupCode = dr.IsDBNull(11)? null : dr.GetString(11), SalesGroupName = dr.IsDBNull(12) ? null : dr.GetString(12), @@ -325,6 +326,7 @@ public class MobileMasterDataService : IMobileMasterDataService CreditOverdue = dr.GetDecimal(24), }); companyCode = dr.GetString(25); + salesOrgCode = dr.GetString(19); } dr.Close(); @@ -335,7 +337,7 @@ public class MobileMasterDataService : IMobileMasterDataService if (!string.IsNullOrWhiteSpace(request.CustomerCode)) { - usedCreditBalance = await GetCurrentApprovedOrderAmountByCustomerAsync(tc, request.CustomerCode, request.SalesOrgCode); + usedCreditBalance = await GetCurrentApprovedOrderAmountByCustomerAsync(tc, request.CustomerCode, salesOrgCode); } tc.End(); @@ -349,7 +351,7 @@ public class MobileMasterDataService : IMobileMasterDataService { response.Items = response.Items.Select(x => { - x.CreditBalance = (customerLedger.CurrentBalance - usedCreditBalance); + x.CreditBalance = (customerLedger.CurrentBalance + usedCreditBalance); x.CreditOverdue = customerLedger.OverdueAmount; return x; }).ToList(); @@ -481,9 +483,9 @@ public class MobileMasterDataService : IMobileMasterDataService response.Add(new DistibutionChannleResponse { CompanyCode = dr.GetString(0), - SalesOrgCode = dr.GetString(1), - DistributorChannelCode = dr.GetString(2), - DistributorChannelName = dr.GetString(3) + SalesOrgCode = dr.IsDBNull(1) ? null: dr.GetString(1), + DistributorChannelCode = dr.IsDBNull(2) ? null : dr.GetString(2), + DistributorChannelName = dr.IsDBNull(3) ? null : dr.GetString(3) }); } dr.Close(); diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/MobileApp/OrderService.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/MobileApp/OrderService.cs index d0a8156..43df29c 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/MobileApp/OrderService.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/MobileApp/OrderService.cs @@ -173,7 +173,22 @@ public class OrderService : IOrderService ApprovedFocQty = (request.OrderStatus == OrderStatusEnum.CancelledByApprover || request.OrderStatus == OrderStatusEnum.Approved) ? focItem.OrderQuantity : 0, PromotionType = 2, }); - } + } + + decimal usedCreditBalance = await GetCurrentApprovedOrderAmountByCustomerAsync(tc, request.CustomerCode, request.SalesOrgCode); + + + if (request.OrderStatus == OrderStatusEnum.Approved && customer.IsCreditCustomer && request.PaymentTermCode != "0001") + { + var customerLedger = await _httpService.GetCustomerLedgerAsync(request.CustomerCode, customer.CompanyCode); + + var creditBalance = customer.CreditLimit- (customerLedger.CurrentBalance + usedCreditBalance); + + if(creditBalance < request.NetValue) + { + throw new Exception("Credit Limit is extend for this customer"); + } + } // 5. Insert or update int orderId; @@ -208,6 +223,33 @@ public class OrderService : IOrderService return response; } + private async Task GetCurrentApprovedOrderAmountByCustomerAsync(TransactionContext tc, string customerCode, string salesOrgCode) + { + decimal totalApprovedAmount = 0; + try + { + SqlParameter[] p = + [ + SqlHelperExtension.CreateInParam(pName: "@CustomerCode", pType: SqlDbType.NVarChar, pValue: customerCode), + SqlHelperExtension.CreateInParam(pName: "@SalesOrgCode", pType: SqlDbType.NVarChar, pValue: salesOrgCode), + ]; + using (IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetCustomerApprovedAmount", p)) + { + if (dr.Read()) + { + totalApprovedAmount = dr.GetDecimal(0); + } + dr.Close(); + } + } + catch (Exception) + { + throw; + } + + return totalApprovedAmount; + } + private bool IsPermissiontoModify(OrderStatusEnum orderStatus, UserRoleTypeEnum roleId) { if( UserRoleTypeEnum.OrderCollector == roleId && ( OrderStatusEnum.Draft == orderStatus || OrderStatusEnum.InOrderValidation == orderStatus)) @@ -308,7 +350,10 @@ public class OrderService : IOrderService { CustomerId = dr.GetInt32(0), CustomerCode = dr.GetString(1), - CustomerName = dr.GetString(2) + CustomerName = dr.GetString(2), + IsCreditCustomer = dr.GetInt32(3) > 0, + CompanyCode = dr.GetString(4), + CreditLimit = dr.IsDBNull(5)? 0 : dr.GetDecimal(5) }; } @@ -777,7 +822,9 @@ public class OrderService : IOrderService GrossValue = dr.GetDecimal(13), NetValue = dr.GetDecimal(14), OrderStatus = dr.GetInt32(15), - TotalVatValue = dr.IsDBNull(17)? null: dr.GetDecimal(17) + TotalVatValue = dr.IsDBNull(17) ? null: dr.GetDecimal(17), + BillingNumber =dr.IsDBNull(18) ? null : dr.GetString(18), + BillingDate =dr.IsDBNull(19) ? null : dr.GetDateTime(19) }); } diff --git a/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Integretion/IntegrationAuthController.cs b/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Integretion/IntegrationAuthController.cs index 2719f21..ae8d3cd 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Integretion/IntegrationAuthController.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Integretion/IntegrationAuthController.cs @@ -1,6 +1,7 @@ using System; using System.DirectoryServices; using System.IdentityModel.Tokens.Jwt; +using System.IO; using System.Runtime.Versioning; using System.Security.Claims; using System.Text; @@ -309,6 +310,25 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers return Ok(); } + + [AllowAnonymous] + [HttpGet("download")] + public IActionResult Download() + { + string fileName = "AutoCropCare.apk"; + var filePath = Path.Combine(@"D:\Local", fileName); + + if (!System.IO.File.Exists(filePath)) + return NotFound(); + + return PhysicalFile( + filePath, + "application/octet-stream", + fileName, + enableRangeProcessing: true + ); + } + /// /// /// diff --git a/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Web/UserController.cs b/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Web/UserController.cs index 19b093e..26f5f05 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Web/UserController.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Web/UserController.cs @@ -611,6 +611,7 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers.Web /// [ValidateSession] [HttpPost("uploadPasswords")] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(BulkPasswordUploadResponse))] public async Task UploadPasswords([FromForm] BulkPasswordUploadRequest request) { diff --git a/Api/OnlineSalesAutoCrop.CoreAPI/Converter/NullableDecimalConverter.cs b/Api/OnlineSalesAutoCrop.CoreAPI/Converter/NullableDecimalConverter.cs index b7bc7ee..fdc3f6f 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI/Converter/NullableDecimalConverter.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI/Converter/NullableDecimalConverter.cs @@ -17,7 +17,7 @@ public class NullableDecimalConverter : JsonConverter if (reader.TokenType == JsonTokenType.Number) { - var value = reader.GetInt32(); + var value = reader.GetDecimal(); return value == 0 ? null : value; } diff --git a/Api/OnlineSalesAutoCrop.CoreAPI/Startup.cs b/Api/OnlineSalesAutoCrop.CoreAPI/Startup.cs index 0ceed54..b6bf889 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI/Startup.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI/Startup.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -462,11 +463,11 @@ namespace OnlineSalesAutoCrop.CoreAPI app.UseStaticFiles(); - #endregion + #endregion - #region Routing + #region Routing - app.UseRouting(); + app.UseRouting(); #endregion diff --git a/App/ClientApp/src/app/app.routes.module.ts b/App/ClientApp/src/app/app.routes.module.ts index c64619b..4628249 100644 --- a/App/ClientApp/src/app/app.routes.module.ts +++ b/App/ClientApp/src/app/app.routes.module.ts @@ -19,6 +19,7 @@ const routes: Routes = [ { path: 'newuser', loadComponent: () => import('./users/newuser.component').then(m => m.NewUserComponent), canActivate: [AuthGuard], data: { permissionKey: 'ELIT.1.2.2_1' } }, { path: 'edituser', loadComponent: () => import('./users/edituser.component').then(m => m.EditUserComponent), canActivate: [AuthGuard], data: { permissionKey: 'ELIT.1.2.2_2' } }, { path: 'resetPwd', loadComponent: () => import('./users/resetpwd.component').then(m => m.ResetPwdComponent), canActivate: [AuthGuard], data: { permissionKey: 'ELIT.1.2.3,ELIT.1.2.3_1,ELIT.1.2.3_2' } }, + { path: 'uploadPwd', loadComponent: () => import('./users/uploadpwd.component').then(m => m.UploadPwdComponent), canActivate: [AuthGuard], data: { permissionKey: 'ELIT.1.2.3,ELIT.1.2.3_2' } }, { path: 'forcelogout', loadComponent: () => import('./forcelogout/forcelogout.component').then(m => m.ForceLogoutComponent), canActivate: [AuthGuard], data: { permissionKey: 'ELIT.1.2.4,ELIT.1.2.4_1,ELIT.1.2.4_2,ELIT.1.2.4_3' } }, //Landing Page diff --git a/App/ClientApp/src/app/components/models/menu.definition.ts b/App/ClientApp/src/app/components/models/menu.definition.ts index ef1b94e..8246774 100644 --- a/App/ClientApp/src/app/components/models/menu.definition.ts +++ b/App/ClientApp/src/app/components/models/menu.definition.ts @@ -50,6 +50,7 @@ export const MENU_DEFINITION: MenuNode[] = [ { moduleId: 'ELIT.1.2.1', label: 'Groups', icon: 'fa fa-address-book-o', routerLink: 'groups' }, { moduleId: 'ELIT.1.2.2', label: 'Users', icon: 'fa fa-address-card-o', routerLink: 'users' }, + { moduleId: 'ELIT.1.2.3', label: 'Upload Passwords', icon: 'fa fa-upload', routerLink: 'uploadPwd' }, { moduleId: 'ELIT.1.2.4', label: 'Force Logout', icon: 'fa fa-address-card-o', routerLink: 'forcelogout' } ] }, diff --git a/App/ClientApp/src/app/users/uploadpwd.component.html b/App/ClientApp/src/app/users/uploadpwd.component.html new file mode 100644 index 0000000..f3a1326 --- /dev/null +++ b/App/ClientApp/src/app/users/uploadpwd.component.html @@ -0,0 +1,58 @@ +
+
+ Excel file must have EmployeeCode and Password columns in the first row. +
+ +
+ Excel File: +
+
+
+ + +
+ @if (submitted && f.fileName.errors) + { +
+ Please select an excel file to upload. +
+ } +
+
+ +
+
+ + +
+
+ + @if (canSave) + { +
+
+ +
+ } + + @if (totalRows > 0) + { +
+ Total Row(s): {{totalRows}} | Updated: {{successCount}} | Failed: {{failedCount}} +
+ + + + + + +
+ } +
diff --git a/App/ClientApp/src/app/users/uploadpwd.component.ts b/App/ClientApp/src/app/users/uploadpwd.component.ts new file mode 100644 index 0000000..ea65367 --- /dev/null +++ b/App/ClientApp/src/app/users/uploadpwd.component.ts @@ -0,0 +1,143 @@ +import { Router } from '@angular/router'; +import { NgClass } from '@angular/common'; +import { KENDO_GRID } from '@progress/kendo-angular-grid'; +import { Component, ElementRef, OnInit, ViewChild, inject } from '@angular/core'; +import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; + +import { AlertService } from '../components/alert'; +import { BreadcrumbService } from '../breadcrumb.service'; +import { UserService } from '../../providers/user/user.service.'; + +@Component({ + standalone: true, + selector: 'user-upload-pwd', + templateUrl: './uploadpwd.component.html', + styles: ['#grdUploadPwd tr.failed{color:#f00;}'], + imports: [FormsModule, ReactiveFormsModule, NgClass, KENDO_GRID] +}) +export class UploadPwdComponent implements OnInit +{ + private readonly router = inject(Router); + private readonly service = inject(UserService); + private readonly formBuilder = inject(FormBuilder); + private readonly alertService = inject(AlertService); + + @ViewChild('uploadFileLabel') private uploadFileLabel: ElementRef; + + public uploadPwdForm: FormGroup; + public loading: boolean = false; + public canSave: boolean = false; + public submitted: boolean = false; + public items: any[] = []; + public totalRows: number = 0; + public successCount: number = 0; + public failedCount: number = 0; + public readonly acceptFileType: string = '.xls,.xlsx'; + + private formData: FormData; + + constructor() + { + const bcService = inject(BreadcrumbService); + + this.alertService.clear(); + bcService.setItems([{ label: 'System Setup' }, { label: 'User Management' }, { label: 'Upload Passwords', routerLink: ['/uploadPwd'] }]); + if (!this.service.currentUserValue) + { + this.router.navigate(['/login']); + return; + } + + this.canSave = this.service.currentUserValue?.moduleIds?.includes('ELIT.1.2.3_2'); + } + + ngOnInit() + { + this.uploadPwdForm = this.formBuilder.group( + { + uploadFile: '', + fileName: ['', Validators.required], + forcePasswordChange: false + }); + } + + get f() { return this.uploadPwdForm.controls; } + + public rowCallback = (context: any) => { return { failed: !context.dataItem.updated }; }; + + public onFileSelected(event: any): void + { + this.clearData(); + + const selectedFile = event.target.files[0]; + if (!selectedFile) + { + this.uploadPwdForm.patchValue({ fileName: '' }); + return; + } + + const fileName = selectedFile.name; + const extension = fileName.substring(fileName.lastIndexOf('.')).toLowerCase(); + if (extension !== '.xls' && extension !== '.xlsx') + { + this.uploadPwdForm.patchValue({ fileName: '' }); + this.alertService.error('Only excel (.xls/.xlsx) file is allowed to process.'); + return; + } + + this.uploadFileLabel.nativeElement.innerText = fileName; + this.uploadPwdForm.patchValue({ fileName: fileName }); + + this.formData = new FormData(); + this.formData.append('fileName', fileName); + this.formData.append('fileData', selectedFile); + } + + public clearData(): void + { + this.items = []; + this.totalRows = this.successCount = this.failedCount = 0; + } + + public onSubmit(): void + { + this.submitted = true; + this.alertService.clear(); + if (this.uploadPwdForm.invalid || !this.formData) + return; + + this.clearData(); + this.loading = true; + this.formData.set('forcePasswordChange', `${this.f.forcePasswordChange.value === true}`); + this.service.uploadPasswords(this.formData).subscribe( + { + next: (resp: any) => + { + this.loading = false; + this.items = resp.items ?? []; + this.totalRows = resp.totalRows ?? 0; + this.failedCount = resp.failedCount ?? 0; + this.successCount = resp.successCount ?? 0; + + if (resp.returnStatus === 200) + { + if (this.failedCount > 0) + this.alertService.info(resp.returnMessage); + else + this.alertService.success(resp.returnMessage); + + this.submitted = false; + } + else + { + this.alertService.error(resp.returnMessage); + } + }, + error: (errorMessage: string) => + { + this.loading = false; + this.alertService.error(errorMessage); + } + }); + } +} diff --git a/App/ClientApp/src/providers/user/user.service..ts b/App/ClientApp/src/providers/user/user.service..ts index 9dac0b7..1823214 100644 --- a/App/ClientApp/src/providers/user/user.service..ts +++ b/App/ClientApp/src/providers/user/user.service..ts @@ -114,6 +114,11 @@ export class UserService extends Base return this.api.post('v1/users/resetPassword', params); } + public uploadPasswords(formdata: FormData) + { + return this.api.uplaodFormData('v1/users/uploadPasswords', formdata); + } + public findAccount(accountId: string) { const params: any = { accountId: accountId }; diff --git a/Deployment/App/prerendered-routes.json b/Deployment/App/prerendered-routes.json new file mode 100644 index 0000000..3226541 --- /dev/null +++ b/Deployment/App/prerendered-routes.json @@ -0,0 +1,3 @@ +{ + "routes": {} +} \ No newline at end of file