Compare commits

...

71 Commits

Author SHA1 Message Date
0ef4818b42 change upload password forntend 2026-08-13 17:42:16 +06:00
458b7b6976 update UAT feedback 2026-08-12 18:29:10 +06:00
4d9a467558 inactive previous foc and discount when again comes 2026-08-11 21:39:19 +06:00
0529139358 feedback of UAT 2026-08-11 18:24:11 +06:00
68cf33d5a6 update user password via excel 2026-08-10 18:13:54 +06:00
b93734f888 make customerprice group and material code as required 2026-08-10 17:39:18 +06:00
45f95b3184 change material price endpoint request 2026-08-10 17:35:41 +06:00
cf1a8e74a6 setup login for web 2026-08-09 18:20:12 +06:00
83f92ea6c8 upate reqyest 2026-08-06 16:57:29 +06:00
17bd6a7727 update frontend 2026-08-06 11:10:58 +06:00
311a9d5faa update angular frontend and minus the used approved order balance in customer ledger 2026-08-06 11:10:25 +06:00
8290cc1c46 update customer endpoints 2026-08-03 18:24:18 +06:00
11c2e86888 uncommented stock blocks 2026-08-03 13:59:53 +06:00
177ee7d57f issue in discount api 2026-08-02 17:24:50 +06:00
472cdf9409 update foc endpoints 2026-08-02 15:58:59 +06:00
874611cd18 make status nullabale in integration customers endpoint 2026-08-02 09:28:35 +06:00
7763c5278d change discount material response 2026-07-30 11:35:10 +06:00
4f3a990c60 make some properties nullable in customers 2026-07-29 18:13:20 +06:00
93be7ab99c Complete FOC for Integration 2026-07-29 13:54:40 +06:00
769e973f48 chaange format of Material Price Endpoints 2026-07-29 13:09:07 +06:00
6f82ff2ae9 make some field nullable for employee in integration 2026-07-28 16:50:21 +06:00
2b938d35b2 update in foc interation 2026-07-28 15:03:11 +06:00
8b5af20539 inactive all previous slab based on condition 2026-07-28 14:57:24 +06:00
240cb8ba2a change in material price endpoints for base price and discounts 2026-07-28 14:21:55 +06:00
5b35e0b044 show specific message in login 2026-07-27 18:16:10 +06:00
399590a904 add vat value in save order endpoint 2026-07-27 18:12:06 +06:00
e9500278cd make optional some properties in MaterialFOCs for Integration 2026-07-27 16:09:14 +06:00
edf51b4271 make plant optional of Material Endpoint in Integration 2026-07-27 11:47:13 +06:00
c75c96d0fd handle nullable in foc material endpoint 2026-07-27 11:03:25 +06:00
c4ad3e53c7 add stock checking in save order endpoint 2026-07-26 18:18:02 +06:00
3aea7dee2c change discount material, password policy, material price endpoint response 2026-07-26 18:04:49 +06:00
13b1739ab1 complete Integration FOC and Material Price Endpoints 2026-07-26 15:43:14 +06:00
16dcf2351f update format 2026-07-23 18:25:50 +06:00
1a264523a0 implement password policy endpoints 2026-07-23 15:13:45 +06:00
6cd0f8f82f update validaton for material endpoint 2026-07-23 11:11:46 +06:00
4990ac7a4a complete billing and delivey endpoint for integration 2026-07-21 17:34:40 +06:00
827795661a make some properties nullable for customer endpoint for integration 2026-07-21 13:14:41 +06:00
8559e35086 complete order sending process using Hangfire 2026-07-21 13:10:16 +06:00
2143a3761c complete sending data into SAP 2026-07-21 11:45:26 +06:00
4f89ed32a7 add hangfire for sending order to sap 2026-07-20 18:30:13 +06:00
23f07386ba update order endpoint 2026-07-19 18:31:43 +06:00
f02504d76d check customer ledger is null or not before binding 2026-07-19 17:35:45 +06:00
777aff085e implement material stock endpoints 2026-07-19 16:05:07 +06:00
3ae6fa0882 implement intgreation customer ledger api when get customer details by a specific code 2026-07-19 13:58:28 +06:00
56bbcdfa04 check null in save order for discount and foc item 2026-07-19 10:04:03 +06:00
794086917b change response type in order item 2026-07-19 09:54:04 +06:00
dibakorroyme
38e557fbfd completed order summary endpoint 2026-07-18 18:55:29 +06:00
97a07bd216 complete save order endpoint 2026-07-16 18:30:18 +06:00
7acf518892 start working for save order endpoints 2026-07-15 18:51:09 +06:00
1aed28659e completed master data endpoints 2026-07-14 18:50:43 +06:00
376af6828e applied logic in employee integretion for app role type 2026-07-14 12:46:54 +06:00
b094c7ca75 implement payment terms endpoint 2026-07-14 12:18:46 +06:00
8bf59da423 complete foc and foc material endpoints 2026-07-13 18:32:45 +06:00
a7467fbb39 Completed Discount and Discount Materials route 2026-07-13 17:37:58 +06:00
66e52442df add discount endpoints without service implementation 2026-07-12 18:27:00 +06:00
0250908261 update customer endpoint response (add credit pros) and add access token validaity time in app settings 2026-07-12 12:44:45 +06:00
5778ee9203 update in attendance endpoint 2026-07-09 18:53:22 +06:00
cbd94a76ac set current date when create or update 2026-07-08 18:37:40 +06:00
a64aa85d33 add attendance endpoint 2026-07-08 18:15:24 +06:00
bdcda8c1fb update customer,brand, material endpoint request 2026-07-08 14:51:20 +06:00
8c6df3cd19 send only one validation message 2026-07-07 18:39:04 +06:00
18891cd584 implement brand, material and customer and change the response format when got bad request 2026-07-07 18:14:18 +06:00
ad9694f85d update mobile response base format 2026-07-07 12:15:19 +06:00
89ef87fb18 update length of login id 2026-07-06 18:43:46 +06:00
5d6228aba2 change password length in change password method 2026-07-06 18:20:41 +06:00
1b738897d2 complete auth for mobile api 2026-07-06 17:23:05 +06:00
43efe23587 add salesinvoice api 2026-06-30 18:57:11 +06:00
70a4a757f2 add payment terms api 2026-06-28 19:02:40 +06:00
a8f7cd51ce update Material price and FOC 2026-06-25 19:08:21 +06:00
e55821737f Add FOC Api 2026-06-25 12:41:18 +06:00
7ae8591ce6 Add Integration Material Price Api 2026-06-23 17:34:45 +06:00
87 changed files with 9077 additions and 1134 deletions

2
.gitignore vendored
View File

@ -20,3 +20,5 @@ obj
/OnlineSalesAutoCrop/App/ClientApp/node_modules
/App/ClientApp/node_modules
/Api/OnlineSalesAutoCrop.CoreAPI/.config/dotnet-tools.json
/App/ClientApp/.angular/cache/21.2.13/OnlineSalesAutoCropApp
/Deployment/App/browser

View File

@ -700,4 +700,89 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models
[Description("From Others")]
FromOthers = 3,
}
public enum IntegrationMaterialCalCulationType : short
{
[Description("Percentage")]
A = 1,
[Description("Fixed value ")]
B = 2,
}
public enum IntegrationMaterialPriceType: short
{
[Description("Base Price")]
PR00 = 1 ,
[Description("Amount Based Discount")]
ZDS1 = 2 ,
[Description("Percentage Based Discount")]
ZDS2 = 3
}
public enum UserTypeEnum : short
{
SuperAdmin = 1,
Admin = 2,
SalesAppOfficer = 3
}
public enum MaterialPriceTypeEnum : short
{
BasePrice = 1,
Discount = 2,
VatPrice = 3,
SlabBasedDiscount = 4,
}
public enum UserPlatFormType : short
{
SAP=1,
AppUser =2,
}
public enum UserRoleTypeEnum : short
{
[Description("Order Collector")]
OrderCollector = 1,
[Description("OrderValidator")]
OrderValidator = 2,
[Description("OrderApprover")]
OrderApprover = 3 ,
[Description("ApproverWithoutValidate")]
OrderApproverWithoutValidated = 4,
[Description("SAP User")]
SapUser = 5 ,
}
public enum OrderStatusEnum : short
{
[Description("Draft")]
Draft = 1,
[Description("Order Sent to Validator")]
InOrderValidation = 2,
[Description("Cancelled by Validator")]
CancelledByValidator = 3,
[Description("Order Sent to Approver")]
InOrderApproval = 4,
[Description("Cancelled by Approver")]
CancelledByApprover = 5,
[Description("Approved")]
Approved = 6,
[Description("Delivered in SAP")]
Delivered = 7,
}
public enum SAPOrderStatusEnum : short
{
Pending = 0,
Sent = 1,
Error = 2
}
public enum DiscountTypeEnum : short
{
ItemType = 1,
InvoiceValueType =2
}
}

View File

@ -54,7 +54,9 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models.Global
public bool JwtValidateIssuer { get; set; }
public bool JwtValidateAudience { get; set; }
public int RefreshTokenDuration { get; set; }
public int AccessTokenDuration { get; set; }
public int IsLogRequest { get; set; } = 0;
/// <summary>
/// Folder management
/// </summary>
@ -126,22 +128,23 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models.Global
public string LoggerPath { get; set; }
public int LoggerMinLevel { get; set; }
/// <summary>
/// Database connection Hangfire
/// </summary>
private string _hangfireDb;
public string HangfireDb
{
get
{
if (string.IsNullOrEmpty(_hangfireDb) || string.IsNullOrEmpty(PwdSecretKey))
return string.Empty;
/// <summary>
/// Database connection Hangfire
/// </summary>
public string HangfireDb { get; set; }
//private string _hangfireDb;
//public string HangfireDb
//{
// get
// {
// if (string.IsNullOrEmpty(_hangfireDb) || string.IsNullOrEmpty(PwdSecretKey))
// return string.Empty;
string secretKey = GlobalFunctions.ConvertFromBase64String(PwdSecretKey);
return Ease.NetCore.Utility.Global.CipherFunctions.DecryptByAES(privateKey: secretKey, publicKey: secretKey, data: _hangfireDb, input: 2);
}
set { _hangfireDb = value; }
}
// string secretKey = GlobalFunctions.ConvertFromBase64String(PwdSecretKey);
// return Ease.NetCore.Utility.Global.CipherFunctions.DecryptByAES(privateKey: secretKey, publicKey: secretKey, data: _hangfireDb, input: 2);
// }
// set { _hangfireDb = value; }
//}
@ -265,11 +268,13 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models.Global
/// </summary>
[Required]
public ApiSettings API { get; set; }
public AutoCropSAPApi AutoCropSAPApi { get; set; }
/// <summary>
/// Swagger Setting
/// </summary>
[Required]
/// <summary>
/// Swagger Setting
/// </summary>
[Required]
public Swagger Swagger { get; set; }
/// <summary>
@ -345,10 +350,20 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models.Global
public string Url { get; set; }
}
/// <summary>
///
/// </summary>
public class ApiLicense
/// <summary>
/// AutoCrop Integration API Details
/// </summary>
public class AutoCropSAPApi
{
public string BaseUrl { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
/// <summary>
///
/// </summary>
public class ApiLicense
{
public string Name { get; set; }
public string Url { get; set; }

View File

@ -2,7 +2,7 @@
namespace OnlineSalesAutoCrop.CoreAPI.Models.Objects.Integrations;
public class CustomerIntegration
public class IntegrationCustomer
{
public string CustomerNumber { get; set; }
public string CustomerName { get; set; }

View File

@ -1,6 +1,6 @@
namespace OnlineSalesAutoCrop.CoreAPI.Models.Objects.Integrations;
public class EmployeeIntegration
public class IntegrationEmployee
{
public string EmployeeVendorCode { get; set; }
public string EmployeeVendorName { get; set; }

View File

@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Objects.Integrations;
public class MaterialIntegration
public class IntegrationMaterial
{
public int MeterialId { get; set; }
public string MaterialType { get; set; }

View File

@ -0,0 +1,23 @@
using System;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Objects.Integrations;
public class IntegrationMaterialFreeGood
{
public int FreeGoodsId { get; set; } // required for Update
public string SalesOrg { get; set; }
public string DistributionChannel { get; set; }
public int? CustomerId { get; set; } // nullable
public string CustomerPriceGroup { get; set; }
public int MaterialId{ get; set; }
public decimal MinOrderQuantity { get; set; }
public decimal ForQuantity { get; set; }
public string UnitOfMeasure { get; set; }
public decimal FreeQuantity { get; set; }
public string FocUnitOfMeasure { get; set; }
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; }
public string? Status { get; set; } // NULL = Active, X = Inactive
public DateTime CreatedDate { get; set; }
public DateTime ModifiedDate { get; set; }
}

View File

@ -0,0 +1,24 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Objects.Integrations;
public class IntegrationMaterialPrice
{
public int MaterialPriceId { get; set; }
public string SalesOrg { get; set; }
public string DistributionChannel { get; set; }
public string CustomerNumber { get; set; }
public string CustomerGroupCode { get; set; }
public string MaterialCode { get; set; }
public string ConditionType { get; set; }
public string CalculationType { get; set; }
public string PricingUnit { get; set; }
public string UnitOfMeasure { get; set; }
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; }
public string Status { get; set; }
public decimal ConditionValue { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime ModifiedDate { get; set; }
}

View File

@ -0,0 +1,15 @@
using System;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Objects.Integrations;
internal class IntegrationPaymentTerm
{
public int CreditCodeId { get; set; }
public string CreditCode { get; set; }
public string Description { get; set; }
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; }
public string SalesOrg { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime? ModifiedDate { get; set; }
}

View File

@ -1,4 +1,6 @@
using System;
using DocumentFormat.OpenXml.Office2010.ExcelAc;
using System;
using System.Collections.Generic;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Objects.Systems
{
@ -26,9 +28,18 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models.Objects.Systems
public string SchemeName { get; set; }
public string MenuLayout { get; set; }
public bool IsLocked { get; set; }
public EnumLoginStatus LoginStatus { get; set; }
public DateTime? NextLoginTime { get; set; }
public UserPlatFormType? UserPlatFormType { get; set; }
public UserRoleTypeEnum? UserRoleType { get; set; }
public string? EmployeeNumber { get; set; }
public string? DesignationCode { get; set; }
public string? DesignationName { get; set; }
public EnumLoginStatus LoginStatus { get; set; }
public DateTime? NextLoginTime { get; set; }
public int NotificationCount { get; set; }
public List<string> ModuleIds { get; set; } = new List<string>();
public List<int> TeamSpaceIds { get; set; } = new List<int>();
}
public class LoginHistory

View File

@ -27,4 +27,13 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests
{
public string AuthenticationId { get; set; }
}
public abstract class PagedRequest
{
[Range(1, int.MaxValue, ErrorMessage = "PageNumber must be at least 1.")]
public int PageNumber { get; set; } = 1;
[Range(1, 1000, ErrorMessage = "PageSize must be between 1 and 1000.")]
public int PageSize { get; set; } = 1000;
}
}

View File

@ -0,0 +1,14 @@

using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.Common;
public class AuthLoginRequest
{
[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.")]
public string Password { get; set; }
}

View File

@ -1,41 +1,41 @@
using OnlineSalesAutoCrop.CoreAPI.Models.Objects.Systems;
using System;
using System.Collections.Generic;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Common;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Text;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
public class IntegrationLoginRequest
public class IntegrationLoginRequest : AuthLoginRequest
{
[Required, NotNull, StringLength(maximumLength: 150, MinimumLength = 3, ErrorMessage = "Login Id must be between 4 and 30 characters.")]
public string LoginId { get; set; }
[Required, NotNull, StringLength(maximumLength: 150, MinimumLength = 5, ErrorMessage = "Password must be between 1 and 30 characters.")]
public string Password { get; set; }
}
public class IntegrationRefreshTokenRequest
{
public string RefreshToken { get; set; }
[Required]
public string RefreshToken { get; set; }
}
public class InsertRefreshTokenRequest : RefreshToken
{
[Required]
public string RefreshToken { get; set; }
}
public class RevokedRefreshTokenRequest
public class RevokedRefreshTokenRequest
{
[Required]
public string RefreshToken { get; set; }
[Required]
public int UserId { get; set; }
}
public class GenerateRefreshTokenRequest
{
[Required]
public int UserId { get; set; }
[Required]
public string IpAddress { get; set; }
[Required]
public string RawRefreshToken { get; set; }
}

View File

@ -5,7 +5,7 @@ using System.Text;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
public class CustomerIntegrationRequest
public class IntegrationCustomerRequest
{
[Required(ErrorMessage = "Customer number is required.")]
[StringLength(10, ErrorMessage = "Customer number cannot exceed 10 characters.")]
@ -16,7 +16,7 @@ public class CustomerIntegrationRequest
public string CustomerName { get; set; }
[Required(ErrorMessage = "Account group is required.")]
[StringLength(4, ErrorMessage = "Account group cannot exceed 3 characters.")]
[StringLength(4, ErrorMessage = "Account group cannot exceed 4 characters.")]
public string AccountGroup { get; set; }
[Required(ErrorMessage = "Account group description is required.")]
@ -60,57 +60,45 @@ public class CustomerIntegrationRequest
[Phone(ErrorMessage = "Mobile number is not a valid phone number.")]
public string MobileNumber { 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 EmailAddress { get; set; }
public string? EmailAddress { get; set; }
[Required(ErrorMessage = "Business/Tax number is required.")]
[StringLength(16, ErrorMessage = "Business/Tax number cannot exceed 16 characters.")]
public string BusinessTaxNumber { get; set; }
public string? BusinessTaxNumber { get; set; }
[Required(ErrorMessage = "Credit limit is required.")]
[Range(0, 9999999999999.99, ErrorMessage = "Credit limit must be between 0 and 9,999,999,999,999.99.")]
public decimal CreditLimit { get; set; }
[Required(ErrorMessage = "Sales group is required.")]
[StringLength(3, ErrorMessage = "Sales group cannot exceed 3 characters.")]
public string SalesGroup { get; set; }
[Required(ErrorMessage = "Sales group description is required.")]
[StringLength(20, ErrorMessage = "Sales group description cannot exceed 20 characters.")]
public string SalesGroupDescription { get; set; }
[Required(ErrorMessage = "Customer group is required.")]
[StringLength(2, ErrorMessage = "Customer group cannot exceed 2 characters.")]
public string CustomerGroup { get; set; }
public string? CustomerGroup { get; set; }
[Required(ErrorMessage = "Customer group description is required.")]
[StringLength(20, ErrorMessage = "Customer group description cannot exceed 20 characters.")]
public string CustomerGroupDescription { get; set; }
public string? CustomerGroupDescription { get; set; }
[Required(ErrorMessage = "Customer price group is required.")]
[StringLength(2, ErrorMessage = "Customer price group cannot exceed 2 characters.")]
public string CustomerPriceGroup { get; set; }
public string? CustomerPriceGroup { get; set; }
[Required(ErrorMessage = "Customer price group description is required.")]
[StringLength(20, ErrorMessage = "Customer price group description cannot exceed 20 characters.")]
public string CustomerPriceGroupDescription { get; set; }
public string? CustomerPriceGroupDescription { get; set; }
[Required(ErrorMessage = "Status code is required.")]
[StringLength(1, ErrorMessage = "Status code cannot exceed 1 character.")]
public string StatusCode { get; set; }
public string? StatusCode { get; set; }
[Required(ErrorMessage = "Status description is required.")]
[StringLength(30, ErrorMessage = "Status description cannot exceed 30 characters.")]
public string StatusDescription { get; set; }
public string? StatusDescription { get; set; }
[Required(ErrorMessage = "Payment terms is required.")]
[StringLength(4, ErrorMessage = "Payment terms cannot exceed 4 characters.")]
public string PaymentTerms { get; set; }
public string? PaymentTerms { get; set; }
[Required(ErrorMessage = "Payment terms description is required.")]
public string PaymentTermsDescription { get; set; }
public string? PaymentTermsDescription { get; set; }
[Required(ErrorMessage = "Search term is required.")]
[StringLength(20, ErrorMessage = "Search term cannot exceed 20 characters.")]
@ -150,7 +138,6 @@ public class CustomerIntegrationRequest
}
public class CustomerByCompanyCodeRequest
{
[Required(ErrorMessage = "Customer Number is required")]
@ -161,4 +148,5 @@ public class CustomerByCompanyCodeRequest
[Required(ErrorMessage = "Company Code is required")]
[StringLength(4, MinimumLength = 1, ErrorMessage = "Company Code must be between 1 and 4 characters")]
public string CompanyCode { get; set; } = string.Empty;
}
}

View File

@ -2,7 +2,7 @@
namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
public class EmployeeIntegrationRequest
public class IntegrationEmployeeRequest
{
[Required(ErrorMessage = "Employee vendor code is required.")]
[StringLength(10, ErrorMessage = "Employee vendor code cannot exceed 10 characters.")]
@ -25,7 +25,6 @@ public class EmployeeIntegrationRequest
[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; }
@ -46,21 +45,17 @@ public class EmployeeIntegrationRequest
[StringLength(40, ErrorMessage = "Sales org description cannot exceed 40 characters.")]
public string SalesOrgDescription { get; set; }
[Required(ErrorMessage = "Distribution channel is required.")]
[StringLength(2, ErrorMessage = "Distribution channel cannot exceed 2 characters.")]
public string DistChannel { get; set; }
public string? DistChannel { get; set; }
[Required(ErrorMessage = "Distribution channel description is required.")]
[StringLength(20, ErrorMessage = "Distribution channel description cannot exceed 20 characters.")]
public string DistChannelDescription { get; set; }
public string? DistChannelDescription { get; set; }
[Required(ErrorMessage = "Division is required.")]
[StringLength(2, ErrorMessage = "Division cannot exceed 2 characters.")]
public string Division { get; set; }
public string? Division { get; set; }
[Required(ErrorMessage = "Division description is required.")]
[StringLength(20, ErrorMessage = "Division description cannot exceed 20 characters.")]
public string DivisionDescription { get; set; }
public string? DivisionDescription { get; set; }
[Required(ErrorMessage = "Region/Area code is required.")]
[StringLength(3, ErrorMessage = "Region/Area code cannot exceed 3 characters.")]
@ -70,37 +65,29 @@ public class EmployeeIntegrationRequest
[StringLength(40, ErrorMessage = "Region/Area description cannot exceed 40 characters.")]
public string RegionAreaDescription { get; set; }
[Required(ErrorMessage = "Area-AV/Sales unit code is required.")]
[StringLength(10, ErrorMessage = "Area-AV/Sales unit code cannot exceed 10 characters.")]
public string AreaAVSalesUnitCode { get; set; }
[Required(ErrorMessage = "Area-AV/Sales unit description is required.")]
[StringLength(40, ErrorMessage = "Area-AV/Sales unit description cannot exceed 40 characters.")]
public string AreaAVSalesUnitDescription { get; set; }
[Required(ErrorMessage = "Territory is required.")]
[StringLength(3, ErrorMessage = "Territory cannot exceed 3 characters.")]
public string Territory { get; set; }
[Required(ErrorMessage = "Territory description is required.")]
[StringLength(20, ErrorMessage = "Territory description cannot exceed 20 characters.")]
public string TerritoryDescription { get; set; }
[Required(ErrorMessage = "Plant is required.")]
[StringLength(4, ErrorMessage = "Plant cannot exceed 4 characters.")]
public string Plant { get; set; }
public string? Plant { get; set; }
[Required(ErrorMessage = "Plant description is required.")]
[StringLength(30, ErrorMessage = "Plant description cannot exceed 30 characters.")]
public string PlantDescription { get; set; }
public string? PlantDescription { get; set; }
[Required(ErrorMessage = "Status is required.")]
[StringLength(1, ErrorMessage = "Status cannot exceed 1 character.")]
public string Status { get; set; }
public string? Status { get; set; }
[Required(ErrorMessage = "Status description is required.")]
[StringLength(30, ErrorMessage = "Status description cannot exceed 30 characters.")]
public string StatusDescription { get; set; }
public string? StatusDescription { get; set; }
}
public class EmployeeIntegrationByComapanyRequest

View File

@ -0,0 +1,87 @@

using DocumentFormat.OpenXml.Office2010.ExcelAc;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
public class IntegrationBillingDocumentRequest
{
[Required(ErrorMessage = "Invoice/Billing document number is required.")]
[StringLength(50, ErrorMessage = "Invoice/Billing document number cannot exceed 50 characters.")]
public string BillingDocumentNumber { get; set; } = string.Empty;
[Required(ErrorMessage = "Billing date is required.")]
[DataType(DataType.Date)]
public DateTime BillingDate { get; set; }
[Required(ErrorMessage = "Sales order reference number is required.")]
[StringLength(50, ErrorMessage = "Sales order reference number cannot exceed 50 characters.")]
public string SalesOrderReferenceNumber { get; set; } = string.Empty;
[Required(ErrorMessage = "Delivery reference number is required.")]
[StringLength(50, ErrorMessage = "Delivery reference number cannot exceed 50 characters.")]
public string DeliveryReferenceNumber { get; set; } = string.Empty;
public IList<IntegrationBillingDocumentItemRequest> Items { get; set; } = new List<IntegrationBillingDocumentItemRequest>();
}
public class IntegrationBillingDocumentItemRequest
{
[Required(ErrorMessage = "Item code is required.")]
[StringLength(100, ErrorMessage = "Item code cannot exceed 100 characters.")]
public string ItemCode { get; set; } = string.Empty;
[Required(ErrorMessage = "Quantity is required.")]
[Range(0.000001, double.MaxValue, ErrorMessage = "Quantity must be greater than zero.")]
public decimal Quantity { get; set; }
[Required(ErrorMessage = "Unit of Measure is required.")]
[StringLength(20, ErrorMessage = "Unit of Measure cannot exceed 20 characters.")]
public string UnitOfMeasure { get; set; } = string.Empty;
[Required(ErrorMessage = "Billing amount is required.")]
[Range(0, double.MaxValue, ErrorMessage = "Billing amount must be greater than zero.")]
[DataType(DataType.Currency)]
public decimal BillingAmount { get; set; }
}
public class IntegrationDeliveryDocumentRequest
{
[Required(ErrorMessage = "Delivery Document number is required.")]
[StringLength(50, ErrorMessage = "Delivery Document number cannot exceed 50 characters.")]
public string DeliveryDocumentNo{ get; set; } = string.Empty;
[Required(ErrorMessage = "Delivery date is required.")]
[DataType(DataType.Date)]
public DateTime DeliveryDate { get; set; }
[Required(ErrorMessage = "Sales order reference number is required.")]
[StringLength(50, ErrorMessage = "Sales order reference number cannot exceed 50 characters.")]
public string SalesOrderReferenceNumber { get; set; } = string.Empty;
[Required(ErrorMessage = "Delivery Status is required.")]
[StringLength(5, ErrorMessage = "Delivery Status cannot exceed 5 characters.")]
public string DeliveryStatus { get; set; } = string.Empty;
public IList<IntegrationDeliveryDocumentItemRequest> Items { get; set; } = new List<IntegrationDeliveryDocumentItemRequest>();
}
public class IntegrationDeliveryDocumentItemRequest
{
[Required(ErrorMessage = "Item code is required.")]
[StringLength(100, ErrorMessage = "Item code cannot exceed 100 characters.")]
public string ItemCode { get; set; } = string.Empty;
[Required(ErrorMessage = "Quantity is required.")]
[Range(0.000001, double.MaxValue, ErrorMessage = "Quantity must be greater than zero.")]
public decimal Quantity { get; set; }
[Required(ErrorMessage = "Unit of Measure is required.")]
[StringLength(20, ErrorMessage = "Unit of Measure cannot exceed 20 characters.")]
public string UnitOfMeasure { get; set; } = string.Empty;
}

View File

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
public class IntegrationMaterialFreeGoodsRequest
{
public IntegrationMaterialFreeGoodsRequest()
{
Slabs = new List<IntegrationMaterialFreeGoodItemRequest>();
}
[Required(ErrorMessage = "Condition Record No is Required")]
public int ConditionRecNo { get; set; }
[StringLength(10, MinimumLength = 1, ErrorMessage = "SalesOrg must be between 1 and 10 characters.")]
public string SalesOrg { get; set; }
[StringLength(5, MinimumLength = 1, ErrorMessage = "DistributionChannel must be between 1 and 5 characters.")]
public string DistributionChannel { get; set; }
[StringLength(13, MinimumLength = 1, ErrorMessage = "CustomerNumber must be between 1 and 13 characters.")]
public string? CustomerNumber { get; set; } // nullable
[Required(ErrorMessage = "CustomerPriceGroup Is Required")]
[StringLength(2, MinimumLength = 1, ErrorMessage = "CustomerPriceGroup must be between 1 and 2 characters.")]
public string? CustomerPriceGroup { get; set; }
[Required(ErrorMessage = "MaterialCode Is Required")]
[StringLength(18, MinimumLength = 1, ErrorMessage = "MaterialCode must be between 1 and 18 characters.")]
public string? MaterialCode { get; set; }
public IList<IntegrationMaterialFreeGoodItemRequest> Slabs { get; set; }
}
public class IntegrationMaterialFreeGoodItemRequest
{
[Range(0.001, 9999999999999.999, ErrorMessage = "MinOrderQuantity must be greater than 0.")]
public decimal? MinOrderQuantity { get; set; }
[Range(0.001, 9999999999999.999, ErrorMessage = "ForQuantity must be greater than 0.")]
public decimal? ForQuantity { get; set; }
[StringLength(3, MinimumLength = 1, ErrorMessage = "UnitOfMeasure must be between 1 and 3 characters.")]
public string? UnitOfMeasure { get; set; }
[Range(0.001, 9999999999999.999, ErrorMessage = "FreeQuantity must be greater than 0.")]
public decimal? FreeQuantity { get; set; }
[StringLength(3, MinimumLength = 1, ErrorMessage = "FocUnitOfMeasure must be between 1 and 3 characters.")]
public string? FocUnitOfMeasure { get; set; }
[Required(ErrorMessage = "ValidFrom is required.")]
public DateTime ValidFrom { get; set; }
[Required(ErrorMessage = "ValidTo is required.")]
public DateTime ValidTo { get; set; }
[StringLength(1, MinimumLength = 1, ErrorMessage = "Status must be 1 character.")]
public string? Status { get; set; } // NULL = Active, X = Inactive
}
public class GetMaterialFreeGoodsByFilterRequest
{
[Required(ErrorMessage = "SalesOrg is required.")]
[StringLength(4, MinimumLength = 1, ErrorMessage = "SalesOrg must be between 1 and 4 characters.")]
public string SalesOrg { get; set; }
[Required(ErrorMessage = "DistributionChannel is required.")]
[StringLength(2, MinimumLength = 1, ErrorMessage = "DistributionChannel must be between 1 and 2 characters.")]
public string DistributionChannel { get; set; }
[StringLength(13, MinimumLength = 1, ErrorMessage = "CustomerNumber must be between 1 and 13 characters.")]
public string? CustomerNumber { get; set; } // nullable
[StringLength(2, MinimumLength = 1, ErrorMessage = "CustomerPriceGroup must be between 1 and 2 characters.")]
public string? CustomerPriceGroup { get; set; }
[Required(ErrorMessage = "MaterialCode is required.")]
[StringLength(10, MinimumLength = 1, ErrorMessage = "MaterialCode must be between 1 and 10 characters.")]
public string MaterialCode { get; set; }
public DateTime? ValidFrom { get; set; }
public DateTime? ValidTo { get; set; }
}

View File

@ -0,0 +1,110 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
public class IntegrationMaterialPriceRequest
{
public IntegrationMaterialPriceRequest()
{
Slabs = new List<IntegrationMaterialPriceItemRequest>();
}
[Required(ErrorMessage = "Condition Record No is required.")]
public int ConditionRecNo { get; set; }
[StringLength(4, ErrorMessage = "Sales Org cannot exceed 4 characters.")]
public string? SalesOrg { get; set; }
[StringLength(2, ErrorMessage = "Distribution Channel cannot exceed 2 characters.")]
public string? DistributionChannel { get; set; }
[StringLength(13, ErrorMessage = "Customer Number cannot exceed 13 characters.")]
public string? CustomerNumber { get; set; }
[StringLength(10, ErrorMessage = "Customer Group Code cannot exceed 10 characters.")]
public string? CustomerGroupCode { get; set; }
[StringLength(18, ErrorMessage = "Material Code cannot exceed 18 characters.")]
public string? MaterialCode { get; set; }
[Required(ErrorMessage = "Condition Type is required.")]
[StringLength(4, ErrorMessage = "Condition Type cannot exceed 4 characters.")]
public string ConditionType { get; set; }
[Required(ErrorMessage = "Calculation Type is required.")]
[StringLength(1, ErrorMessage = "Calculation Type cannot exceed 1 character.")]
public string CalculationType { get; set; }
[StringLength(1, ErrorMessage = "Pricing Unit cannot exceed 1 character.")]
public string? PricingUnit { get; set; }
[StringLength(3, ErrorMessage = "Unit Of Measure cannot exceed 3 characters.")]
public string? UnitOfMeasure { get; set; }
public decimal? ScaleValue { get; set; }
[Required(ErrorMessage = "Valid From is Required")]
public DateTime ValidFrom { get; set; }
[Required(ErrorMessage = "Valid To is Required")]
public DateTime ValidTo { get; set; }
[StringLength(1, ErrorMessage = "Status cannot exceed 1 character.")]
[RegularExpression(@"^$|^X$", ErrorMessage = "Status must be blank (Active) or 'X' (Inactive).")]
public string Status { get; set; }
[Required(ErrorMessage = "Condition Value is required.")]
[Range(typeof(decimal), "0", "999999999999999999.99", ErrorMessage = "Condition Value must be greater than or equal to 0.")]
public decimal ConditionValue { get; set; }
public IList<IntegrationMaterialPriceItemRequest> Slabs { get; set; }
}
public class IntegrationMaterialPriceItemRequest
{
public decimal? ScaleValue { get; set; }
[Required(ErrorMessage ="Valid From is Required")]
public DateTime ValidFrom { get; set; }
[Required(ErrorMessage = "Valid To is Required")]
public DateTime ValidTo { get; set; }
[StringLength(1, ErrorMessage = "Status cannot exceed 1 character.")]
[RegularExpression(@"^$|^X$", ErrorMessage = "Status must be blank (Active) or 'X' (Inactive).")]
public string Status { get; set; }
[Required(ErrorMessage = "Condition Value is required.")]
[Range(typeof(decimal), "0", "999999999999999999.99", ErrorMessage = "Condition Value must be greater than or equal to 0.")]
public decimal ConditionValue { get; set; }
}
public class GetMaterialPriceByCodeRequest
{
[Required(ErrorMessage = "Sales Org is required.")]
[StringLength(4, ErrorMessage = "Sales Org cannot exceed 4 characters.")]
public string SalesOrg { get; set; }
[Required(ErrorMessage = "Material Code is required.")]
[StringLength(18, ErrorMessage = "Material Code cannot exceed 18 characters.")]
public string MaterialCode { get; set; }
[Required(ErrorMessage = "Condition Type is required.")]
[StringLength(4, ErrorMessage = "Condition Type cannot exceed 4 characters.")]
public string ConditionType { get; set; }
[StringLength(13, ErrorMessage = "Customer Number cannot exceed 13 characters.")]
public string? CustomerNumber { get; set; }
[StringLength(10, ErrorMessage = "Customer Group Code cannot exceed 10 characters.")]
public string? CustomerGroupCode { get; set; }
[StringLength(10, ErrorMessage = "Distributor Channel Code cannot exceed 10 characters.")]
public string? DistributorChannelCode { get; set; }
public DateTime? ValidFrom { get; set; }
public DateTime? ValidTo { get; set; }
}

View File

@ -2,7 +2,7 @@
namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
public class MaterialIntegrationRequest
public class IntegrationMaterialRequest
{
[Required(ErrorMessage = "Material type is required.")]
[StringLength(4, ErrorMessage = "Material type cannot exceed 4 characters.")]
@ -13,12 +13,20 @@ public class MaterialIntegrationRequest
public string MaterialTypeDescription { get; set; }
[Required(ErrorMessage = "Material code is required.")]
[StringLength(10, ErrorMessage = "Material code cannot exceed 10 characters.")]
[StringLength(18, ErrorMessage = "Material code cannot exceed 18 characters.")]
public string MaterialCode { get; set; }
[Required(ErrorMessage = "Material description is required.")]
[StringLength(40, ErrorMessage = "Material description cannot exceed 40 characters.")]
public string MaterialDescription { get; set; }
public string MaterialDescription { get; set; }
[Required(ErrorMessage = "BrandCode is required.")]
[StringLength(3, ErrorMessage = "BrandCode cannot exceed 3 characters.")]
public string BrandCode { get; set; }
[Required(ErrorMessage = "BrandName is required.")]
[StringLength(20, ErrorMessage = "BrandName cannot exceed 20 characters.")]
public string BrandName { get; set; }
[Required(ErrorMessage = "Material group code is required.")]
[StringLength(9, ErrorMessage = "Material group code cannot exceed 9 characters.")]
@ -40,28 +48,21 @@ public class MaterialIntegrationRequest
[Range(0.001, 999.999, ErrorMessage = "Unit conversion must be between 0.001 and 999.999.")]
public decimal UnitConversionWithBaseUoM { get; set; }
[Required(ErrorMessage = "Plant code is required.")]
[StringLength(4, ErrorMessage = "Plant code cannot exceed 4 characters.")]
public string PlantCode { get; set; }
[StringLength(10, ErrorMessage = "Plant code cannot exceed 10 characters.")]
public string? PlantCode { get; set; }
[Required(ErrorMessage = "Plant description is required.")]
[StringLength(30, ErrorMessage = "Plant description cannot exceed 30 characters.")]
public string PlantDescription { get; set; }
public string? PlantDescription { get; set; }
[Required(ErrorMessage = "Storage location code is required.")]
[StringLength(4, ErrorMessage = "Storage location code cannot exceed 4 characters.")]
public string StorageLocationCode { get; set; }
[Required(ErrorMessage = "Storage location description is required.")]
[StringLength(16, ErrorMessage = "Storage location description cannot exceed 16 characters.")]
public string StorageLocationDescription { get; set; }
public string? StorageLocationCode { get; set; }
public string? StorageLocationDescription { get; set; }
[Required(ErrorMessage = "Sales Organization code is required.")]
[StringLength(4, ErrorMessage = "Sales Organization code cannot exceed 4 characters.")]
public string SalesOrganization { get; set; }
[Required(ErrorMessage = "Sales Organization Description is required.")]
[StringLength(16, ErrorMessage = "Sales Organization Description cannot exceed 16 characters.")]
[StringLength( 40, MinimumLength =1, ErrorMessage = "Sales Organization Description cannot exceed 40 characters.")]
public string SalesOrganizationDescription { get; set; }
}

View File

@ -0,0 +1,39 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
public class IntegrationPaymentTermRequest
{
[Required(ErrorMessage = "CreditCode is required.")]
[StringLength(4, MinimumLength = 1, ErrorMessage = "CreditCode must be between 1 and 4 characters.")]
public string CreditCode { get; set; }
[Required(ErrorMessage = "Description is required.")]
[StringLength(30, MinimumLength = 1, ErrorMessage = "Description must be between 1 and 30 characters.")]
public string Description { get; set; }
[Required(ErrorMessage = "ValidFrom is required.")]
public DateTime ValidFrom { get; set; }
[Required(ErrorMessage = "ValidTo is required.")]
public DateTime ValidTo { get; set; }
[Required(ErrorMessage = "SalesOrg is required.")]
[StringLength(4, MinimumLength = 1, ErrorMessage = "SalesOrg must be between 1 and 4 characters.")]
public string SalesOrg { get; set; }
}
public class GetIntegrationPaymentTermRequest
{
[Required(ErrorMessage = "CreditCode is required.")]
[StringLength(4, MinimumLength = 1, ErrorMessage = "CreditCode must be between 1 and 4 characters.")]
public string CreditCode { get; set; }
[Required(ErrorMessage = "SalesOrg is required.")]
[StringLength(4, MinimumLength = 1, ErrorMessage = "SalesOrg must be between 1 and 4 characters.")]
public string SalesOrg { get; set; }
}

View File

@ -0,0 +1,61 @@

using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Common;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp;
public class MobileAppAuthRequest : AuthLoginRequest
{
}
public class AuthLogoutRequest
{
[Required, NotNull, StringLength(maximumLength: 10, MinimumLength = 3, ErrorMessage = "Login Id must be between 3 and 10 characters.")]
public string LoginId { get; set; }
}
public class AppUserChangePasswordRequest
{
[Required, NotNull, StringLength(maximumLength: 10, MinimumLength = 3, ErrorMessage = "Login Id must be between 3 and 10 characters.")]
public string LoginId { get; set; }
[Required, NotNull, StringLength(maximumLength: 20, MinimumLength = 5, ErrorMessage = "Password must be between 5 and 20 characters.")]
public string Password { get; set; }
[Required, NotNull, StringLength(maximumLength: 20, MinimumLength = 5, ErrorMessage = "Password must be between 5 and 20 characters.")]
public string NewPassword { get; set; }
[Required, NotNull, StringLength(maximumLength: 20, MinimumLength = 5, ErrorMessage = "Password must be between 5 and 20 characters.")]
[Compare("NewPassword", ErrorMessage = "Password and Confirm Password do not match.")]
public string ConfirmPassword { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!string.IsNullOrWhiteSpace(NewPassword) &&
!string.IsNullOrWhiteSpace(ConfirmPassword) &&
Password != ConfirmPassword)
{
yield return new ValidationResult(
"Password and Confirm Password do not match.",
[nameof(NewPassword), nameof(ConfirmPassword)]
);
}
if (!string.IsNullOrWhiteSpace(LoginId) &&
!string.IsNullOrWhiteSpace(NewPassword) &&
Password.Equals(LoginId, StringComparison.OrdinalIgnoreCase))
{
yield return new ValidationResult(
"Password must not be the same as your Login ID.",
[nameof(NewPassword)]
);
}
}
}

View File

@ -0,0 +1,185 @@

using DocumentFormat.OpenXml.Wordprocessing;
using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp;
public class GetMarketHeirarchyByEmpRequest
{
[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(15, MinimumLength = 1, ErrorMessage = "Brand code must be between 1 and 15 characters.")]
public string? BrandCode { get; set; }
[StringLength(15, MinimumLength = 1, ErrorMessage = "Sales organization code must be between 1 and 15 characters.")]
public string? SalesOrgCode { get; set; }
[StringLength(15, MinimumLength = 1, ErrorMessage = "Employee number must be between 1 and 15 characters.")]
public string? EmployeeNumber { get; set; }
}
public class GetMaterialsRequest : PagedRequest
{
[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(15, MinimumLength = 1, ErrorMessage = "Material Code must be between 1 and 15 characters.")]
public string? MaterialCode { get; set; }
[StringLength(15, MinimumLength = 1, ErrorMessage = "Employee number must be between 1 and 15 characters.")]
public string? EmployeeNumber { get; set; }
}
public class GetMaterialStockRequest : PagedRequest
{
[StringLength(15, MinimumLength = 1, ErrorMessage = "Plant Code must be between 1 and 15 characters.")]
public string PlantCode { get; set; }
}
public class GetMaterialPriceRequest : PagedRequest
{
[StringLength(15, MinimumLength = 1, ErrorMessage = "Sales organization code must be between 1 and 15 characters.")]
public string SalesOrgCode { get; set; }
[StringLength(15, MinimumLength = 1, ErrorMessage = "Brand code must be between 1 and 15 characters.")]
public string? BrandCode { get; set; }
[StringLength(15, MinimumLength = 1, ErrorMessage = "Material Code must be between 1 and 15 characters.")]
public string? MaterialCode { get; set; }
[StringLength(15, MinimumLength = 1, ErrorMessage = "Employee number must be between 1 and 15 characters.")]
public string? EmployeeNumber { get; set; }
}
public class GetCustomersRequest : PagedRequest
{
[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(15, MinimumLength = 5, ErrorMessage = "Customer Code must be between 1 and 15 characters.")]
public string? CustomerCode { get; set; }
[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: 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: 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; }
public string? CheckInLongitude { get; set; }
public string? CheckInLocationName { get; set; }
public TimeSpan? CheckOutTime { get; set; }
public string? CheckOutLatitude { get; set; }
public string? CheckOutLongitude { get; set; }
public string? CheckOutLocationName { get; set; }
public bool IsCheckIn => CheckInTime.HasValue ? true : false;
public bool IsCheckOut => CheckOutTime.HasValue ? true : false;
}
public class GetDiscountRequest : PagedRequest
{
[StringLength(15, MinimumLength = 3, ErrorMessage = "Sales organization code must be between 1 and 15 characters.")]
public string SalesOrgCode { get; set; }
[StringLength(15, MinimumLength = 5, ErrorMessage = "Customer Code must be between 1 and 15 characters.")]
public string? CustomerCode { get; set; }
[StringLength(15, MinimumLength = 5, ErrorMessage = "Employee number must be between 1 and 15 characters.")]
public string? EmployeeNumber { get; set; }
[StringLength(15, MinimumLength = 3, ErrorMessage = "Material Code must be between 1 and 15 characters.")]
public string? MaterialCode { get; set; }
[StringLength(15, MinimumLength = 1, ErrorMessage = "Discount Code must be between 1 and 15 characters.")]
public string? DiscountCode { get; set; }
}
public class GetDiscountMaterialRequest : PagedRequest
{
[StringLength(15, MinimumLength = 3, ErrorMessage = "Sales organization code must be between 1 and 15 characters.")]
public string SalesOrgCode { get; set; }
[StringLength(15, MinimumLength = 5, ErrorMessage = "Customer Code must be between 1 and 15 characters.")]
public string? CustomerCode { get; set; }
[StringLength(15, MinimumLength = 5, ErrorMessage = "Employee number must be between 1 and 15 characters.")]
public string? EmployeeNumber { get; set; }
[StringLength(15, MinimumLength = 3, ErrorMessage = "Material Code must be between 1 and 15 characters.")]
public string? MaterialCode { get; set; }
[StringLength(15, MinimumLength = 1, ErrorMessage = "Discount Code must be between 1 and 15 characters.")]
public string? DiscountCode { get; set; }
}
public class GetFOCRequest : PagedRequest
{
[StringLength(15, MinimumLength = 3, ErrorMessage = "Sales organization code must be between 1 and 15 characters.")]
public string SalesOrgCode { get; set; }
[StringLength(15, MinimumLength = 5, ErrorMessage = "Customer Code must be between 1 and 15 characters.")]
public string? CustomerCode { get; set; }
[StringLength(15, MinimumLength = 5, ErrorMessage = "Employee number must be between 1 and 15 characters.")]
public string? EmployeeNumber { get; set; }
[StringLength(15, MinimumLength = 3, ErrorMessage = "Material Code must be between 1 and 15 characters.")]
public string? MaterialCode { get; set; }
[StringLength(15, MinimumLength = 1, ErrorMessage = "Discount Code must be between 1 and 15 characters.")]
public string? FOCCode { get; set; }
}
public class GetFOCMaterialRequest : PagedRequest
{
[StringLength(15, MinimumLength = 3, ErrorMessage = "Sales organization code must be between 1 and 15 characters.")]
public string SalesOrgCode { get; set; }
[StringLength(15, MinimumLength = 5, ErrorMessage = "Customer Code must be between 1 and 15 characters.")]
public string? CustomerCode { get; set; }
[StringLength(15, MinimumLength = 5, ErrorMessage = "Employee number must be between 1 and 15 characters.")]
public string? EmployeeNumber { get; set; }
[StringLength(15, MinimumLength = 3, ErrorMessage = "Material Code must be between 1 and 15 characters.")]
public string? MaterialCode { get; set; }
[StringLength(15, MinimumLength = 1, ErrorMessage = "Discount Code must be between 1 and 15 characters.")]
public string? FOCCode { get; set; }
}
public class GetPaymentTermRequest : PagedRequest
{
[StringLength(15, MinimumLength = 3, ErrorMessage = "Sales organization code must be between 1 and 15 characters.")]
public string SalesOrgCode { get; set; }
[StringLength(15, MinimumLength = 3, ErrorMessage = "PayementTerm Code must be between 3 and 15 characters.")]
public string? PaymentTermCode { get; set; }
}

View File

@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp;
public class SaveOrderRequest
{
public SaveOrderRequest()
{
Items = new List<SaveOrderItemRequest>();
}
public int? OrderId { get; set; }
public string? OrderNo { get; set; }
public DateTime? OrderDate { get; set; }
public DateTime? ExpectedDeliveryDate { get; set; }
public Guid UUID { get; set; }
public string SalesOrgCode { get; set; }
public int? CustomerId { get; set; }
public string CustomerCode { get; set; }
public string CustomerName { get; set; }
public string PlantCode { get; set; }
public int? EmployeeId { get; set; }
public string EmployeeCode { get; set; }
public string? ReferenceTxtNo { get; set; }
public string PaymentTermCode { get; set; }
public string DiscountCode { get; set; }
public string FocCode { get; set; }
public decimal FocValue { get; set; }
public decimal DiscountValue { get; set; }
public decimal? TotalVatValue { get; set; }
public decimal GrossValue { get; set; }
public decimal NetValue { get; set; }
public OrderStatusEnum OrderStatus { get; set; }
public IList<SaveOrderItemRequest> Items { get; set; }
}
public class SaveOrderItemRequest
{
public int? OrderId { get; set; }
public int? ItemId { get; set; }
public int? MaterialId { get; set; }
public string MaterialCode { get; set; }
public string MaterialName { get; set; }
public decimal UnitPrice { get; set; }
public string SalesUnit { get; set; }
public decimal PromoQuantity { get; set; }
public decimal OrderQuantity { get; set; }
public decimal ValidatedQuantity { get; set; }
public decimal ApprovedQuantity { get; set; }
public decimal DiscountPercentage { get; set; }
public decimal DiscountAmount { get; set; }
public bool IsFocItem { get; set; }
public decimal LineTotalValue { get; set; }
public decimal? VatValue { get; set; }
}
public class SaveOrderPromotionRequest
{
public int ItemId { get; set; }
public int OrderId { get; set; }
public int MaterialId { get; set; }
public string PromotionCode { get; set; }
public decimal DiscountPercentage { get; set; }
public decimal DiscountValue { get; set; }
public decimal ForQuantity { get; set; }
public decimal OrderFocQty { get; set; }
public decimal ValidatedFocQty { get; set; }
public decimal ApprovedFocQty { get; set; }
public int PromotionType { get; set; }
}
public class GetOrderSummaryRequest : PagedRequest
{
public string? SalesOrgCode { get; set; }
public string? EmployeeCode { get; set; }
public string? OrderNo { get; set; }
public string? UUID { get; set; }
public string? CustomerCode { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public OrderStatusEnum? OrderStatus { get; set; }
}
public class UpdateSapDetailsRequest
{
public int OrderId { get; set; }
public string SapOrderNo { get; set; }
public SAPOrderStatusEnum Status { get; set; }
public DateTime SentAt { get; set; }
public int RetryCount { get; set; }
public string LastError { get; set; }
public string ReturnMessage { get; set; }
}

View File

@ -1,6 +1,8 @@
using System;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Setups;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.Systems;
@ -70,6 +72,14 @@ public class ResetPasswordRequest : ByUserIdRequest
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
{
[Required, NotNull, StringLength(maximumLength: 30, MinimumLength = 1, ErrorMessage = "Old Password must be between 1 and 30 characters.")]
@ -219,3 +229,43 @@ public class PayslipRequest
[Required, NotNull]
public DateTime YearMonth { get; set; }
}
public class AppUserCreateRequest
{
[Required(ErrorMessage = "Login ID is required.")]
[MaxLength(100, ErrorMessage = "Login ID cannot exceed 100 characters.")]
public string LoginId { get; set; } = string.Empty;
[Required(ErrorMessage = "Username is required.")]
[MaxLength(150, ErrorMessage = "Username cannot exceed 150 characters.")]
public string UserName { get; set; } = string.Empty;
[Required]
public int Status { get; set; }
[Required]
public int AccessStatus { get; set; }
[Phone(ErrorMessage = "Invalid mobile number format.")]
[MaxLength(20, ErrorMessage = "Mobile number cannot exceed 20 characters.")]
public string? MobileNo { get; set; }
[Required(ErrorMessage = "Email address is required.")]
[EmailAddress(ErrorMessage = "Invalid email address format.")]
[MaxLength(200, ErrorMessage = "Email address cannot exceed 200 characters.")]
public string EmailAddress { get; set; } = string.Empty;
public int IsLocked { get; set; }
[Required(ErrorMessage = "Password is required.")]
[MaxLength(500, ErrorMessage = "Password hash length cannot exceed 500 characters.")]
public string Password { get; set; } = string.Empty;
public int? UserPlatformId { get; set; }
public int? UserRoleType { get; set; }
[MaxLength(10,ErrorMessage = "EmployeeNumber hash length cannot exceed 10 characters")]
public string? EmployeeNumber { get; set; }
}

View File

@ -1,7 +1,4 @@
using OnlineSalesAutoCrop.CoreAPI.Models.Objects.Systems;
using System;
using System.Collections.Generic;
using System.Text;
using System;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
@ -14,16 +11,22 @@ public class IntegrationLoginResponse : ResponseBase
public DateTime AccessTokenExpiry { get; set; }
}
public class IntegrationRrefreshTokenResponse : ResponseBase
{
public string AccessToken { get; set; } = string.Empty;
public string RefreshToken { get; set; } = string.Empty;
}
public class RefreshTokenResponse : ResponseBase
public class MobileRrefreshTokenResponse
{
public int UserId { get; set; }
public string AccessToken { get; set; } = string.Empty;
public string RefreshToken { get; set; } = string.Empty;
}
public class RefreshTokenResponse : ResponseBase
{
public int UserId { get; set; }
public string TokenHash { get; set; }
public string IpAddress { get; set; }
public DateTime ExpiredAt { get; set; }
@ -35,14 +38,14 @@ public class RefreshTokenResponse : ResponseBase
public class GenerateRefreshTokenResponse : ResponseBase
{
public string RefreshToken { get; set; }
public DateTime ExpireTime { get; set; }
public DateTime ExpireTime { get; set; }
}
public class UserByRefreshTokenResponse : ResponseBase
public class UserByRefreshTokenResponse : ResponseBase
{
public int UserId { get; set; }
public string LoginId { get; set; }
public string EmailAddress { get; set; }
public string AuthKey { get; set; }
public bool IsActive { set; get; }
public string EmailAddress { get; set; }
public string AuthKey { get; set; }
public bool IsActive { set; get; }
}

View File

@ -1,6 +1,6 @@
namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
public class CustomerIntegrationResponse : ResponseBase
public class IntegrationCustomerResponse : ResponseBase
{
public string CustomerNumber { get; set; }
public string CompanyCode { get; set; }

View File

@ -1,6 +1,6 @@
namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
public class EmployeeIntegrationResponse : ResponseBase
public class IntegrationEmployeeResponse : ResponseBase
{
public int EmployeeId { get; set; }
public string EmployeeVendorCode { get; set; }

View File

@ -0,0 +1,90 @@

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
public sealed class SapODataResponse<T>
{
[JsonPropertyName("d")]
public SapODataResult<T> D { get; set; } = default!;
}
public sealed class SapODataResult<T>
{
[JsonPropertyName("results")]
public List<T> Results { get; set; } = new();
}
public sealed class IntegrationCustLedgerHttpResponse
{
[JsonPropertyName("company")]
public string Company { get; set; } = string.Empty;
[JsonPropertyName("customer_no")]
public string CustomerNo { get; set; } = string.Empty; // stays string — SAP customer numbers can carry leading zeros
[JsonPropertyName("current_balance")]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public decimal CurrentBalance { get; set; }
[JsonPropertyName("overdue_amount")]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public decimal OverdueAmount { get; set; }
[JsonPropertyName("currency")]
public string Currency { get; set; } = string.Empty;
[JsonPropertyName("overdue_date")]
[JsonConverter(typeof(SapODataDateConverter))]
public DateTime OverdueDate { get; set; }
}
public sealed class IntegrationMaterialStockHttpResponse
{
[JsonPropertyName("plant")]
public string PlantCode { get; set; } = string.Empty;
[JsonPropertyName("material")]
public string MaterialCode { get; set; } = string.Empty; // stays string — SAP customer numbers can carry leading zeros
[JsonPropertyName("available_stock")]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public decimal AvailableStock { get; set; }
[JsonPropertyName("uom")]
public string UOM { get; set; } = string.Empty;
}
public sealed class SapODataDateConverter : JsonConverter<DateTime>
{
// Matches "/Date(1783641600000)/" and the rarer offset form "/Date(1783641600000+0600)/"
private static readonly Regex Pattern = new(@"/Date\((-?\d+)(?:[+-]\d{4})?\)/", RegexOptions.Compiled);
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var raw = reader.GetString();
if (string.IsNullOrEmpty(raw))
return default;
var match = Pattern.Match(raw);
if (!match.Success)
throw new JsonException($"Unexpected SAP OData date format: '{raw}'");
var ms = long.Parse(match.Groups[1].Value);
return DateTimeOffset.FromUnixTimeMilliseconds(ms).UtcDateTime;
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
var ms = new DateTimeOffset(value, TimeSpan.Zero).ToUnixTimeMilliseconds();
writer.WriteStringValue($"/Date({ms})/");
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
public class IntegrationBillingReqResponse : ResponseBase
{
public string InvoiceBillingDocumentNumber { get; set; } = string.Empty;
public DateTime BillingDate { get; set; }
}
public class IntegrationDeliveryReqResponse : ResponseBase
{
public string DeliveryDocumentNo{ get; set; } = string.Empty;
public DateTime DeliveryDate { get; set; }
}

View File

@ -0,0 +1,31 @@
using System;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
public class IntegrationMaterialFreeGoodsResponse : ResponseBase
{
public int FreeGoodsId { get; set; } // required for Update
public string SalesOrg { get; set; }
public string DistributionChannel { get; set; }
public string? CustomerNumber { get; set; } // nullable
public string? CustomerPriceGroup { get; set; }
public string? MaterialCode { get; set; }
public decimal? MinOrderQuantity { get; set; }
public decimal? ForQuantity { get; set; }
public string? UnitOfMeasure { get; set; }
public decimal? FreeQuantity { get; set; }
public string? FocUnitOfMeasure { get; set; }
public DateTime? ValidFrom { get; set; }
public DateTime? ValidTo { get; set; }
public string? Status { get; set; }
}
public class MaterialFreeGoodsByReqResponse : ResponseBase
{
public string SalesOrg { get; set; }
public string DistributionChannel { get; set; }
public string CustomerNumber { get; set; }
public string CustomerPriceGroup { get; set; }
public string MaterialCode { get; set; }
public bool IsUpdated { get; set; }
}

View File

@ -0,0 +1,39 @@
using System;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
public class IntegrationMaterialPriceResponse : ResponseBase
{
public int MaterialPriceId { get; set; }
public string? SalesOrg { get; set; }
public string? DistributionChannel { get; set; }
public string? CustomerNumber { get; set; }
public string? CustomerGroupCode { get; set; }
public string MaterialCode { get; set; }
public string ConditionType { get; set; }
public string CalculationType { get; set; }
public string PricingUnit { get; set; }
public string UnitOfMeasure { get; set; }
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; }
public string Status { get; set; }
public decimal ConditionValue { get; set; }
}
public class MaterialIntegrationPriceReqResponse : ResponseBase
{
public string MaterialCode { get; set; }
public string ConditionType { get; set; }
public string? CustomerNumber { get; set; }
public string? CustomerGroupCode { get; set; }
public bool IsUpdated { get; set; }
}
public class MaterialPriceTypeResponse
{
public int PriceTypeId { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public MaterialPriceTypeEnum PriceType { get; set; }
}

View File

@ -2,7 +2,7 @@
namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
public class MaterialIntegrationResponse : ResponseBase
public class IntegrationMaterialResponse : ResponseBase
{
public int MeterialId { get; set; }
public string MaterialType { get; set; }
@ -20,6 +20,8 @@ public class MaterialIntegrationResponse : ResponseBase
public string StorageLocationDescription { get; set; }
public string SalesOrganization { get; set; }
public string SalesOrganizationDescription { get; set; }
public string BrandCode { get; set; }
public string BrandName { get; set; }
}
public class MaterialIntegrationReqResponse : ResponseBase

View File

@ -0,0 +1,20 @@
using System;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
public class IntegrationPaymentTermResponse : ResponseBase
{
public int CreditCodeId { get; set; }
public string CreditCode { get; set; }
public string Description { get; set; }
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; }
public string SalesOrg { get; set; }
}
public class IntegrationPaymentTermReqResponse : ResponseBase
{
public string CreditCode { get; set; }
public string SalesOrg { get; set; }
public bool IsUpdated { get; set; }
}

View File

@ -0,0 +1,41 @@

using System;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses.MobileApp;
public class AppAuthUserResponse
{
public int UserId { get; set; }
public string LoginId { get; set; }
public string Name { get; set; }
public string? Email { get; set; }
public string? MobileNumber { get; set; }
public string EmployeeNumber { get; set; }
public string DesignationCode { get; set; }
public string DesignationName { get; set; }
public bool IsLocked { get; set; }
public UserRoleTypeEnum UserRole { get; set; }
public string AccessToken { get; set; }
public DateTime AccessTokenExpiryTime { get; set; }
public string RefreshToken { get; set; }
public DateTime RefreshTokenExpiryTime { get; set; }
public EnumLoginStatus LoginStatus { get; set; }
public bool IsAuthorized { get; set; }
public bool IsInitialPassword { get; set; }
}
public class AuthLogoutResponse
{
public int UserId { get; set; }
public string LoginId { get; set; }
public bool IsSuccess { get; set; }
}
public class AppChangePasswordResponse
{
public int UserId { get; set; }
public string LoginId { get; set; }
public bool IsSuccess { get; set; }
}

View File

@ -0,0 +1,248 @@
using Microsoft.AspNetCore.Mvc.Formatters;
using System;
using System.Collections.Generic;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses.MobileApp;
public class MarketHeirarchyByEmpResponse
{
public MarketHeirarchyByEmpResponse()
{
Companies = new List<EmpCompanyResponse>();
SalesOrgs = new List<SalesOrgResponse>();
DistributionChannels = new List<DistibutionChannleResponse>();
Regions = new List<RegionResponse>();
Areas = new List<AreaResponse>();
Teritories = new List<TeritoryResponse>();
Plants = new List<PlantResponse>();
}
public IList<EmpCompanyResponse> Companies { get; set; }
public IList<SalesOrgResponse> SalesOrgs { get; set; }
public IList<DistibutionChannleResponse> DistributionChannels { get; set; }
public IList<RegionResponse> Regions { get; set; }
public IList<AreaResponse> Areas { get; set; }
public IList<TeritoryResponse> Teritories { get; set; }
public IList<PlantResponse> Plants { get; set; }
}
public class EmpCompanyResponse
{
public string CompanyCode { get; set; }
public string CompanyName { get; set; }
}
public class SalesOrgResponse
{
public string CompanyCode { get; set; }
public string SalesOrgCode { get; set; }
public string SalesOrgName { get; set; }
}
public class DistibutionChannleResponse
{
public string CompanyCode { get; set; }
public string SalesOrgCode { get; set; }
public string DistributorChannelCode { get; set; }
public string DistributorChannelName { get; set; }
}
public class RegionResponse
{
public string CompanyCode { get; set; }
public string SalesOrgCode { get; set; }
public string RegionCode { get; set; }
public string RegionName { get; set; }
}
public class AreaResponse
{
public string CompanyCode { get; set; }
public string SalesOrgCode { get; set; }
public string RegionCode { get; set; }
public string AreaCode { get; set; }
public string AreaName { get; set; }
}
public class TeritoryResponse
{
public string CompanyCode { get; set; }
public string SalesOrgCode { get; set; }
public string RegionCode { get; set; }
public string AreaCode { get; set; }
public string TeritoryCode { get; set; }
public string TeritoryName { get; set; }
}
public class PlantResponse
{
public string CompanyCode { get; set; }
public string SalesOrgCode { get; set; }
public string PlantCode { get; set; }
public string PlantName { get; set; }
}
public class GetBrandResponse
{
public string BrandCode { get; set; } = string.Empty;
public string BrandName { get; set; } = string.Empty;
}
public class GetMaterialResponse
{
public string MaterialName { get; set; }
public string MaterialCode { get; set; }
public string TypeCode { get; set; }
public string TypeName { get; set; }
public string GroupCode { get; set; }
public string GroupName { get; set; }
public string BaseUnitMeasurement { get; set; }
public string SalesUnitMeasurement { get; set; }
public decimal UnitConversionValue { get; set; }
public string BrandCode { get; set; }
public string BrandName { get; set; }
public string SalesOrg { get; set; }
public bool IsSealable { get; set; } = true;
}
public class GetMaterialPriceResponse
{
public string SalesOrgCode { get; set; }
public string CustomerCode { get; set; }
public string MaterialCode { get; set; }
public decimal BasePrice { get; set; }
public decimal? VatValue { get; set; }
public string? VatCalculationType { get; set; }
}
public class GetCustomerResponse
{
public string CustomerCode { get; set; }
public string CustomerName { get; set; }
public string AccountGroupCode { get; set; }
public string AccountGroupDescription { get; set; }
public string DistributionChannelCode { get; set; }
public string DistributionChannelName { get; set; }
public string DivisionCode { get; set; }
public string DivisionName { get; set; }
public string MobileNumber { get; set; }
public string EmailAddress { get; set; }
public string TaxNumber { get; set; }
public string? SalesGroupCode { get; set; }
public string? SalesGroupName { get; set; }
public string? CustomerGroupCode { get; set; }
public string? CustomerGroupName { get; set; }
public string? CustomerPriceGroupCode { get; set; }
public string? CustomerPriceGroupName { get; set; }
public string? PaymentTermCode { get; set; }
public string SalesOrgCode { get; set; }
public string TeritoryCode { get; set; }
public string PlantCode { get; set; }
public bool IsCreditCustomer { get; set; }
public decimal CreditLimit { get; set; }
public decimal CreditBalance { get; set; }
public decimal CreditAvailable => CreditLimit - CreditBalance;
public decimal CreditOverdue { get; set; }
}
public class GetAttendanceByEmpResponse
{
public DateTime? AttendanceDate { get; set; }
public string EmployeeNumber { get; set; }
public TimeSpan? CheckInTime { get; set; }
public string? CheckInLatitude { get; set; }
public string? CheckInLongitude { get; set; }
public string? CheckInLocationName { get; set; }
public TimeSpan? CheckOutTime { get; set; }
public string? CheckOutLatitude { get; set; }
public string? CheckOutLongitude { get; set; }
public string? CheckOutLocationName { get; set; }
public bool? IsCheckIn => CheckInTime.HasValue ? true : false;
public bool? IsCheckOut => CheckOutTime.HasValue ? true : false;
}
public class GetDiscountResponse
{
public string? SalesOrgCode { get; set; }
public string DiscountCode { get; set; }
public string DiscountName { get; set; }
public string? DistributorChannelCode { get; set; }
public string? DistributorChannelName { get; set; }
public DiscountTypeEnum DiscountType { get; set; }
}
public class GetDiscountMaterialResponse
{
public string DiscountCode { get; set; }
public string CustomerCode { get; set; }
public string SalesOrgCode { get; set; }
public string TeritoryCode { get; set; }
public string? MaterialCode { get; set; }
public string UnitOfMeasurement { get; set; }
public string CalculationType { get; set; }
public decimal Threshold { get; set; }
public decimal DiscountValue { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
public class GetFOCResponse
{
public string? SalesOrgcCode { get; set; }
public string FOCCode { get; set; }
public string FOCName { get; set; }
public string? DistributorChannelCode { get; set; }
public string? DistributorChannelName { get; set; }
}
public class GetFOCMaterialResponse
{
public string FOCCode { get; set; }
public string CustomerCode { get; set; }
public string SalesOrgCode { get; set; }
public string TeritoryCode { get; set; }
public string MaterialCode { get; set; }
public string? UnitOfMeasurement { get; set; }
public decimal? MinimumOrderQty { get; set; }
public decimal? ForQuantity { get; set; }
public decimal? FreeQty { get; set; }
public string? FOCUnitOfMeasurement { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
public class GetPaymentTermResponse
{
public int PaymentTermId { get; set; }
public string PaymentTermCode { get; set; }
public string PaymentTermDescription { get; set; }
public string SalesOrgCode { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string PaymentTermType { get; set; }
}
public class GetMaterialStockReponse
{
public string PlantCode { get;set; }
public string MaterialCode { get; set; }
public decimal AvailableStock { get; set; }
}
public class GetPasswordPolicyResponse
{
public int? MinLength { get; set; }
public int? MaxLength { get; set; }
public bool IsUppercase { get; set; }
public bool IsLowercase { get; set; }
public bool IsNumber { get; set; }
public bool IsSpecialCharacter { get; set; }
public int? PasswordLife { get; set; }
}

View File

@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses.MobileApp;
public class SaveOrderResponse
{
public int OrderId { get; set; }
public string OrderNo { get; set; }
public DateTime OrderDate { get; set; }
public Guid UUID { get; set; }
public bool IsNewOrder { get; set; }
}
public class OrderBasicResponse
{
public int OrderId { get; set; }
public string OrderNo { get; set; }
public DateTime OrderDate { get; set; }
public Guid UUID { get; set; }
public OrderStatusEnum OrderStatus { get; set; }
}
public class GetSalesOrderResponse
{
public int OrderId { get; set; }
public Guid Uuid { get; set; }
public string OrderNo { get; set; }
public DateTime OrderDate { get; set; }
public string SalesOrgCode { get; set; }
public string CustomerCode { get; set; }
public string CustomerName { get; set; }
public string? ReferenceTxtNo { get; set; }
public string EmployeeCode { get; set; }
public DateTime? ExpectedDeliveryDate { get; set; }
public string PaymentTermCode { get; set; }
public string DiscountCode { get; set; }
public string FocCode { get; set; }
public decimal DiscountValue { get; set; }
public decimal GrossValue { get; set; }
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<GetSalesOrderItemResponse> Items { get; set; } = new();
}
public class GetSalesOrderItemResponse
{
public int OrderId { get; set; }
public Guid UUID { get;set; }
public string MaterialCode { get; set; }
public string MaterialName { get; set; }
public decimal UnitPrice { get; set; }
public string SalesUnit { get; set; }
public decimal? PromoQuantity { get; set; }
public decimal? OrderQuantity { get; set; }
public decimal? ValidatedQuantity { get; set; }
public decimal? ApprovedQuantity { get; set; }
public decimal? DiscountPercentage { get; set; }
public decimal? DiscountAmount { get; set; }
public decimal? VatValue { get; set; }
public bool IsFocItem { get; set; }
public decimal LineTotalValue { get; set; }
}
public class GetOrderPromotionResponse
{
public int ItemId { get; set; }
public int OrderID { get; set; }
public int MaterialId { get; set; }
public string PromotionCode { get; set; }
public decimal DiscountPercentage { get; set; }
public decimal DiscountValue { get; set; }
public decimal ForQuantity { get; set; }
public decimal OrderFocQuantity { get; set; }
public decimal ValidatedFocQuantity { get; set; }
public decimal ApprovedFocQuantity { get; set; }
public int PromotionType { get; set; }
}
public sealed class UserRoleDto
{
public int UserId { get; set; }
public UserRoleTypeEnum RoleId { get; set; }
}
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
{
public int PaymentTermId { get; set; }
public string PaymentTermCode { get; set; } = string.Empty;
}
public sealed class DiscountLookupDto
{
public int DiscountId { get; set; }
public string DiscountCode { get; set; } = string.Empty;
}
public sealed class MaterialLookupDto
{
public int MaterialId { get; set; }
public string MaterialCode { get; set; } = string.Empty;
public string MaterialName { get; set; } = string.Empty;
}
public sealed class FreeGoodsRuleDto
{
public int FreeGoodsId { get; set; }
public int MaterialId { get; set; }
public decimal MinOrderQuantity { get; set; }
public decimal ForQuantity { get; set; }
public decimal FreeQuantity { get; set; }
public string UnitOfMeasure { get; set; } = string.Empty;
public string FocUnitOfMeasure { get; set; } = string.Empty;
}
public class GetOrderForSAPResponse
{
public GetOrderForSAPResponse()
{
to_Item = new List<GetOrderItemForSAPResponse>();
}
public string SalesOrderType { get; set; }
public string SalesOrganization { get; set; }
public string DistributionChannel { get; set; }
public string OrganizationDivision { get; set; }
public string SoldToParty { get; set; }
public string PurchaseOrderByCustomer { get; set; }
public string CustomerPurchaseOrderDate { get; set; }
public string PurchaseOrderByShipToParty { get; set; }
public string CustomerPaymentTerms { get; set; }
public string? CustomerGroup { get; set; }
public string? CustomerPriceGroup { get; set; }
public IList<GetOrderItemForSAPResponse> to_Item { get; set; }
}
public class GetOrderItemForSAPResponse
{
public string Material { get; set; }
public string RequestedQuantity { get; set; }
}

View File

@ -1,7 +1,9 @@
using OnlineSalesAutoCrop.CoreAPI.Models.Global;
using OnlineSalesAutoCrop.CoreAPI.Models.Objects;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses
{
@ -12,7 +14,21 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses
public List<string> ReturnMessage { get; set; } = [];
}
public abstract class TotalRowsResponseBase : ResponseBase
public class MobileResponseBase<T>
{
public int ReturnStatus { get; set; }
public List<string> ReturnMessage { get; set; } = [];
public T? Data { get; set; }
public static MobileResponseBase<T> Success(T data, string message = "Success") =>
new() { ReturnStatus = 200, ReturnMessage = [message], Data = data };
public static MobileResponseBase<T> Failure(string message, int status = 400 ) =>
new() { ReturnStatus = status, ReturnMessage = [message], Data = default };
}
public abstract class TotalRowsResponseBase : ResponseBase
{
public int TotalRows { get; set; }
}
@ -50,4 +66,15 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses
{
public List<FoundKeywordItem> FoundKeywords { get; set; }
}
public class PagedResult<T>
{
public IList<T> Items { get; set; } = [];
public int TotalCount { get; set; }
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 10000;
public int TotalPages => (int)Math.Ceiling((double)TotalCount / PageSize);
public bool HasNextPage => PageNumber < TotalPages;
public bool HasPreviousPage => PageNumber > 1;
}
}

View File

@ -27,46 +27,28 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses.Systems
public DateTime? Expires { get; set; }
public DateTime SystemDate { get; set; }
public string LoginTime { get; set; }
public int LogId { get; set; }
public DateTime? LogoutTime { get; private set; }
public string MenuLayout { get; set; }
public string ThemeName { get; set; }
public string SchemeName { get; set; }
public bool DbOnStartup { get; set; }
public bool ViewOwnTaskOnly { get; private set; }
public int? EmployeeId { get; private set; }
public int NotificationCount { get; private set; }
public int IdleTime { get; set; }
public int PingTime { get; set; }
public int TimeoutTime { get; set; }
public bool BatchEnabled { get; private set; }
public int BmProcessId { get; private set; }
public int PrProcessId { get; private set; }
public string IdsValue { get; set; }
//public List<string> ModuleIds { get; set; } = [];
public List<string> ModuleIds { get; set; } = [];
public void Map(User source)
public string RefreshToken { get;set; }
public UserPlatFormType? UserPlatFormType { get; set; }
public UserRoleTypeEnum? UserRoleType { get; set; }
public string? EmployeeNumber { get; set; }
public void Map(User source)
{
Id = source.UserId;
LoginId = source.LoginId;
UserName = source.UserName;
ThemeName = source.ThemeName;
//ModuleIds = source.ModuleIds;
ModuleIds = source.ModuleIds;
LogoutTime = source.LogoutTime;
SchemeName = source.SchemeName;
MenuLayout = source.MenuLayout;
LoginStatus = source.LoginStatus;
//DbOnStartup = source.DbOnStartup;
//TimeoutTime = source.TimeoutTime;
//BmProcessId = source.BmProcessId;
//PrProcessId = source.PrProcessId;
//BatchEnabled = source.BatchEnabled;
//ViewOwnTaskOnly = source.ViewOwnTaskOnly;
//NotificationCount = source.NotificationCount;
//AuthRequiredAtLogin = source.AuthRequiredAtLogin;
//IdsValue = Newtonsoft.Json.JsonConvert.SerializeObject(source.ModuleIds);
}
UserPlatFormType = source.UserPlatFormType;
UserRoleType = source.UserRoleType;
EmployeeNumber = source.EmployeeNumber;
}
}
public class MenuResponse
@ -142,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<BulkPasswordUploadItem> Items { get; set; } = [];
}
}

View File

@ -1,4 +1,5 @@
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
using Ease.NetCore.DataAccess;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
using System.Threading.Tasks;
@ -9,4 +10,5 @@ public interface IRefreshTokenService
Task<GenerateRefreshTokenResponse> GenerateRefreshTokenByUserAsync(GenerateRefreshTokenRequest request);
Task<string> GenerateRefreshTokenAsync(string refreshToken, string ipAddress);
Task<UserByRefreshTokenResponse> GetUserByRefreshTokenAsync(string refreshToken);
Task<bool> InvalidRefreshTokenAsync(TransactionContext tc, int userId);
}

View File

@ -0,0 +1,13 @@

using Hangfire;
using System.Threading;
using System.Threading.Tasks;
namespace OnlineSalesAutoCrop.CoreAPI.Services.Contracts.BackgroupJobs;
public interface IBackgroundJobService
{
[Queue("sap_order_send")]
[DisableConcurrentExecution(timeoutInSeconds: 40 * 60)]
Task ExecuteSendingOrderAsync(CancellationToken cancellationToken);
}

View File

@ -0,0 +1,22 @@

using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.MobileApp;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Integrations;
public interface IIntegrationHttpService
{
Task<IntegrationCustLedgerHttpResponse?> GetCustomerLedgerAsync(string customerNumber, string companyCode,
CancellationToken cancellationToken = default);
Task<IList<IntegrationMaterialStockHttpResponse>> GetMaterialStockAsync(string plantCode,
CancellationToken cancellationToken = default);
Task<Tuple<string, string>> SendSAPOrdersAsync(GetOrderForSAPResponse request,
CancellationToken cancellationToken = default);
}

View File

@ -8,14 +8,31 @@ namespace OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Integrations;
public interface IIntegrationService
{
//Customer
Task<CustomerIntegrationResponse> UpsertCustomerAsync(CustomerIntegrationRequest request);
Task<IntegrationCustomerResponse> UpsertCustomerAsync(IntegrationCustomerRequest request);
Task<CustomerByCompanyCodeResponse> GetCustomerByCompanyCodeAsync(CustomerByCompanyCodeRequest request);
//Employee
Task<EmployeeIntegrationReqResponse> UpsertEmployeeAsync(EmployeeIntegrationRequest request);
Task<EmployeeIntegrationResponse> GetEmployeeBySalesOrgAsync(EmployeeIntegrationByComapanyRequest request);
Task<EmployeeIntegrationReqResponse> UpsertEmployeeAsync(IntegrationEmployeeRequest request);
Task<IntegrationEmployeeResponse> GetEmployeeBySalesOrgAsync(EmployeeIntegrationByComapanyRequest request);
//Meterial
Task<MaterialIntegrationReqResponse > UpsertMaterialAsync(MaterialIntegrationRequest request);
Task<MaterialIntegrationResponse> GetMaterialByCodeAsync(GetMaterialByCodeRequest request);
//Material
Task<MaterialIntegrationReqResponse > UpsertMaterialAsync(IntegrationMaterialRequest request);
Task<IntegrationMaterialResponse> GetMaterialByCodeAsync(GetMaterialByCodeRequest request);
//Material Price
Task<MaterialIntegrationPriceReqResponse> UpsertMaterialPriceAsync(IntegrationMaterialPriceRequest request);
Task<IntegrationMaterialPriceResponse> GetMaterialPriceByCodeAsync( GetMaterialPriceByCodeRequest request);
//Material Free Goods
Task<MaterialFreeGoodsByReqResponse> UpsertMaterialFreeGoodsAsync(IntegrationMaterialFreeGoodsRequest request);
Task<IntegrationMaterialFreeGoodsResponse> GetMaterialFreeGoodseByFilterAsync(GetMaterialFreeGoodsByFilterRequest request);
//Payment Terms
Task<IntegrationPaymentTermReqResponse> UpsertPaymentTermsAsync(IntegrationPaymentTermRequest request);
Task<IntegrationPaymentTermResponse> GetPaymentTermsByFilterAsync(GetIntegrationPaymentTermRequest request);
//Invoices
Task<IntegrationBillingReqResponse> SaveBillingDocumentAsync(IntegrationBillingDocumentRequest request);
Task<IntegrationDeliveryReqResponse> SaveDeliveryInvoiceDocumentAsync(IntegrationDeliveryDocumentRequest request);
}

View File

@ -0,0 +1,25 @@
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.MobileApp;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace OnlineSalesAutoCrop.CoreAPI.Services.Contracts.MobileApp;
public interface IMobileMasterDataService
{
Task<MarketHeirarchyByEmpResponse> GetMarketHeirarchiesByEmpAsync(GetMarketHeirarchyByEmpRequest request);
Task<PagedResult<GetBrandResponse>> GetBrandsAsync(GetBrandsRequest request);
Task<PagedResult<GetMaterialResponse>> GetMaterialsAsync(GetMaterialsRequest request);
Task<PagedResult<GetMaterialStockReponse>> GetMaterialsStockAsync(GetMaterialStockRequest request);
Task<PagedResult<GetMaterialPriceResponse>> GetMaterialPricesAsync(GetMaterialPriceRequest request);
Task<PagedResult<GetCustomerResponse>> GetCustomersAsync(GetCustomersRequest request);
Task<GetAttendanceByEmpResponse> GetAttendanceByEmpAsync(GetAttendanceByEmpRequest request);
Task<GetAttendanceByEmpResponse> UpsertAttendanceByEmpAsync(UpsertAttendanceByEmpRequest request);
Task<PagedResult<GetDiscountResponse>> GetDiscountAsync(GetDiscountRequest request);
Task<PagedResult<GetDiscountMaterialResponse>> GetDiscountMaterialsAsync(GetDiscountMaterialRequest request);
Task<PagedResult<GetFOCResponse>> GetFOCsAsync(GetFOCRequest request);
Task<PagedResult<GetFOCMaterialResponse>> GetFOCMaterialsAsync(GetFOCMaterialRequest request);
Task<PagedResult<GetPaymentTermResponse>> GetPaymentTermsAsync(GetPaymentTermRequest request);
Task<GetPasswordPolicyResponse> GetPasswordPolicyAsync();
}

View File

@ -0,0 +1,19 @@
using OnlineSalesAutoCrop.CoreAPI.Models;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.MobileApp;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace OnlineSalesAutoCrop.CoreAPI.Services.Contracts.MobileApp;
public interface IOrderService
{
Task<SaveOrderResponse> SaveOrderAsync(SaveOrderRequest request, int userId);
Task<PagedResult<GetSalesOrderResponse>> GetSalesOrdersSummary(GetOrderSummaryRequest request, int userId);
Task<IList<int>> GetAvailableOrderIdsForSAPAsync(SAPOrderStatusEnum orderStaus, CancellationToken cancellationToken = default);
Task<GetOrderForSAPResponse?> GetOrderDetailsForSapByIdAsync(int orderId, CancellationToken cancellationToken = default);
Task<bool> UpdateSendingSapDetailsAsync(UpdateSapDetailsRequest request, CancellationToken cancellationToken = default);
}

View File

@ -1,7 +1,10 @@
using OnlineSalesAutoCrop.CoreAPI.Models.Objects.Setups;
using OnlineSalesAutoCrop.CoreAPI.Models.Objects.Systems;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Common;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Systems;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Systems;
using System;
using System.Collections.Generic;
@ -13,10 +16,14 @@ namespace OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems
{
Task<bool> ValidateAuthValueAsync(string authValue, int userId);
Task<User> LoginAsync(LoginRequest request, string ipAddress, bool checkPwd);
Task<AppAuthUserResponse> LoginAsync(MobileAppAuthRequest request, string ipAddress);
Task<User> IntegrationLoginAsync(IntegrationLoginRequest request, string ipAddress, bool checkPwd);
Task<bool> LogoutAsync(string ipAddress, int userId, int logId, bool attendanceLogout, string loginId, string localIp, string macAddress, string hostName, string logoutRemarks);
Task<AuthLogoutResponse> LogoutAsync(AuthLogoutRequest request, string ipAddress);
Task<AppChangePasswordResponse> ChangePasswordAsync(AppUserChangePasswordRequest request, string ipAddress);
Task<bool> DeleteUserAsync(int userId, int deletedBy);
Task<bool> ForceLogoutNowAsync(List<int> userIds, string ipAddress);
Task<bool> UnlockUserAsync(int userId, string loginId, int unlockedBy);
@ -25,6 +32,7 @@ namespace OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems
Task<bool> AddUserAsync(NewUserRequest user, string ipAddress, int createdBy);
Task<bool> UpdateMyInfoAsync(string address, string contactNo, int modifiedBy, int emplyeeId);
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> SaveAuthorizeLimitAsync(decimal maxAuthLimit, int userId, string ipAddress, string savedBy);
Task<bool> UploadDocumentAsync(int userId, int id, int documentOf, string orgFileName, string fileName);

View File

@ -96,6 +96,7 @@
<ItemGroup>
<PackageReference Include="Ease.NetCore" Version="2.1.4" />
<PackageReference Include="Hangfire.Core" Version="1.8.24" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.8" />
<PackageReference Include="System.Data.SqlClient" Version="4.9.1" />

View File

@ -3,6 +3,7 @@ using Ease.NetCore.DataAccess.SQL;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Options;
using OnlineSalesAutoCrop.CoreAPI.Models.Global;
using OnlineSalesAutoCrop.CoreAPI.Models.Objects.Systems;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Auth;
@ -116,6 +117,22 @@ public class RefreshTokenService : IRefreshTokenService
return refreshTokenResponse;
}
public async Task<bool> InvalidRefreshTokenAsync( TransactionContext tc, int userId)
{
bool response = false;
try
{
RevokeRefreshTokenByUserIdAsync(tc, userId);
response = true;
}
catch (Exception e)
{
throw new InvalidOperationException(e.Message, e);
}
return response;
}
#region Private Methods
// ----- private helpers -----
@ -145,7 +162,7 @@ public class RefreshTokenService : IRefreshTokenService
SqlHelperExtension.CreateInParam(pName: "@TokenHash", pType: SqlDbType.VarChar, pValue: refreshToken.TokenHash),
SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: refreshToken.IpAddress),
SqlHelperExtension.CreateInParam(pName: "@CreatedAt", pType: SqlDbType.DateTime, pValue: DateTime.Now),
SqlHelperExtension.CreateInParam(pName: "@ExpiredAt", pType: SqlDbType.DateTime, pValue: DateTime.Now.AddMinutes(_settings.RefreshTokenDuration)),
SqlHelperExtension.CreateInParam(pName: "@ExpiredAt", pType: SqlDbType.DateTime, pValue: refreshToken.ExpiresAt),
];
_ = tc.ExecuteNonQuerySp(spName: "dbo.InsertRefreshToken", parameterValues: p);
@ -250,8 +267,8 @@ public class RefreshTokenService : IRefreshTokenService
UserId = userId,
TokenHash = tokenHash,
IpAddress = ipAddress,
CreatedAt = DateTime.UtcNow,
ExpiresAt = DateTime.UtcNow.AddDays(_settings.RefreshTokenDuration)
CreatedAt = DateTime.Now,
ExpiresAt = DateTime.Now.AddMinutes(_settings.RefreshTokenDuration)
};
AddAsync(tc,refreshToken);
@ -259,7 +276,7 @@ public class RefreshTokenService : IRefreshTokenService
return new GenerateRefreshTokenResponse
{
RefreshToken = tokenHash,
ExpireTime = DateTime.UtcNow.AddMinutes(_settings.RefreshTokenDuration)
ExpireTime = DateTime.Now.AddMinutes(_settings.RefreshTokenDuration)
};
}
@ -277,5 +294,7 @@ public class RefreshTokenService : IRefreshTokenService
return Convert.ToBase64String(bytes);
}
#endregion
}

View File

@ -0,0 +1,75 @@

using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using OnlineSalesAutoCrop.CoreAPI.Models;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.BackgroupJobs;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.MobileApp;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.BackgroupJobs;
public class BackgroundJobService : IBackgroundJobService
{
private readonly IIntegrationHttpService _httpService;
private readonly IOrderService _orderService;
private readonly ILogger<BackgroundJobService> _logger;
public BackgroundJobService(IIntegrationHttpService httpService, IOrderService orderService, ILogger<BackgroundJobService> logger)
{
_httpService = httpService;
_orderService = orderService;
_logger = logger;
}
public async Task ExecuteSendingOrderAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("SendingOrder Job successfully run at: " + DateTime.Now.ToString());
try
{
var pendingOrderIds = await _orderService.GetAvailableOrderIdsForSAPAsync(SAPOrderStatusEnum.Pending);
foreach (var item in pendingOrderIds)
{
var orderDetails = await _orderService.GetOrderDetailsForSapByIdAsync(item);
_logger.LogInformation("SendingOrder for Order No with: " + orderDetails.PurchaseOrderByCustomer);
var httpResponse = await _httpService.SendSAPOrdersAsync(orderDetails);
if(string.IsNullOrEmpty( httpResponse.Item2))
{
await _orderService.UpdateSendingSapDetailsAsync(new UpdateSapDetailsRequest
{
OrderId = item,
SapOrderNo = httpResponse.Item2,
SentAt = DateTime.Now,
Status = SAPOrderStatusEnum.Error,
RetryCount = 1,
LastError = httpResponse.Item1
});
}
else
{
await _orderService.UpdateSendingSapDetailsAsync(new UpdateSapDetailsRequest
{
OrderId = item,
SapOrderNo = httpResponse.Item2,
SentAt = DateTime.Now,
Status = SAPOrderStatusEnum.Sent,
RetryCount = 1,
ReturnMessage = httpResponse.Item1
});
}
_logger.LogInformation("Order Successfully sent to SAP,with Order No: " + orderDetails.PurchaseOrderByCustomer);
}
}
catch(Exception ex)
{
_logger.LogError("Exception Occur in ExecuteSendingOrder "+ ex.Message);
throw;
}
_logger.LogInformation("SendingOrder Job successfully finished at: " + DateTime.Now.ToString());
}
}

View File

@ -0,0 +1,121 @@

using Microsoft.Extensions.Logging;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Integrations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.Integrations;
public class IntegrationHttpService : IIntegrationHttpService
{
private readonly HttpClient _httpClient;
private readonly ILogger<IntegrationHttpService> _logger;
public IntegrationHttpService(HttpClient httpClient, ILogger<IntegrationHttpService> logger)
{
_httpClient = httpClient;
_logger = logger;
}
public async Task<IntegrationCustLedgerHttpResponse> GetCustomerLedgerAsync(string customerNumber, string companyCode, CancellationToken cancellationToken = default)
{
try
{
var requestUri = $"http/get_customer_ledger?customer={Uri.EscapeDataString(customerNumber)}&company={Uri.EscapeDataString(companyCode)}";
var response = await _httpClient.GetAsync(requestUri, cancellationToken);
if (!response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync(cancellationToken);
_logger.LogError("Customer ledger call failed: {StatusCode} — {Body}", response.StatusCode, body);
response.EnsureSuccessStatusCode();
}
var payload= await response.Content.ReadFromJsonAsync< SapODataResponse<IntegrationCustLedgerHttpResponse>>(cancellationToken: cancellationToken);
return payload?.D.Results.FirstOrDefault();
}
catch(Exception)
{
throw;
}
}
public async Task<IList<IntegrationMaterialStockHttpResponse>> GetMaterialStockAsync(string plantCode, CancellationToken cancellationToken = default)
{
try
{
var requestUri = $"http/get_plantwise_available_stocks?plant={Uri.EscapeDataString(plantCode)}";
var response = await _httpClient.GetAsync(requestUri, cancellationToken);
if (!response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync(cancellationToken);
_logger.LogError("Material Stock call failed: {StatusCode} — {Body}", response.StatusCode, body);
response.EnsureSuccessStatusCode();
}
var payload = await response.Content.ReadFromJsonAsync<SapODataResponse<IntegrationMaterialStockHttpResponse>>(cancellationToken: cancellationToken);
return payload?.D.Results;
}
catch (Exception)
{
throw;
}
}
public async Task<Tuple<string,string>> SendSAPOrdersAsync(GetOrderForSAPResponse request, CancellationToken cancellationToken = default)
{
try
{
var requestUri = "http/sales_order";
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = null
};
var json = JsonSerializer.Serialize(request, options);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(requestUri, content, cancellationToken);
if (!response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync(cancellationToken);
_logger.LogError("Save Orders call failed: {StatusCode} — {Body}", response.StatusCode, body);
return new Tuple<string, string>(body, string.Empty);
}
var responseString = await response.Content.ReadAsStringAsync(cancellationToken);
using var document = JsonDocument.Parse(responseString);
var contentJson = document.RootElement
.GetProperty("content")
.GetRawText();
string salesOrder = document.RootElement
.GetProperty("content")
.GetProperty("properties")
.GetProperty("SalesOrder")
.GetString();
return new Tuple<string, string>(contentJson, salesOrder);
}
catch (Exception)
{
throw;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,15 @@
using Microsoft.Extensions.DependencyInjection;
using Hangfire;
using Microsoft.Extensions.DependencyInjection;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Auth;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.BackgroupJobs;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Setups;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems;
using OnlineSalesAutoCrop.CoreAPI.Services.Services.Auth;
using OnlineSalesAutoCrop.CoreAPI.Services.Services.BackgroupJobs;
using OnlineSalesAutoCrop.CoreAPI.Services.Services.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Services.Services.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Services.Services.Setups;
using OnlineSalesAutoCrop.CoreAPI.Services.Services.Systems;
@ -23,14 +28,16 @@ namespace OnlineSalesAutoCrop.CoreAPI.Configuration.DI
{
if (services == null)
return;
services.AddSingleton<IEaseCache, EaseCache>();
services.AddSingleton<IEaseCache, EaseCache>();
services.AddTransient<IUserService, UserService>();
services.AddTransient<IParamTypeService, ParamTypeService>();
services.AddTransient<IThisSystemService, ThisSystemService>();
services.AddTransient<IAuthModulesService, AuthModulesService>();
services.AddTransient<IRefreshTokenService, RefreshTokenService>();
services.AddScoped<IIntegrationService, IntegrationService>();
services.AddScoped<IMobileMasterDataService, MobileMasterDataService>();
services.AddScoped<IBackgroundJobService, BackgroundJobService>();
services.AddScoped<IOrderService, OrderService>();
}
}
}

View File

@ -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;
@ -230,7 +231,7 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers
loginReponse.AccessToken = userToken;
loginReponse.RefreshToken = refreshToken.RefreshToken;
loginReponse.AccessTokenExpiry = refreshToken.ExpireTime;
return StatusCode(StatusCodes.Status431RequestHeaderFieldsTooLarge, loginReponse);
return StatusCode(StatusCodes.Status200OK, loginReponse);
}
catch (Exception ex)
@ -295,7 +296,7 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers
response.AccessToken = userToken;
response.RefreshToken = refreshToken;
return StatusCode(StatusCodes.Status431RequestHeaderFieldsTooLarge, response);
return StatusCode(StatusCodes.Status200OK, response);
}
catch (Exception ex)
@ -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
);
}
/// <summary>
///
/// </summary>

View File

@ -0,0 +1,358 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using OnlineSalesAutoCrop.CoreAPI;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Integrations;
using System;
using System.Threading.Tasks;
namespace OnlineSalesAutoCrop.CoreAPI.Controllers
{
/// <summary>
///
/// </summary>
/// <remarks>
///
/// </remarks>
/// <param name="logger"></param>
/// <param name="integrationService"></param>
[Authorize]
[ApiController]
[ApiVersion("1.0")]
[ValidateAntiForgeryToken]
[Route("api/v{version:apiVersion}/Integration")]
public class IntegrationController(ILogger<IntegrationAuthController> logger, IIntegrationService integrationService) : ControllerBase
{
private readonly ILogger _logger = logger;
private readonly IIntegrationService _integrationService = integrationService;
/// <summary>
/// Insert or Update Customers for SAP
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <returns>if created return 201 or if updated return 200 true</returns>
[HttpPost("Customers")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IntegrationLoginResponse))]
public async Task<IActionResult> UpsertCustomers(IntegrationCustomerRequest request)
{
IntegrationCustomerResponse response = new IntegrationCustomerResponse();
try
{
response = await _integrationService.UpsertCustomerAsync(request);
if (!response.IsUpdated)
{
response.ReturnMessage.Add($"Customer Created Successfully for CustomerNumber :{request.CustomerNumber} & CompanyCode:{request.CompanyCode}");
response.ReturnStatus = StatusCodes.Status201Created;
return StatusCode(StatusCodes.Status201Created, response);
}
else
{
response.ReturnMessage.Add($"Customer Updated Successfully for CustomerNumber :{request.CustomerNumber} & CompanyCode:{request.CompanyCode}");
response.ReturnStatus = StatusCodes.Status200OK;
return StatusCode(StatusCodes.Status200OK, response);
}
}
catch (Exception ex)
{
string msg = $"Exception Occur in Customer Operation {request?.CustomerName}~{request?.CompanyCode}";
_logger.LogError(exception: ex, msg);
response.ReturnStatus = StatusCodes.Status500InternalServerError;
response.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, response);
}
}
/// <summary>
/// Insert or Update Employees for SAP
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <returns>if created return 201 or if updated return 200 true</returns>
[HttpPost("Employees")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(EmployeeIntegrationReqResponse))]
public async Task<IActionResult> UpsertEmployee(IntegrationEmployeeRequest request)
{
EmployeeIntegrationReqResponse response = new EmployeeIntegrationReqResponse();
try
{
response = await _integrationService.UpsertEmployeeAsync(request);
if (!response.IsUpdated)
{
response.ReturnMessage.Add($"Employee Created Successfully for EmployeeVendorName :{request.EmployeeVendorName} & SalesOrgCode:{request.CompanyCode}");
response.ReturnStatus = StatusCodes.Status201Created;
return StatusCode(StatusCodes.Status201Created, response);
}
else
{
response.ReturnMessage.Add($"Employee Updated Successfully for EmployeeVendorName :{request.EmployeeVendorName} & SalesOrgCode:{request.CompanyCode}");
response.ReturnStatus = StatusCodes.Status200OK;
return StatusCode(StatusCodes.Status200OK, response);
}
}
catch (Exception ex)
{
string msg = $"Exception Occur in Employee Operation {request?.EmployeeVendorName}~{request?.CompanyCode}";
_logger.LogError(exception: ex, msg);
response.ReturnStatus = StatusCodes.Status500InternalServerError;
response.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, response);
}
}
/// <summary>
/// Insert or Update Material for SAP
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <returns>if created return 201 or if updated return 200 true</returns>
[HttpPost("Materials")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(MaterialIntegrationReqResponse))]
[ProducesResponseType(StatusCodes.Status201Created, Type = typeof(MaterialIntegrationReqResponse))]
public async Task<IActionResult> UpsertMeterial(IntegrationMaterialRequest request)
{
MaterialIntegrationReqResponse response = new MaterialIntegrationReqResponse();
try
{
response = await _integrationService.UpsertMaterialAsync(request);
if (!response.IsUpdated)
{
response.ReturnMessage.Add($"Material Created Successfully for MaterialCode :{request.MaterialCode} ");
response.ReturnStatus = StatusCodes.Status201Created;
return StatusCode(StatusCodes.Status201Created, response);
}
else
{
response.ReturnMessage.Add($"Material Updated Successfully for MaterialCode :{request.MaterialCode} ");
response.ReturnStatus = StatusCodes.Status200OK;
return StatusCode(StatusCodes.Status200OK, response);
}
}
catch (Exception ex)
{
string msg = $"Exception Occur in Material Operation {request?.MaterialCode}";
_logger.LogError(exception: ex, msg);
response.ReturnStatus = StatusCodes.Status500InternalServerError;
response.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, response);
}
}
/// <summary>
/// Insert or Update Material Prices for SAP
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <returns>if created return 201 or if updated return 200 true</returns>
[HttpPost("MaterialPrices")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(MaterialIntegrationPriceReqResponse))]
[ProducesResponseType(StatusCodes.Status201Created, Type = typeof(MaterialIntegrationPriceReqResponse))]
public async Task<IActionResult> UpsertMeterialPrices(IntegrationMaterialPriceRequest request)
{
MaterialIntegrationPriceReqResponse response = new MaterialIntegrationPriceReqResponse();
try
{
response = await _integrationService.UpsertMaterialPriceAsync(request);
if (!response.IsUpdated)
{
response.ReturnMessage.Add($"Material Price Created Successfully for MaterialCode :{request.MaterialCode} And ConditionType : {request.ConditionType}" +
$"And Customer Number : {request.CustomerNumber} And Customer Group Code : {request.CustomerGroupCode} ");
response.ReturnStatus = StatusCodes.Status201Created;
return StatusCode(StatusCodes.Status201Created, response);
}
else
{
response.ReturnMessage.Add($"Material Price Updated Successfully for MaterialCode :{request.MaterialCode} And ConditionType : {request.ConditionType}" +
$"And Customer Number : {request.CustomerNumber} And Customer Group Code : {request.CustomerGroupCode} ");
response.ReturnStatus = StatusCodes.Status200OK;
return StatusCode(StatusCodes.Status200OK, response);
}
}
catch (Exception ex)
{
string msg = $"Exception Occur in Material Price Operation MaterialCode :{request.MaterialCode} And ConditionType : {request.ConditionType}" +
$"And Customer Number : {request.CustomerNumber} And Customer Group Code : {request.CustomerGroupCode} ";
_logger.LogError(exception: ex, msg);
response.ReturnStatus = StatusCodes.Status500InternalServerError;
response.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, response);
}
}
/// <summary>
/// Insert or Update Material Prices for SAP
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <returns>if created return 201 or if updated return 200 true</returns>
[HttpPost("MaterialFreeGoods")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(MaterialFreeGoodsByReqResponse))]
[ProducesResponseType(StatusCodes.Status201Created, Type = typeof(MaterialFreeGoodsByReqResponse))]
public async Task<IActionResult> UpsertMaterialFreeGoods(IntegrationMaterialFreeGoodsRequest request)
{
MaterialFreeGoodsByReqResponse response = new MaterialFreeGoodsByReqResponse();
try
{
if(request.Slabs == null || request.Slabs?.Count==0)
{
response.ReturnMessage.Add($"At least one slab must be sent.");
response.ReturnStatus = StatusCodes.Status400BadRequest;
return StatusCode(StatusCodes.Status400BadRequest, response);
}
response = await _integrationService.UpsertMaterialFreeGoodsAsync(request);
if (!response.IsUpdated)
{
response.ReturnMessage.Add($"Material Free Goods Created Successfully for SalesOrg :{request.SalesOrg} " +
$"And CustomerPriceGroup : {request.CustomerPriceGroup} And MaterialCode : {request.MaterialCode} And DistributorChannel : {request.DistributionChannel}" +
$" And Customer Number :{request.CustomerNumber} And Customer Price Group : {request.CustomerPriceGroup}");
response.ReturnStatus = StatusCodes.Status201Created;
return StatusCode(StatusCodes.Status201Created, response);
}
else
{
response.ReturnMessage.Add($"Material Free Goods Updated Successfully for SalesOrg :{request.SalesOrg} " +
$"And CustomerPriceGroup : {request.CustomerPriceGroup} And MaterialCode : {request.MaterialCode} And DistributorChannel : {request.DistributionChannel}" +
$" And Customer Number :{request.CustomerNumber} And Customer Price Group : {request.CustomerPriceGroup}");
response.ReturnStatus = StatusCodes.Status200OK;
return StatusCode(StatusCodes.Status200OK, response);
}
}
catch (Exception ex)
{
string msg = $"Exception Occur in Material Free Goods Operation SalesOrg :{request.SalesOrg} " +
$" And CustomerPriceGroup : {request.CustomerPriceGroup} And MaterialCode : {request.MaterialCode} And DistributorChannel : {request.DistributionChannel}" +
$" And Customer Number :{request.CustomerNumber} And Customer Price Group : {request.CustomerPriceGroup}";
_logger.LogError(exception: ex, msg);
response.ReturnStatus = StatusCodes.Status500InternalServerError;
response.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, response);
}
}
/// <summary>
/// Insert or Update Payment Terms for SAP
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <returns>if created return 201 or if updated return 200 true</returns>
[HttpPost("PaymentTerms")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IntegrationPaymentTermReqResponse))]
[ProducesResponseType(StatusCodes.Status201Created, Type = typeof(IntegrationPaymentTermReqResponse))]
public async Task<IActionResult> UpsertPaymentTerms(IntegrationPaymentTermRequest request)
{
IntegrationPaymentTermReqResponse response = new IntegrationPaymentTermReqResponse();
try
{
response = await _integrationService.UpsertPaymentTermsAsync(request);
if (!response.IsUpdated)
{
response.ReturnMessage.Add($"Payment Terms Created Successfully for SalesOrg :{request.SalesOrg} " +
$"And CreditCode : {request.CreditCode} ");
response.ReturnStatus = StatusCodes.Status201Created;
return StatusCode(StatusCodes.Status201Created, response);
}
else
{
response.ReturnMessage.Add($"Payment Terms Updated Successfully for SalesOrg :{request.SalesOrg} " +
$"And CreditCode : {request.CreditCode} ");
response.ReturnStatus = StatusCodes.Status200OK;
return StatusCode(StatusCodes.Status200OK, response);
}
}
catch (Exception ex)
{
string msg = $"Exception Occur in Payment Terms for SalesOrg :{request.SalesOrg} " +
$"And CreditCode : {request.CreditCode} ";
_logger.LogError(exception: ex, msg);
response.ReturnStatus = StatusCodes.Status500InternalServerError;
response.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, response);
}
}
/// <summary>
/// Insert Deliveries for SAP
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <returns>if created return 201 or if updated return 200 true</returns>
[HttpPost("Deliveries")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IntegrationDeliveryReqResponse))]
[ProducesResponseType(StatusCodes.Status201Created, Type = typeof(IntegrationDeliveryReqResponse))]
public async Task<IActionResult> SaveDeliveries(IntegrationDeliveryDocumentRequest request)
{
IntegrationDeliveryReqResponse response = new IntegrationDeliveryReqResponse();
try
{
response = await _integrationService.SaveDeliveryInvoiceDocumentAsync(request);
response.ReturnMessage.Add($"Successfully Save the Deliveries for Delivery Documnet No :{request.DeliveryDocumentNo} " +
$"And DeliveryDate : {request.DeliveryDate} ");
response.ReturnStatus = StatusCodes.Status201Created;
return StatusCode(StatusCodes.Status201Created, response);
}
catch (Exception ex)
{
string msg = $"Exception Occur in Deliveries for Delivery Documnet No :{request.DeliveryDocumentNo} " +
$"And DeliveryDate : {request.DeliveryDate} " ;
_logger.LogError(exception: ex, msg);
response.ReturnStatus = StatusCodes.Status500InternalServerError;
response.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, response);
}
}
/// <summary>
/// Insert Deliveries for SAP
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <returns>if created return 201 or if updated return 200 true</returns>
[HttpPost("Billing")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IntegrationBillingReqResponse))]
[ProducesResponseType(StatusCodes.Status201Created, Type = typeof(IntegrationBillingReqResponse))]
public async Task<IActionResult> SaveBilling(IntegrationBillingDocumentRequest request)
{
IntegrationBillingReqResponse response = new IntegrationBillingReqResponse();
try
{
response = await _integrationService.SaveBillingDocumentAsync(request);
response.ReturnMessage.Add($"Successfully Save the Billing for Billing Documnet No :{request.BillingDocumentNumber} " +
$"And BillingDate : {request.BillingDate} ");
response.ReturnStatus = StatusCodes.Status201Created;
return StatusCode(StatusCodes.Status201Created, response);
}
catch (Exception ex)
{
string msg = $"Exception Occur in Billing for Billing Documnet No :{request.BillingDocumentNumber} " +
$"And BillingDate : {request.BillingDate} ";
_logger.LogError(exception: ex, msg);
response.ReturnStatus = StatusCodes.Status500InternalServerError;
response.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, response);
}
}
}
}

View File

@ -1,153 +0,0 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Integrations;
using System;
using System.Threading.Tasks;
namespace OnlineSalesAutoCrop.CoreAPI.Controllers.IntegretionApi
{
/// <summary>
///
/// </summary>
/// <remarks>
///
/// </remarks>
/// <param name="logger"></param>
/// <param name="integrationService"></param>
[Authorize]
[ApiController]
[ApiVersion("1.0")]
[ValidateAntiForgeryToken]
[Route("api/v{version:apiVersion}/Integration")]
public class IntegrationController(ILogger<IntegrationAuthController> logger, IIntegrationService integrationService) : ControllerBase
{
private readonly ILogger _logger = logger;
private readonly IIntegrationService _integrationService = integrationService;
/// <summary>
/// Insert or Update Customers for SAP
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <returns>if created return 201 or if updated return 200 true</returns>
[HttpPost("Customers")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IntegrationLoginResponse))]
public async Task<IActionResult> UpsertCustomers(CustomerIntegrationRequest request)
{
CustomerIntegrationResponse response = new CustomerIntegrationResponse();
try
{
response = await _integrationService.UpsertCustomerAsync(request);
if (!response.IsUpdated)
{
response.ReturnMessage.Add($"Customer Created Successfully for CustomerNumber :{request.CustomerNumber} & CompanyCode:{request.CompanyCode}");
response.ReturnStatus = StatusCodes.Status201Created;
return StatusCode(StatusCodes.Status201Created, response);
}
else
{
response.ReturnMessage.Add($"Customer Updated Successfully for CustomerNumber :{request.CustomerNumber} & CompanyCode:{request.CompanyCode}");
response.ReturnStatus = StatusCodes.Status200OK;
return StatusCode(StatusCodes.Status200OK, response);
}
}
catch (Exception ex)
{
string msg = $"Exception Occur in Customer Operation {request?.CustomerName}~{request?.CompanyCode}";
_logger.LogError(exception: ex, msg);
response.ReturnStatus = StatusCodes.Status500InternalServerError;
response.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, response);
}
}
/// <summary>
/// Insert or Update Employees for SAP
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <returns>if created return 201 or if updated return 200 true</returns>
[HttpPost("Employees")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(EmployeeIntegrationReqResponse))]
public async Task<IActionResult> UpsertEmployee(EmployeeIntegrationRequest request)
{
EmployeeIntegrationReqResponse response = new EmployeeIntegrationReqResponse();
try
{
response = await _integrationService.UpsertEmployeeAsync(request);
if (!response.IsUpdated)
{
response.ReturnMessage.Add($"Employee Created Successfully for EmployeeVendorName :{request.EmployeeVendorName} & SalesOrgCode:{request.CompanyCode}");
response.ReturnStatus = StatusCodes.Status201Created;
return StatusCode(StatusCodes.Status201Created, response);
}
else
{
response.ReturnMessage.Add($"Employee Updated Successfully for EmployeeVendorName :{request.EmployeeVendorName} & SalesOrgCode:{request.CompanyCode}");
response.ReturnStatus = StatusCodes.Status200OK;
return StatusCode(StatusCodes.Status200OK, response);
}
}
catch (Exception ex)
{
string msg = $"Exception Occur in Employee Operation {request?.EmployeeVendorName}~{request?.CompanyCode}";
_logger.LogError(exception: ex, msg);
response.ReturnStatus = StatusCodes.Status500InternalServerError;
response.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, response);
}
}
/// <summary>
/// Insert or Update Material for SAP
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <returns>if created return 201 or if updated return 200 true</returns>
[HttpPost("Materials")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(MaterialIntegrationReqResponse))]
[ProducesResponseType(StatusCodes.Status201Created, Type = typeof(MaterialIntegrationReqResponse))]
public async Task<IActionResult> UpsertMeterial(MaterialIntegrationRequest request)
{
MaterialIntegrationReqResponse response = new MaterialIntegrationReqResponse();
try
{
response = await _integrationService.UpsertMaterialAsync(request);
if (!response.IsUpdated)
{
response.ReturnMessage.Add($"Material Created Successfully for MaterialCode :{request.MaterialCode} ");
response.ReturnStatus = StatusCodes.Status201Created;
return StatusCode(StatusCodes.Status201Created, response);
}
else
{
response.ReturnMessage.Add($"Material Updated Successfully for MaterialCode :{request.MaterialCode} ");
response.ReturnStatus = StatusCodes.Status200OK;
return StatusCode(StatusCodes.Status200OK, response);
}
}
catch (Exception ex)
{
string msg = $"Exception Occur in Material Operation {request?.MaterialCode}";
_logger.LogError(exception: ex, msg);
response.ReturnStatus = StatusCodes.Status500InternalServerError;
response.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, response);
}
}
}
}

View File

@ -0,0 +1,354 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using OnlineSalesAutoCrop.CoreAPI;
using OnlineSalesAutoCrop.CoreAPI.Models;
using OnlineSalesAutoCrop.CoreAPI.Models.Global;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Systems;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Auth;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace OnlineSalesAutoCrop.CoreAPI.Controllers
{
/// <summary>
///
/// </summary>
/// <remarks>
///
/// </remarks>
/// <param name="service"></param>
/// <param name="appSettings"></param>
/// <param name="cache"></param>
/// <param name="logger"></param>
/// <param name="refreshTokenService"></param>
/// <param name="masterService"></param>
[Authorize]
[ApiController]
[ApiVersion("1.0")]
[ValidateAntiForgeryToken]
[Route("api/mobile/v{version:apiVersion}/auth")]
public class MobileAuthController(IUserService service, IOptions<AppSettings> appSettings, IEaseCache cache, ILogger<MobileAuthController> logger,
IRefreshTokenService refreshTokenService, IMobileMasterDataService masterService) : ControllerBase
{
private readonly ILogger _logger = logger;
private readonly IEaseCache _cache = cache;
private readonly IUserService _service = service;
private readonly IRefreshTokenService _refreshTokenService = refreshTokenService;
private readonly AppSettings _appSettings = appSettings?.Value;
private readonly DateTimeOffset _options = Helper.CreateEaseCacheOptions();
private readonly IMobileMasterDataService _masterService = masterService;
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <returns>If login successful ValidUser: true</returns>
/// <response code="200">If login successful Return ValidUser: true and UserName: not empty</response>
[HttpPost("Login")]
[AllowAnonymous]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(MobileAppAuthRequest))]
public async Task<IActionResult> Login([FromBody] MobileAppAuthRequest request)
{
ArgumentNullException.ThrowIfNull(request);
AppAuthUserResponse loginReponse = new();
if (string.IsNullOrEmpty(request.LoginId))
{
string message = "Login ID is required.";
return BadRequest(MobileResponseBase<AppAuthUserResponse>.Failure(message, StatusCodes.Status400BadRequest));
}
if (string.IsNullOrEmpty(request.Password))
{
string message = "Password is required.";
return BadRequest(MobileResponseBase<AppAuthUserResponse>.Failure(message, StatusCodes.Status400BadRequest));
}
string ipAddress = string.Empty;
try
{
#region Decrypt LoginID
string cipherSecretKey = GlobalFunctions.ConvertFromBase64String(_appSettings.CipherSecretKey);
#endregion
bool checkPwd = true;
ipAddress = Request.HttpContext.GetIpAddress();
loginReponse = await _service.LoginAsync(request: request, ipAddress: ipAddress);
if (loginReponse.LoginStatus == EnumLoginStatus.Unsuccessful)
{
string message = checkPwd ? $"Password is invalid for {request.LoginId}." : "You are not Authorized to login into the System";
return Unauthorized(MobileResponseBase<AppAuthUserResponse>.Failure(message, StatusCodes.Status401Unauthorized));
}
if (loginReponse == null || loginReponse.UserId == 0 || loginReponse.IsAuthorized ==false)
{
string message = "Login ID is invalid.\" : \"You are not Authorized to login into the System.";
return Unauthorized(MobileResponseBase<AppAuthUserResponse>.Failure(message, StatusCodes.Status401Unauthorized));
}
if (loginReponse.IsLocked)
{
string message =$"Your Account is locked . Please try again after few minutes";
return Unauthorized(MobileResponseBase<AppAuthUserResponse>.Failure(message, StatusCodes.Status401Unauthorized));
}
if (!loginReponse.IsAuthorized )
{
string message = $"You are not Authorized to Login into the System, Please contact with System Administrator.";
return Unauthorized(MobileResponseBase<AppAuthUserResponse>.Failure(message, StatusCodes.Status401Unauthorized));
}
string pwdSecretKey = GlobalFunctions.ConvertFromBase64String(_appSettings.PwdSecretKey);
string userPwd = Ease.NetCore.Utility.Global.CipherFunctions.EncryptByAES(privateKey: pwdSecretKey, publicKey: pwdSecretKey, data: request.Password);
byte[] key = Encoding.ASCII.GetBytes(_appSettings.JwtCryptoKey);
JwtSecurityTokenHandler tokenHandler = new();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(
[
Helper.CreateClaim("LoginId", loginReponse.LoginId),
Helper.CreateClaim("Email", loginReponse.Email),
Helper.CreateClaim("UserId", $"{loginReponse.UserId}"),
Helper.CreateClaim("HashKey", Guid.NewGuid().ToString())
]),
Expires = DateTime.UtcNow.AddMinutes(_appSettings.AccessTokenDuration),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
};
SecurityToken token = tokenHandler.CreateToken(tokenDescriptor);
string userToken = tokenHandler.WriteToken(token);
GenerateRefreshTokenRequest refreshTokenRequest = new GenerateRefreshTokenRequest()
{
UserId = loginReponse.UserId,
IpAddress = ipAddress,
RawRefreshToken = string.Empty
};
var refreshToken = await _refreshTokenService.GenerateRefreshTokenByUserAsync(refreshTokenRequest);
//If token length is greater than or equal to 4096 (4KB) then return error
//because cookie can not store more than 4KB data and we are storing this token in cookie for authentication
if (userToken.Length >= 4096) //4Kb
{
string message = $"Authentication Token is too large for cookie.";
return StatusCode(StatusCodes.Status431RequestHeaderFieldsTooLarge, MobileResponseBase<AppAuthUserResponse>.Failure(message, StatusCodes.Status431RequestHeaderFieldsTooLarge));
}
loginReponse.LoginId = loginReponse.LoginId;
loginReponse.AccessToken = userToken;
loginReponse.RefreshToken = refreshToken.RefreshToken;
loginReponse.AccessTokenExpiryTime = DateTime.Now.AddMinutes(_appSettings.AccessTokenDuration);
loginReponse.RefreshTokenExpiryTime = refreshToken.ExpireTime;
return Ok(MobileResponseBase<AppAuthUserResponse>.Success(loginReponse));
}
catch (Exception ex)
{
string msg = $"{request?.LoginId}~{ipAddress}";
_logger.LogError(exception: ex, message: msg);
string mesgae= ex.InnerException != null ? ex.InnerException.Message : ex.Message;
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(mesgae, StatusCodes.Status500InternalServerError));
}
}
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <returns>If login successful ValidUser: true</returns>
/// <response code="200">If login successful Return ValidUser: true and UserName: not empty</response>
[HttpPost("RefreshToken")]
[AllowAnonymous]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(MobileRrefreshTokenResponse))]
public async Task<IActionResult> GetRefreshToken([FromBody] IntegrationRefreshTokenRequest request)
{
MobileRrefreshTokenResponse response = new();
try
{
var userRefreshToken = await _refreshTokenService.GetUserByRefreshTokenAsync(request.RefreshToken);
if (!userRefreshToken.IsActive)
{
string msg = $"Refresh Token is not active: {request?.RefreshToken}";
_logger.LogError(message: msg);
return Unauthorized( MobileResponseBase<AppAuthUserResponse>.Failure(msg, StatusCodes.Status401Unauthorized));
}
byte[] key = Encoding.ASCII.GetBytes(_appSettings.JwtCryptoKey);
JwtSecurityTokenHandler tokenHandler = new();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(
[
Helper.CreateClaim("LoginId", userRefreshToken.LoginId),
Helper.CreateClaim("Email", userRefreshToken.EmailAddress),
Helper.CreateClaim("UserId", $"{userRefreshToken.UserId}"),
Helper.CreateClaim("HashKey", Guid.NewGuid().ToString())
]),
Expires = DateTime.UtcNow.AddMinutes(_appSettings.AccessTokenDuration),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
};
SecurityToken token = tokenHandler.CreateToken(tokenDescriptor);
string userToken = tokenHandler.WriteToken(token);
string ipAddress = Request.HttpContext.GetIpAddress();
var refreshToken = await _refreshTokenService.GenerateRefreshTokenAsync(request.RefreshToken, ipAddress);
response.AccessToken = userToken;
response.RefreshToken = refreshToken;
return Ok(MobileResponseBase<MobileRrefreshTokenResponse>.Success(response));
}
catch (Exception ex)
{
string msg = $"{request?.RefreshToken}";
_logger.LogError(exception: ex, message: msg);
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
}
}
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <returns>If login successful ValidUser: true</returns>
/// <response code="200">If login successful Return ValidUser: true and UserName: not empty</response>
[HttpPost("Logout")]
[Authorize]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AuthLogoutResponse))]
public async Task<IActionResult> Logout([FromBody] AuthLogoutRequest request)
{
AuthLogoutResponse response = new();
try
{
string ipAddress = Request.HttpContext.GetIpAddress();
response = await _service.LogoutAsync(request, ipAddress);
return Ok(MobileResponseBase<AuthLogoutResponse>.Success(response, "Successfully Logout"));
}
catch (Exception ex)
{
string msg = $"Exception occur on logout {request?.LoginId}";
_logger.LogError(exception: ex, message: msg);
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
}
}
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <returns>If login successful ValidUser: true</returns>
/// <response code="200">If login successful Return ValidUser: true and UserName: not empty</response>
[HttpPost("ChangePassword")]
[Authorize]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AppChangePasswordResponse))]
public async Task<IActionResult> ChangePassword([FromBody] AppUserChangePasswordRequest request)
{
AppChangePasswordResponse response = new();
try
{
if (request.Password.Equals(request.ConfirmPassword))
{
string msg = $"You can't use your old password as your new password.";
_logger.LogError(message: msg);
return BadRequest(MobileResponseBase<AppAuthUserResponse>.Failure(msg, StatusCodes.Status400BadRequest));
}
GetPasswordPolicyResponse passwordPolicy = await _masterService.GetPasswordPolicyAsync();
string passValidationResponse = ValidatePassword(request.ConfirmPassword, passwordPolicy);
if(!string.IsNullOrEmpty(passValidationResponse))
{
string msg = passValidationResponse;
_logger.LogError(message: msg);
return BadRequest(MobileResponseBase<AppAuthUserResponse>.Failure(msg, StatusCodes.Status400BadRequest));
}
string ipAddress = Request.HttpContext.GetIpAddress();
response = await _service.ChangePasswordAsync(request, ipAddress);
return Ok(MobileResponseBase<AppChangePasswordResponse>.Success(response, "Successfully Changed the Password"));
}
catch (Exception ex)
{
string msg = $"Exception occur on logout {request?.LoginId}";
_logger.LogError(exception: ex, message: msg);
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
}
}
private string ValidatePassword(string password, GetPasswordPolicyResponse policy)
{
if (policy == null)
throw new ArgumentNullException(nameof(policy));
if (string.IsNullOrEmpty(password))
{
return "Password cannot be empty.";
}
if (policy.MinLength.HasValue && password.Length < policy.MinLength.Value)
return $"Password must be at least {policy.MinLength.Value} characters long.";
if (policy.MaxLength.HasValue && password.Length > policy.MaxLength.Value)
return $"Password must not exceed {policy.MaxLength.Value} characters.";
if (policy.IsUppercase && !password.Any(char.IsUpper))
return "Password must contain at least one uppercase letter.";
if (policy.IsLowercase && !password.Any(char.IsLower))
return "Password must contain at least one lowercase letter.";
if (policy.IsNumber && !password.Any(char.IsDigit))
return "Password must contain at least one digit.";
if (policy.IsSpecialCharacter && !password.Any(c => !char.IsLetterOrDigit(c)))
return "Password must contain at least one special character.";
return string.Empty;
}
}
}

View File

@ -0,0 +1,415 @@
using Asp.Versioning;
using DocumentFormat.OpenXml.Office2010.ExcelAc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OnlineSalesAutoCrop.CoreAPI;
using OnlineSalesAutoCrop.CoreAPI.Models.Global;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.MobileApp;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace OnlineSalesAutoCrop.CoreAPI.Controllers
{
/// <summary>
///
/// </summary>
/// <remarks>
///
/// </remarks>
/// <param name="service"></param>
/// <param name="appSettings"></param>
/// <param name="cache"></param>
/// <param name="logger"></param>
[Authorize]
[ApiController]
[ApiVersion("1.0")]
[ValidateAntiForgeryToken]
[Route("api/mobile/v{version:apiVersion}/masterdata")]
public class MobileMasterDataController(IMobileMasterDataService service, IOptions<AppSettings> appSettings, IEaseCache cache, ILogger<MobileMasterDataController> logger) : ControllerBase
{
private readonly ILogger _logger = logger;
private readonly IEaseCache _cache = cache;
private readonly IMobileMasterDataService _service = service;
private readonly AppSettings _appSettings = appSettings?.Value;
private readonly DateTimeOffset _options = Helper.CreateEaseCacheOptions();
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <response code="200">If login successful Return MarketHierarchies List</response>
[HttpGet("MarketHierarchies")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(MarketHeirarchyByEmpResponse))]
public async Task<IActionResult> EmployeeMarketHierarchies([FromQuery] GetMarketHeirarchyByEmpRequest request)
{
MarketHeirarchyByEmpResponse response = new();
try
{
response = await _service.GetMarketHeirarchiesByEmpAsync(request);
return Ok(MobileResponseBase<MarketHeirarchyByEmpResponse>.Success(response));
}
catch (Exception ex)
{
string msg = $"Exception occur on MarketHeirarchys endpoint with employee request - {request?.EmployeeNumber}";
_logger.LogError(exception: ex, message: msg);
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
}
}
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <response code="200">If login successful Return Brands List</response>
[HttpGet("Brands")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(MarketHeirarchyByEmpResponse))]
public async Task<IActionResult> Brands([FromQuery] GetBrandsRequest request)
{
PagedResult<GetBrandResponse> response = new();
try
{
response = await _service.GetBrandsAsync(request);
return Ok(MobileResponseBase<PagedResult<GetBrandResponse>>.Success(response));
}
catch (Exception ex)
{
string msg = $"Exception occur on brands endpoint with employee request - {request?.BrandCode}";
_logger.LogError(exception: ex, message: msg);
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
}
}
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <response code="200">If login successful Return Material List</response>
[HttpGet("Materials")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(GetMaterialResponse))]
public async Task<IActionResult> Materials([FromQuery] GetMaterialsRequest request)
{
PagedResult<GetMaterialResponse> response = new();
try
{
response = await _service.GetMaterialsAsync(request);
return Ok(MobileResponseBase<PagedResult<GetMaterialResponse>>.Success(response));
}
catch (Exception ex)
{
string msg = $"Exception occur on Materials endpoint with employee request - {request?.BrandCode} -{request?.MaterialCode}";
_logger.LogError(exception: ex, message: msg);
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
}
}
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <response code="200">If login successful Return Material List</response>
[HttpGet("MaterialsStock")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(GetMaterialStockReponse))]
public async Task<IActionResult> GetMaterialsStock([FromQuery] GetMaterialStockRequest request)
{
PagedResult<GetMaterialStockReponse> response = new();
try
{
response = await _service.GetMaterialsStockAsync(request);
return Ok(MobileResponseBase<PagedResult<GetMaterialStockReponse>>.Success(response));
}
catch (Exception ex)
{
string msg = $"Exception occur on MaterialsStock endpoint with plant request - {request?.PlantCode} ";
_logger.LogError(exception: ex, message: msg);
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
}
}
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <response code="200">If login successful Return Material List</response>
[HttpGet("CustomerWiseMaterialPrices")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(GetMaterialPriceResponse))]
public async Task<IActionResult> GetCustomerWiseMaterialPrices([FromQuery] GetMaterialPriceRequest request)
{
PagedResult<GetMaterialPriceResponse> response = new();
try
{
response = await _service.GetMaterialPricesAsync(request);
return Ok(MobileResponseBase<PagedResult<GetMaterialPriceResponse>>.Success(response));
}
catch (Exception ex)
{
string msg = $"Exception occur on Materials prices endpoint with employee request - {request?.MaterialCode} -{request?.EmployeeNumber}";
_logger.LogError(exception: ex, message: msg);
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
}
}
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <response code="200">If login successful Return Material List</response>
[HttpGet("Customers")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(GetCustomerResponse))]
public async Task<IActionResult> Customers([FromQuery] GetCustomersRequest request)
{
PagedResult<GetCustomerResponse> response = new();
try
{
response = await _service.GetCustomersAsync(request);
return Ok(MobileResponseBase<PagedResult<GetCustomerResponse>>.Success(response));
}
catch (Exception ex)
{
string msg = $"Exception occur on Materials endpoint with employee request - {request?.TeritoryCode} -{request?.CustomerCode}";
_logger.LogError(exception: ex, message: msg);
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
}
}
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <response code="200">If login successful Return Attendance List</response>
[HttpGet("Attendance")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(GetAttendanceByEmpResponse))]
public async Task<IActionResult> GetAttendance([FromQuery] GetAttendanceByEmpRequest request)
{
GetAttendanceByEmpResponse response = new();
try
{
response = await _service.GetAttendanceByEmpAsync(request);
return Ok(MobileResponseBase<GetAttendanceByEmpResponse>.Success(response));
}
catch (Exception ex)
{
string msg = $"Exception occur on Get Attendance endpoint with employee request - {request?.EmployeeNumber} -{request?.AttendanceDate}";
_logger.LogError(exception: ex, message: msg);
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
}
}
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <response code="200">If login successful Return Attendance List</response>
[HttpPost("SaveAttendance")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(GetAttendanceByEmpResponse))]
public async Task<IActionResult> UpsertAttendance([FromBody] UpsertAttendanceByEmpRequest request)
{
GetAttendanceByEmpResponse response = new();
try
{
response = await _service.UpsertAttendanceByEmpAsync(request);
return Ok(MobileResponseBase<GetAttendanceByEmpResponse>.Success(response));
}
catch (Exception ex)
{
string msg = $"Exception occur on Save Attendance endpoint with employee request - {request?.EmployeeNumber}";
_logger.LogError(exception: ex, message: msg);
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
}
}
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <response code="200">If login successful Return Attendance List</response>
[HttpGet("Discounts")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(GetDiscountResponse))]
public async Task<IActionResult> GetDiscounts([FromQuery] GetDiscountRequest request)
{
PagedResult<GetDiscountResponse> response = new();
try
{
response = await _service.GetDiscountAsync(request);
return Ok(MobileResponseBase<PagedResult<GetDiscountResponse>>.Success(response));
}
catch (Exception ex)
{
string msg = $"Exception occur on Get Discounts endpoint with request - {request?.EmployeeNumber} -{request?.DiscountCode}";
_logger.LogError(exception: ex, message: msg);
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
}
}
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <response code="200">If login successful Return Attendance List</response>
[HttpGet("DiscountMaterials")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(GetDiscountMaterialResponse))]
public async Task<IActionResult> GetDiscountMaterials([FromQuery] GetDiscountMaterialRequest request)
{
PagedResult<GetDiscountMaterialResponse> response = new();
try
{
response = await _service.GetDiscountMaterialsAsync(request);
return Ok(MobileResponseBase<PagedResult<GetDiscountMaterialResponse>>.Success(response));
}
catch (Exception ex)
{
string msg = $"Exception occur on Get DiscountMaterials endpoint with request - {request?.MaterialCode} -{request?.DiscountCode}";
_logger.LogError(exception: ex, message: msg);
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
}
}
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <response code="200">If login successful Return Attendance List</response>
[HttpGet("FOCs")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(GetFOCResponse))]
public async Task<IActionResult> GetMaterialFOCs([FromQuery] GetFOCRequest request)
{
PagedResult<GetFOCResponse> response = new();
try
{
response = await _service.GetFOCsAsync(request);
return Ok(MobileResponseBase<PagedResult<GetFOCResponse>>.Success(response));
}
catch (Exception ex)
{
string msg = $"Exception occur on Get MaterialFOCs endpoint with request - {request?.EmployeeNumber} -{request?.FOCCode}";
_logger.LogError(exception: ex, message: msg);
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
}
}
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <response code="200">If login successful Return Attendance List</response>
[HttpGet("FOCsMaterials")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(GetFOCMaterialResponse))]
public async Task<IActionResult> GetFOCsMaterials([FromQuery] GetFOCMaterialRequest request)
{
PagedResult<GetFOCMaterialResponse> response = new();
try
{
response = await _service.GetFOCMaterialsAsync(request);
return Ok(MobileResponseBase<PagedResult<GetFOCMaterialResponse>>.Success(response));
}
catch (Exception ex)
{
string msg = $"Exception occur on Get FOCsMaterials endpoint with request - {request?.MaterialCode} -{request?.FOCCode}";
_logger.LogError(exception: ex, message: msg);
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
}
}
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <response code="200">If login successful Return Attendance List</response>
[HttpGet("PaymentTerms")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(GetPaymentTermResponse))]
public async Task<IActionResult> GetPaymentTerms([FromQuery] GetPaymentTermRequest request)
{
PagedResult<GetPaymentTermResponse> response = new();
try
{
response = await _service.GetPaymentTermsAsync(request);
return Ok(MobileResponseBase<PagedResult<GetPaymentTermResponse>>.Success(response));
}
catch (Exception ex)
{
string msg = $"Exception occur on Get PaymentTerms endpoint with request - {request?.SalesOrgCode} -{request?.PaymentTermCode}";
_logger.LogError(exception: ex, message: msg);
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
}
}
/// <summary>
/// For PasswordPolicy
/// </summary>
/// <remarks>
/// </remarks>
/// <response code="200">If login successful Return Attendance List</response>
[HttpGet("PasswordPolicy")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(GetPasswordPolicyResponse))]
public async Task<IActionResult> GetPasswordPolicy()
{
GetPasswordPolicyResponse response = new();
try
{
response = await _service.GetPasswordPolicyAsync();
return Ok(MobileResponseBase<GetPasswordPolicyResponse>.Success(response));
}
catch (Exception ex)
{
string msg = $"Exception occur on Get PasswordPolicy endpoint ";
_logger.LogError(exception: ex, message: msg);
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
}
}
}
}

View File

@ -0,0 +1,98 @@
using Asp.Versioning;
using Google.Api;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OnlineSalesAutoCrop.CoreAPI.Models;
using OnlineSalesAutoCrop.CoreAPI.Models.Global;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.MobileApp;
using System;
using System.Threading.Tasks;
namespace OnlineSalesAutoCrop.CoreAPI.Controllers.Mobile;
/// <summary>
///
/// </summary>
/// <remarks>
///
/// </remarks>
/// <param name="service"></param>
/// <param name="appSettings"></param>
/// <param name="cache"></param>
/// <param name="logger"></param>
[Authorize]
[ApiController]
[ApiVersion("1.0")]
[ValidateAntiForgeryToken]
[Route("api/mobile/v{version:apiVersion}/orders")]
public class OrdersController(IOrderService service, IOptions<AppSettings> appSettings, IEaseCache cache, ILogger<OrdersController> logger) : ControllerBase
{
private readonly ILogger _logger = logger;
private readonly IEaseCache _cache = cache;
private readonly IOrderService _service = service;
private readonly AppSettings _appSettings = appSettings?.Value;
private readonly DateTimeOffset _options = Helper.CreateEaseCacheOptions();
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <response code="200">If login successful Return Order List</response>
[HttpPost("SaveOrder")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(SaveOrderResponse))]
public async Task<IActionResult> SaveOrders([FromBody] SaveOrderRequest request)
{
SaveOrderResponse response = new();
try
{
int userId = HttpContext.User.GetClaimValue<int>(Constants.UserId);
response = await _service.SaveOrderAsync(request, userId);
return Ok(MobileResponseBase<SaveOrderResponse>.Success(response));
}
catch (Exception ex)
{
string msg = $"Exception occur on Save Orders endpoint with Order UUID request - {request?.UUID}";
_logger.LogError(exception: ex, message: msg);
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
}
}
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <response code="200">If login successful Return Attendance List</response>
[HttpGet("OrderSummary")]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(GetSalesOrderResponse))]
public async Task<IActionResult> GetOrderSummaries([FromQuery] GetOrderSummaryRequest request)
{
PagedResult<GetSalesOrderResponse> response = new();
try
{
int userId = HttpContext.User.GetClaimValue<int>(Constants.UserId);
response = await _service.GetSalesOrdersSummary(request, userId);
return Ok(MobileResponseBase<PagedResult<GetSalesOrderResponse>>.Success(response));
}
catch (Exception ex)
{
string msg = $"Exception occur on Save Orders endpoint with Order UUID request - {request?.UUID}";
_logger.LogError(exception: ex, message: msg);
return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
}
}
}

View File

@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace OnlineSalesAutoCrop.CoreAPI.Controllers.V1
namespace OnlineSalesAutoCrop.CoreAPI.Controllers.Web
{
/// <summary>
///

View File

@ -13,7 +13,7 @@ using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace OnlineSalesAutoCrop.CoreAPI.Controllers.V1
namespace OnlineSalesAutoCrop.CoreAPI.Controllers.Web
{
/// <summary>
///

View File

@ -1,16 +1,5 @@
using Asp.Versioning;
using OnlineSalesAutoCrop.CoreAPI.Configurations;
using OnlineSalesAutoCrop.CoreAPI.Models;
using OnlineSalesAutoCrop.CoreAPI.Models.Global;
using OnlineSalesAutoCrop.CoreAPI.Models.Objects;
using OnlineSalesAutoCrop.CoreAPI.Models.Objects.Systems;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Setups;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Systems;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Systems;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems;
using OnlineSalesAutoCrop.CoreAPI.SignalRHub;
using Google.Apis.Auth.OAuth2.Requests;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@ -18,6 +7,20 @@ using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using OnlineSalesAutoCrop.CoreAPI.Configurations;
using OnlineSalesAutoCrop.CoreAPI.Models;
using OnlineSalesAutoCrop.CoreAPI.Models.Global;
using OnlineSalesAutoCrop.CoreAPI.Models.Objects;
using OnlineSalesAutoCrop.CoreAPI.Models.Objects.Systems;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Setups;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Systems;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Systems;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Auth;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems;
using OnlineSalesAutoCrop.CoreAPI.SignalRHub;
using System;
using System.Collections.Generic;
using System.DirectoryServices;
@ -29,7 +32,7 @@ using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace OnlineSalesAutoCrop.CoreAPI.Controllers.V1
namespace OnlineSalesAutoCrop.CoreAPI.Controllers.Web
{
/// <summary>
///
@ -45,11 +48,11 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers.V1
[Authorize]
[ApiController]
[ApiVersion("1.0")]
[Route("api/users")]
[ValidateAntiForgeryToken]
[Route("api/v{version:apiVersion}/users")]
public class UserController(IUserService service, IOptions<AppSettings> appSettings, IEaseCache cache, ILogger<IntegrationAuthController> logger, IHubContext<NotificationHub, INotificationHub> hub) : ControllerBase
public class UserController(IUserService service, IOptions<AppSettings> appSettings, IEaseCache cache, ILogger<IntegrationAuthController> logger,
IHubContext<NotificationHub, INotificationHub> hub, IRefreshTokenService refreshTokenService) : ControllerBase
{
private readonly ILogger _logger = logger;
private readonly IEaseCache _cache = cache;
@ -57,6 +60,7 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers.V1
private readonly AppSettings _appSettings = appSettings?.Value;
private readonly DateTimeOffset _options = Helper.CreateEaseCacheOptions();
private readonly IHubContext<NotificationHub, INotificationHub> _hub = hub;
private readonly IRefreshTokenService _refreshTokenService= refreshTokenService;
/// <summary>
/// Login using your credential data retrieve from SqlServer
@ -215,17 +219,15 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers.V1
{
Subject = new ClaimsIdentity(
[
//Helper.CreateClaim("UserId", $"{user.Id}"),
//Helper.CreateClaim("LoginId", user.LoginId),
//Helper.CreateClaim("UserPwd", userPwd),
//Helper.CreateClaim("AuthKey", $"{user.AuthKey}"),
//Helper.CreateClaim("TeamSpaceIds", $"{string.Join(',',user.TeamSpaceIds)}"),
//Helper.CreateClaim("BatchEnabled", user.BatchEnabled ? "1" : "0"),
//Helper.CreateClaim("BmProcessId", $"{user.BmProcessId}"),
//Helper.CreateClaim("PrProcessId", $"{user.PrProcessId}"),
//Helper.CreateClaim("EmployeeId", $"{user.EmployeeCode}")
Helper.CreateClaim("UserId", $"{user.UserId}"),
Helper.CreateClaim("LoginId", user.LoginId),
Helper.CreateClaim("AuthKey", $"{user.AuthKey}"),
Helper.CreateClaim("TeamSpaceIds", $"{string.Join(',',user.TeamSpaceIds)}"),
Helper.CreateClaim("UserPlatformId", ((int)user.UserPlatFormType).ToString()),
Helper.CreateClaim("UserRoleType", $"{user.UserRoleType}"),
Helper.CreateClaim("EmployeeCode", $"{user.EmployeeNumber}")
]),
Expires = DateTime.UtcNow.AddHours(12),
Expires = DateTime.UtcNow.AddMinutes(_appSettings.AccessTokenDuration),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
};
@ -246,37 +248,17 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers.V1
response.AuthenticationToken = userToken;
response.LoginTime = $"{DateTime.Now:dd-MM-yy H:mm:ss}";
//await HttpContext.Session.SetModulesToSession(key: userToken, value: user.ModuleIds);
//if (user.LoginStatus == EnumLoginStatus.Success)
//{
// if (user.AuthMethod == EnumAuthenticationMethod.Email && !string.IsNullOrEmpty(user.EmailAddress) && !string.IsNullOrWhiteSpace(user.EmailAddress) && !string.IsNullOrWhiteSpace(user.AuthValue))
// {
// List<string> to = [.. user.EmailAddress.Split(separator: ';', options: StringSplitOptions.RemoveEmptyEntries)];
// await MailHelper.SendMailMessageAsync(settings: _appSettings, to: to,
// cc: null, bcc: null, attachments: null, embeddedImages: null, isHtmlBody: false, priority: System.Net.Mail.MailPriority.Normal,
// subject: "Your OTP", messageBody: string.Format("Your OTP: {0} and is valid for 5 minutes only", user.AuthValue));
// }
// else if (user.AuthMethod == EnumAuthenticationMethod.MobileSMS && !string.IsNullOrEmpty(user.MobileNo) && !string.IsNullOrWhiteSpace(user.MobileNo) && !string.IsNullOrWhiteSpace(user.AuthValue))
// {
// MailHelper.SendSMSOrWhatsAppMessage(settings: _appSettings, whatsAppMsg: false, msg: string.Format("Your OTP: {0} and is valid for 5 minutes only", user.AuthValue), mobileNumber: user.MobileNo);
// }
//}
GenerateRefreshTokenRequest refreshTokenRequest = new GenerateRefreshTokenRequest()
{
UserId = user.UserId,
IpAddress = ipAddress,
RawRefreshToken = string.Empty
};
//if (user.DisallowMultiLogin)
//{
// await _hub.Clients.All.NotifySubscriber(userId: user.Id, msgType: 1, itemId: 0, ipAddress: ipAddress);
// if (request.AttendanceLogin)
// await _hub.Clients.All.NotifySubscriber(userId: user.Id, msgType: 6, itemId: 0, ipAddress: ipAddress);
//}
//else
//{
// await _hub.Clients.All.NotifySubscriber(userId: user.Id, msgType: 2, itemId: 0, ipAddress: ipAddress);
// if (request.AttendanceLogin)
// await _hub.Clients.All.NotifySubscriber(userId: user.Id, msgType: 6, itemId: 0, ipAddress: ipAddress);
//}
//response.IdsValue = Ease.NetCore.Utility.Global.CipherFunctions.EncryptByAES(data: Newtonsoft.Json.JsonConvert.SerializeObject(user.ModuleIds), privateKey: cipherSecretKey, publicKey: cipherSecretKey, output: 2);
var refreshToken = await _refreshTokenService.GenerateRefreshTokenByUserAsync(refreshTokenRequest);
return Ok(response);
response.RefreshToken = refreshToken.RefreshToken;
return Ok(response);
}
catch (Exception ex)
{
@ -623,7 +605,96 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers.V1
}
/// <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")]
[AllowAnonymous]
[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.5_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>
/// <param name="request"></param>
/// <returns></returns>

View File

@ -0,0 +1,23 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace OnlineSalesAutoCrop.CoreAPI.Converter;
public class EmptyStringToNullConverter : JsonConverter<string>
{
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
return null;
var value = reader.GetString();
return string.IsNullOrWhiteSpace(value) ? null : value;
}
public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options)
{
writer.WriteStringValue(value);
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace OnlineSalesAutoCrop.CoreAPI.Converter;
public class NullableDecimalConverter : JsonConverter<decimal?>
{
public override decimal? Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
return null;
if (reader.TokenType == JsonTokenType.Number)
{
var value = reader.GetDecimal();
return value == 0 ? null : value;
}
if (reader.TokenType == JsonTokenType.String)
{
var value = reader.GetString();
if (string.IsNullOrWhiteSpace(value) || value == "0")
return null;
if (int.TryParse(value, out var result))
return result;
throw new JsonException($"Invalid ScaleValue: {value}");
}
throw new JsonException("ScaleValue must be a number, string, or null.");
}
public override void Write(
Utf8JsonWriter writer,
decimal? value,
JsonSerializerOptions options)
{
if (value is null)
writer.WriteNullValue();
else
writer.WriteNumberValue(value.Value);
}
}

View File

@ -167,10 +167,13 @@
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="10.0.0" />
<PackageReference Include="Ease.NetCore" Version="2.1.4" />
<PackageReference Include="Google.Cloud.Speech.V1" Version="3.9.0" />
<PackageReference Include="Hangfire" Version="1.8.23" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.24" />
<PackageReference Include="Hangfire.SqlServer" Version="1.8.24" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.10" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="10.8.0" />
<PackageReference Include="OpenAI" Version="2.10.0" />
<PackageReference Include="RabbitMQ.Client" Version="7.2.1" />
<PackageReference Include="ReportViewerCore.NETCore" Version="15.1.33" />

View File

@ -1,12 +1,5 @@
using Asp.Versioning;
using Asp.Versioning.ApiExplorer;
using OnlineSalesAutoCrop.CoreAPI.API.Swagger;
using OnlineSalesAutoCrop.CoreAPI.Configuration.DI;
using OnlineSalesAutoCrop.CoreAPI.Configurations;
using OnlineSalesAutoCrop.CoreAPI.Models;
using OnlineSalesAutoCrop.CoreAPI.Models.Global;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems;
using OnlineSalesAutoCrop.CoreAPI.SignalRHub;
using Hangfire;
using Hangfire.SqlServer;
using Microsoft.AspNetCore.Authentication.JwtBearer;
@ -19,17 +12,33 @@ 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;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using OnlineSalesAutoCrop.CoreAPI.API.Swagger;
using OnlineSalesAutoCrop.CoreAPI.Configuration.DI;
using OnlineSalesAutoCrop.CoreAPI.Configurations;
using OnlineSalesAutoCrop.CoreAPI.Models;
using OnlineSalesAutoCrop.CoreAPI.Models.Global;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.BackgroupJobs;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Services.Services.Integrations;
using OnlineSalesAutoCrop.CoreAPI.SignalRHub;
using Swashbuckle.AspNetCore.SwaggerGen;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.RateLimiting;
using System.Threading.Tasks;
@ -47,11 +56,11 @@ namespace OnlineSalesAutoCrop.CoreAPI
/// </summary>
public IConfiguration Configuration { get; }
/// <summary>
/// Initializes a new instance of the Startup class using the specified configuration settings.
/// </summary>
/// <param name="configuration">The configuration settings used to initialize the application. Cannot be null.</param>
public Startup(IConfiguration configuration)
/// <summary>
/// Initializes a new instance of the Startup class using the specified configuration settings.
/// </summary>
/// <param name="configuration">The configuration settings used to initialize the application. Cannot be null.</param>
public Startup(IConfiguration configuration)
{
Configuration = configuration;
_appSettings = Configuration.GetSection("AppSettings").Get<AppSettings>();
@ -73,20 +82,52 @@ namespace OnlineSalesAutoCrop.CoreAPI
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
services.Configure<MenuSettings>(Configuration.GetSection("MenuSettings"));
#endregion
#endregion
#region Controllers
#region Controllers
services.AddControllers(options =>
services.AddControllers(options =>
{
options.Filters.Add(new ProducesAttribute("application/json"));
}).AddJsonOptions(options =>
{
options.Filters.Add(new ProducesAttribute("application/json"));
options.JsonSerializerOptions.Converters.Add(
new CoreAPI.Converter.EmptyStringToNullConverter());
options.JsonSerializerOptions.Converters.Add(
new CoreAPI.Converter.NullableDecimalConverter());
});
#endregion
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<Startup>>();
#region API versioning
var errors = context.ModelState
.Where(kvp => kvp.Value?.Errors.Count > 0)
.SelectMany(kvp => kvp.Value!.Errors.Select(e => e.ErrorMessage))
.ToList();
services.AddApiVersioning(options =>
var response = new MobileResponseBase<object>
{
ReturnStatus = StatusCodes.Status400BadRequest,
ReturnMessage =new List<string>() { errors.Count > 0 ? errors[0] : string.Empty },
Data = null
};
logger.LogError("Validation Failed. Request: {@Errors}", errors);
return new BadRequestObjectResult(response);
};
});
#endregion
#region API versioning
services.AddApiVersioning(options =>
{
options.ReportApiVersions = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
@ -286,6 +327,10 @@ namespace OnlineSalesAutoCrop.CoreAPI
services.AddHangfire(config =>
{
config.UseFilter(new TimeRestrictionServerFilter(startHour: 1, endHour: 7));
config.SetDataCompatibilityLevel(CompatibilityLevel.Version_180);
config.UseSimpleAssemblyNameTypeSerializer();
config.UseRecommendedSerializerSettings();
config.UseSqlServerStorage(_appSettings.HangfireDb, new SqlServerStorageOptions
{
DisableGlobalLocks = true,
@ -295,14 +340,18 @@ namespace OnlineSalesAutoCrop.CoreAPI
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5)
});
});
services.AddHangfireServer();
services.AddHangfireServer(options =>
{
options.Queues = new[] { "sap_order_send", "default" }; // merge with any queues you already have
});
}
#endregion
#region SWAGGER
#endregion
if (_appSettings.Swagger.Enabled)
#region SWAGGER
if (_appSettings.Swagger.Enabled)
{
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
services.AddSwaggerGen(options =>
@ -318,9 +367,35 @@ namespace OnlineSalesAutoCrop.CoreAPI
services.ConfigureBusinessServices();
#endregion
}
}
#endregion
#region AutoCrop Integration Service
services.AddHttpClient<IIntegrationHttpService, IntegrationHttpService>((sp, client) =>
{
var opt = _appSettings.AutoCropSAPApi;
client.BaseAddress = new Uri(opt.BaseUrl);
var basicAuth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{opt.UserName}:{opt.Password}"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", basicAuth);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");
})
.AddStandardResilienceHandler(options =>
{
options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(300);
options.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(600); // must be >= 2x AttemptTimeout
options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(300);
options.Retry.MaxRetryAttempts = 1; // see note below
});
#endregion
}
}
catch
{
//Nothimg to do here as we are handling all exceptions in Configure method using global exception handler
@ -388,11 +463,11 @@ namespace OnlineSalesAutoCrop.CoreAPI
app.UseStaticFiles();
#endregion
#endregion
#region Routing
#region Routing
app.UseRouting();
app.UseRouting();
#endregion
@ -445,11 +520,114 @@ namespace OnlineSalesAutoCrop.CoreAPI
loggerFactory.AddFile(pathFormat: $@"{_appSettings.LoggerPath}\Log.txt", minimumLevel: (LogLevel)_appSettings.LoggerMinLevel);
}
#endregion
#endregion
#region Controllers and SignalR
#region Log Request and Response
app.UseEndpoints(endpoints =>
if (_appSettings.IsLogRequest > 0)
{
app.Use(async (context, next) =>
{
var logDirectory = Path.Combine(
env.ContentRootPath,
"ApiLogs"
);
var logErrorDirectory = Path.Combine(
env.ContentRootPath,
"ApiErrorLogs"
);
context.Request.EnableBuffering();
string requestBody = "";
if (context.Request.ContentLength > 0)
{
using var reader = new StreamReader(
context.Request.Body,
Encoding.UTF8,
leaveOpen: true);
requestBody = await reader.ReadToEndAsync();
context.Request.Body.Position = 0;
}
var originalResponseBody = context.Response.Body;
await using var responseBody = new MemoryStream();
context.Response.Body = responseBody;
try
{
await next();
responseBody.Position = 0;
var responseText =
await new StreamReader(responseBody).ReadToEndAsync();
responseBody.Position = 0;
await responseBody.CopyToAsync(originalResponseBody);
var log = new
{
timestamp = DateTimeOffset.UtcNow,
request = new
{
method = context.Request.Method,
scheme = context.Request.Scheme,
host = context.Request.Host.ToString(),
path = context.Request.Path.ToString(),
query = context.Request.QueryString.ToString(),
ipAddress = context.Connection.RemoteIpAddress?.ToString(),
body = JsonDocument.Parse(requestBody).RootElement
},
response = new
{
statusCode = context.Response.StatusCode,
body = JsonDocument.Parse(responseText).RootElement
}
};
var json = System.Text.Json.JsonSerializer.Serialize(
log,
new JsonSerializerOptions
{
WriteIndented = true
});
var fileName =
$"{ context.Request.Path.ToString().Split('/').Last() }-{ DateTime.Now:yyyy-MM-dd_HH-mm-ss-fff}.json";
string filePath = string.Empty;
if(context.Response.StatusCode ==200 || context.Response.StatusCode == 201)
{
filePath = Path.Combine(logDirectory, fileName);
}
else
{
filePath = Path.Combine(logErrorDirectory, fileName);
}
await File.WriteAllTextAsync(filePath, json);
}
finally
{
context.Response.Body = originalResponseBody;
}
});
}
#endregion
#region Controllers and SignalR
app.UseEndpoints(endpoints =>
{
if (!string.IsNullOrEmpty(_appSettings.RLName))
{
@ -483,6 +661,11 @@ namespace OnlineSalesAutoCrop.CoreAPI
app.UseHangfireDashboard();
RecurringJobOptions options = new() { TimeZone = TimeZoneInfo.Local, MisfireHandling = MisfireHandlingMode.Relaxed };
RecurringJob.AddOrUpdate<IBackgroundJobService>(
"send-sap-orders",
service => service.ExecuteSendingOrderAsync(CancellationToken.None),
"*/1 * * * *");
}
#endregion

View File

@ -51,7 +51,7 @@
"EmailUseDefaultCredentials": false,
"EPMaxLevel": 2,
"FileProcessFolder": "D:\\Local\\EaseBilling\\Api\\OnlineSalesAutoCrop.CoreAPI\\FileProcessFolder",
"HangfireDb": "9oXGnVl0H8yqF5I2MjHeXGNp5367ljjQjnsatcKsW/KFPlA8iwEVdaY7ZMBnU+AtKZIaKjwWoHZvDLmNn0K8Ke96pS3Sj4tD+mJejgSfJLhaB+UERm6M+xqVC30kk25TsVBQezB7AsmS8PXLfgWAboe4YmyQ3pXMrDx7WCfiuJA=",
"HangfireDb": "Data Source=210.4.65.222,3341;Initial Catalog=AutoCropDB;User ID=dmsuser;Password=dMsu@er;Encrypt=false",
"JwtAudience": "www.celdevbd.com",
"JwtCryptoKey": "Computer ease limited, dhaka, bangladesh BDComputer ease limited",
"JwtIssuer": "https://www.celdevbd.com",
@ -84,7 +84,14 @@
"WaAuthToken": "024a6897584671d9f9fa588d7c94aa96",
"WaMsgSvcSid": "MG8401d33a9a3b2aea95619bda3e5757b5",
"WaSenderId": "+8801326755660",
"RefreshTokenDuration": "15"
"RefreshTokenDuration": "2",
"AccessTokenDuration": "1",
"IsLogRequest": 0,
"AutoCropSAPApi": {
"BaseUrl": "https://accl-test-pad-l06vsvh7.it-cpi004-rt.cfapps.ap11.hana.ondemand.com/",
"UserName": "sb-1635ae0e-9941-4998-962f-e23f592d29b5!b45657|it-rt-accl-test-pad-l06vsvh7!b68",
"Password": "01b3f9b9-8dee-4aed-9cb9-bbefeffc1db8$eB6J_oUJZDGuJnpzAzLUxI9lAha_hljim2cfH1niJa8="
}
},
"MenuSettings": {

View File

@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "10.0.9",
"commands": [
"dotnet-ef"
],
"rollForward": false
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -76,7 +76,7 @@
"@angular-eslint/eslint-plugin-template": "21.4.0",
"@angular-eslint/schematics": "21.4.0",
"@angular-eslint/template-parser": "21.4.0",
"@angular/cli": "21.2.13",
"@angular/cli": "^21.2.19",
"@angular/compiler-cli": "21.2.15",
"@angular/language-service": "21.2.15",
"@types/crypto-js": "4.2.2",
@ -581,12 +581,11 @@
}
},
"node_modules/@angular-devkit/schematics": {
"version": "21.2.13",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.2.13.tgz",
"integrity": "sha512-gifpOcMNiAy49lQmQKhzpxoSfS3qJQSEdJSF5m7RVFkAcmllfcCD76GPN4dhho3wdAnbZ3qr54LtDqrGY4xNjw==",
"license": "MIT",
"version": "21.2.19",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.2.19.tgz",
"integrity": "sha512-AG3Fzh9wJCmKBfxUQOUWaEHMj5Gq2O+Msf1z52aDSxbVhs5/iSQcXGPv/DLdAXu7d4xmQhLouNe9Glaq2omDyw==",
"dependencies": {
"@angular-devkit/core": "21.2.13",
"@angular-devkit/core": "21.2.19",
"jsonc-parser": "3.3.1",
"magic-string": "0.30.21",
"ora": "9.3.0",
@ -598,6 +597,32 @@
"yarn": ">= 1.13.0"
}
},
"node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": {
"version": "21.2.19",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.2.19.tgz",
"integrity": "sha512-dtpJMQBz5nhkcIogPmXP/aT2Ak8m/wLRPOSTI/g4vSJSuGiI53PgtWq4/wfQga6E6wdM2XWsblAE89d8w5heQQ==",
"dependencies": {
"ajv": "8.18.0",
"ajv-formats": "3.0.1",
"jsonc-parser": "3.3.1",
"picomatch": "4.0.4",
"rxjs": "7.8.2",
"source-map": "0.7.6"
},
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"chokidar": "^5.0.0"
},
"peerDependenciesMeta": {
"chokidar": {
"optional": true
}
}
},
"node_modules/@angular-eslint/builder": {
"version": "21.4.0",
"resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-21.4.0.tgz",
@ -823,26 +848,26 @@
}
},
"node_modules/@angular/cli": {
"version": "21.2.13",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.2.13.tgz",
"integrity": "sha512-j1kOV/f0og/3xCwG7Y8RyPd6V7uYfX2NuvXbvN1mzgxLLN2mu6CTsvPg5l/9Pu9SJI3KOPRgDxWyuP3k8KuzMg==",
"version": "21.2.19",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.2.19.tgz",
"integrity": "sha512-i78NzvoNonAY17QgzSmqrYnXHmEfraLv4wZ/o/m3efxuz61ZJ+5X/PsCeAhbwBvQfRrPRQaJV2tK9vGjHa+U6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@angular-devkit/architect": "0.2102.13",
"@angular-devkit/core": "21.2.13",
"@angular-devkit/schematics": "21.2.13",
"@angular-devkit/architect": "0.2102.19",
"@angular-devkit/core": "21.2.19",
"@angular-devkit/schematics": "21.2.19",
"@inquirer/prompts": "7.10.1",
"@listr2/prompt-adapter-inquirer": "3.0.5",
"@modelcontextprotocol/sdk": "1.26.0",
"@schematics/angular": "21.2.13",
"@schematics/angular": "21.2.19",
"@yarnpkg/lockfile": "1.1.0",
"algoliasearch": "5.48.1",
"ini": "6.0.0",
"jsonc-parser": "3.3.1",
"listr2": "9.0.5",
"npm-package-arg": "13.0.2",
"pacote": "21.3.1",
"pacote": "21.5.1",
"parse5-html-rewriting-stream": "8.0.0",
"semver": "7.7.4",
"yargs": "18.0.0",
@ -857,6 +882,51 @@
"yarn": ">= 1.13.0"
}
},
"node_modules/@angular/cli/node_modules/@angular-devkit/architect": {
"version": "0.2102.19",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2102.19.tgz",
"integrity": "sha512-cj4tzUMiloLTg5rNf17E8MsvIxCWYoBiBsaj7ns6dgXqT9XCeG+J0TA2t1M+N9uuqfeLd22U/rYoCkADmcircQ==",
"dev": true,
"dependencies": {
"@angular-devkit/core": "21.2.19",
"rxjs": "7.8.2"
},
"bin": {
"architect": "bin/cli.js"
},
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
}
},
"node_modules/@angular/cli/node_modules/@angular-devkit/core": {
"version": "21.2.19",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.2.19.tgz",
"integrity": "sha512-dtpJMQBz5nhkcIogPmXP/aT2Ak8m/wLRPOSTI/g4vSJSuGiI53PgtWq4/wfQga6E6wdM2XWsblAE89d8w5heQQ==",
"dev": true,
"dependencies": {
"ajv": "8.18.0",
"ajv-formats": "3.0.1",
"jsonc-parser": "3.3.1",
"picomatch": "4.0.4",
"rxjs": "7.8.2",
"source-map": "0.7.6"
},
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"chokidar": "^5.0.0"
},
"peerDependenciesMeta": {
"chokidar": {
"optional": true
}
}
},
"node_modules/@angular/common": {
"version": "21.2.15",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-21.2.15.tgz",
@ -3523,7 +3593,6 @@
"resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz",
"integrity": "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
@ -3970,7 +4039,6 @@
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
"dev": true,
"license": "ISC",
"dependencies": {
"minipass": "^7.0.4"
},
@ -5141,7 +5209,6 @@
"resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.2.tgz",
"integrity": "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg==",
"dev": true,
"license": "ISC",
"dependencies": {
"agent-base": "^7.1.0",
"http-proxy-agent": "^7.0.0",
@ -5154,11 +5221,10 @@
}
},
"node_modules/@npmcli/agent/node_modules/lru-cache": {
"version": "11.5.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz",
"integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==",
"version": "11.5.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
"integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
@ -5168,7 +5234,6 @@
"resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz",
"integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==",
"dev": true,
"license": "ISC",
"dependencies": {
"semver": "^7.3.5"
},
@ -5181,7 +5246,6 @@
"resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.2.tgz",
"integrity": "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==",
"dev": true,
"license": "ISC",
"dependencies": {
"@gar/promise-retry": "^1.0.0",
"@npmcli/promise-spawn": "^9.0.0",
@ -5201,17 +5265,15 @@
"resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz",
"integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=20"
}
},
"node_modules/@npmcli/git/node_modules/lru-cache": {
"version": "11.5.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz",
"integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==",
"version": "11.5.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
"integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
@ -5221,7 +5283,6 @@
"resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz",
"integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^4.0.0"
},
@ -5237,7 +5298,6 @@
"resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-4.0.0.tgz",
"integrity": "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==",
"dev": true,
"license": "ISC",
"dependencies": {
"npm-bundled": "^5.0.0",
"npm-normalize-package-bin": "^5.0.0"
@ -5254,7 +5314,6 @@
"resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-5.0.0.tgz",
"integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==",
"dev": true,
"license": "ISC",
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
@ -5264,7 +5323,6 @@
"resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.5.tgz",
"integrity": "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"@npmcli/git": "^7.0.0",
"glob": "^13.0.0",
@ -5283,7 +5341,6 @@
"resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz",
"integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==",
"dev": true,
"license": "ISC",
"dependencies": {
"which": "^6.0.0"
},
@ -5296,7 +5353,6 @@
"resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz",
"integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=20"
}
@ -5306,7 +5362,6 @@
"resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz",
"integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^4.0.0"
},
@ -5322,7 +5377,6 @@
"resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz",
"integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==",
"dev": true,
"license": "ISC",
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
@ -5332,7 +5386,6 @@
"resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.4.tgz",
"integrity": "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==",
"dev": true,
"license": "ISC",
"dependencies": {
"@npmcli/node-gyp": "^5.0.0",
"@npmcli/package-json": "^7.0.0",
@ -7621,13 +7674,12 @@
]
},
"node_modules/@schematics/angular": {
"version": "21.2.13",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.2.13.tgz",
"integrity": "sha512-e5guslSLKbb3PJ6gUuVqM+V9xgn68cJkG1IyBohho34shbpOeoWW2eYdWQQjxvn0KUdgEhYSRBluBamCHngaUA==",
"license": "MIT",
"version": "21.2.19",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.2.19.tgz",
"integrity": "sha512-eL+UU9eizoadhDB4YEctRmmo0A5iwrSmGzeuEa6akrq8nLGVWM8zO91HTJutkPqGQjelF+UOiOShsQSZAU9SIQ==",
"dependencies": {
"@angular-devkit/core": "21.2.13",
"@angular-devkit/schematics": "21.2.13",
"@angular-devkit/core": "21.2.19",
"@angular-devkit/schematics": "21.2.19",
"jsonc-parser": "3.3.1"
},
"engines": {
@ -7636,12 +7688,37 @@
"yarn": ">= 1.13.0"
}
},
"node_modules/@schematics/angular/node_modules/@angular-devkit/core": {
"version": "21.2.19",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.2.19.tgz",
"integrity": "sha512-dtpJMQBz5nhkcIogPmXP/aT2Ak8m/wLRPOSTI/g4vSJSuGiI53PgtWq4/wfQga6E6wdM2XWsblAE89d8w5heQQ==",
"dependencies": {
"ajv": "8.18.0",
"ajv-formats": "3.0.1",
"jsonc-parser": "3.3.1",
"picomatch": "4.0.4",
"rxjs": "7.8.2",
"source-map": "0.7.6"
},
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"chokidar": "^5.0.0"
},
"peerDependenciesMeta": {
"chokidar": {
"optional": true
}
}
},
"node_modules/@sigstore/bundle": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz",
"integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@sigstore/protobuf-specs": "^0.5.0"
},
@ -7654,7 +7731,6 @@
"resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.2.1.tgz",
"integrity": "sha512-qRsxPnCrbC/puegGxKuynfnxgLiHqWStrSjxkoB4YKqq3Z3s4cyZyj42ZdWFAEblNP65C+rBH8EuREHIXoi83g==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
@ -7664,7 +7740,6 @@
"resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.1.tgz",
"integrity": "sha512-/ScWUhhoFasJsSRGTVBwId1loQjjnjAfE4djL6ZhrXRpNCmPTnUKF5Jokd58ILseOMjzET3UrMOtJPS9sYeI0g==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.17.0 || >=20.5.0"
}
@ -7674,7 +7749,6 @@
"resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.1.1.tgz",
"integrity": "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@gar/promise-retry": "^1.0.2",
"@sigstore/bundle": "^4.0.0",
@ -7692,7 +7766,6 @@
"resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.2.tgz",
"integrity": "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@sigstore/protobuf-specs": "^0.5.0",
"tuf-js": "^4.1.0"
@ -7706,7 +7779,6 @@
"resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.1.1.tgz",
"integrity": "sha512-qv7+G3J2cc6wwFj3yKvXOamzqhMwSk1ogPGmhpS8iXllcPrJaIIBA+4HbttlHVu1pqWTdmaCH/WE7UOC51kdoA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@sigstore/bundle": "^4.0.0",
"@sigstore/core": "^3.2.1",
@ -7733,7 +7805,6 @@
"resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz",
"integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^16.14.0 || >=18.0.0"
}
@ -7743,7 +7814,6 @@
"resolved": "https://registry.npmjs.org/@tufjs/models/-/models-4.1.0.tgz",
"integrity": "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==",
"dev": true,
"license": "MIT",
"dependencies": {
"@tufjs/canonical-json": "2.0.0",
"minimatch": "^10.1.1"
@ -8758,7 +8828,6 @@
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz",
"integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==",
"dev": true,
"license": "ISC",
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
@ -9444,7 +9513,6 @@
"resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.4.tgz",
"integrity": "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==",
"dev": true,
"license": "ISC",
"dependencies": {
"@npmcli/fs": "^5.0.0",
"fs-minipass": "^3.0.0",
@ -9462,11 +9530,10 @@
}
},
"node_modules/cacache/node_modules/lru-cache": {
"version": "11.5.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz",
"integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==",
"version": "11.5.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
"integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
@ -9569,7 +9636,6 @@
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
@ -10605,13 +10671,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/err-code": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
"dev": true,
"license": "MIT"
},
"node_modules/errno": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
@ -11111,8 +11170,7 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz",
"integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==",
"dev": true,
"license": "Apache-2.0"
"dev": true
},
"node_modules/express": {
"version": "5.2.1",
@ -11440,7 +11498,6 @@
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz",
"integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==",
"dev": true,
"license": "ISC",
"dependencies": {
"minipass": "^7.0.3"
},
@ -11706,7 +11763,6 @@
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz",
"integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==",
"dev": true,
"license": "ISC",
"dependencies": {
"lru-cache": "^11.1.0"
},
@ -11715,11 +11771,10 @@
}
},
"node_modules/hosted-git-info/node_modules/lru-cache": {
"version": "11.5.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz",
"integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==",
"version": "11.5.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
"integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
@ -11814,8 +11869,7 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
"integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==",
"dev": true,
"license": "BSD-2-Clause"
"dev": true
},
"node_modules/http-deceiver": {
"version": "1.2.7",
@ -11870,7 +11924,6 @@
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"dev": true,
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.0",
"debug": "^4.3.4"
@ -11966,7 +12019,6 @@
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz",
"integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==",
"dev": true,
"license": "ISC",
"dependencies": {
"minimatch": "^10.0.3"
},
@ -12044,7 +12096,6 @@
"resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz",
"integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==",
"dev": true,
"license": "ISC",
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
@ -12651,7 +12702,6 @@
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz",
"integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
@ -12710,8 +12760,7 @@
"dev": true,
"engines": [
"node >= 0.2.0"
],
"license": "MIT"
]
},
"node_modules/jsonwebtoken": {
"version": "9.0.3",
@ -13772,7 +13821,6 @@
"resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.6.tgz",
"integrity": "sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw==",
"dev": true,
"license": "ISC",
"dependencies": {
"@gar/promise-retry": "^1.0.0",
"@npmcli/agent": "^4.0.0",
@ -14014,7 +14062,6 @@
"resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz",
"integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==",
"dev": true,
"license": "ISC",
"dependencies": {
"minipass": "^7.0.3"
},
@ -14027,7 +14074,6 @@
"resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.2.tgz",
"integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"minipass": "^7.0.3",
"minipass-sized": "^2.0.0",
@ -14045,7 +14091,6 @@
"resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz",
"integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"minipass": "^3.0.0"
},
@ -14058,7 +14103,6 @@
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dev": true,
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
@ -14070,15 +14114,13 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true,
"license": "ISC"
"dev": true
},
"node_modules/minipass-pipeline": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
"integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
"dev": true,
"license": "ISC",
"dependencies": {
"minipass": "^3.0.0"
},
@ -14091,7 +14133,6 @@
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dev": true,
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
@ -14103,15 +14144,13 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true,
"license": "ISC"
"dev": true
},
"node_modules/minipass-sized": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz",
"integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==",
"dev": true,
"license": "ISC",
"dependencies": {
"minipass": "^7.1.2"
},
@ -14124,7 +14163,6 @@
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"minipass": "^7.1.2"
},
@ -14360,11 +14398,10 @@
}
},
"node_modules/node-gyp": {
"version": "12.3.0",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.3.0.tgz",
"integrity": "sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==",
"version": "12.4.0",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz",
"integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==",
"dev": true,
"license": "MIT",
"dependencies": {
"env-paths": "^2.2.0",
"exponential-backoff": "^3.1.1",
@ -14405,17 +14442,15 @@
"resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz",
"integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=20"
}
},
"node_modules/node-gyp/node_modules/undici": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.26.0.tgz",
"integrity": "sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==",
"version": "6.28.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz",
"integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.17"
}
@ -14425,7 +14460,6 @@
"resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz",
"integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^4.0.0"
},
@ -14450,7 +14484,6 @@
"resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz",
"integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==",
"dev": true,
"license": "ISC",
"dependencies": {
"abbrev": "^4.0.0"
},
@ -14475,7 +14508,6 @@
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-5.0.0.tgz",
"integrity": "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==",
"dev": true,
"license": "ISC",
"dependencies": {
"npm-normalize-package-bin": "^5.0.0"
},
@ -14488,7 +14520,6 @@
"resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-8.0.0.tgz",
"integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"semver": "^7.1.1"
},
@ -14501,7 +14532,6 @@
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz",
"integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==",
"dev": true,
"license": "ISC",
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
@ -14511,7 +14541,6 @@
"resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.2.tgz",
"integrity": "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA==",
"dev": true,
"license": "ISC",
"dependencies": {
"hosted-git-info": "^9.0.0",
"proc-log": "^6.0.0",
@ -14527,7 +14556,6 @@
"resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.4.tgz",
"integrity": "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng==",
"dev": true,
"license": "ISC",
"dependencies": {
"ignore-walk": "^8.0.0",
"proc-log": "^6.0.0"
@ -14541,7 +14569,6 @@
"resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-11.0.3.tgz",
"integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"npm-install-checks": "^8.0.0",
"npm-normalize-package-bin": "^5.0.0",
@ -14557,7 +14584,6 @@
"resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.1.1.tgz",
"integrity": "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==",
"dev": true,
"license": "ISC",
"dependencies": {
"@npmcli/redact": "^4.0.0",
"jsonparse": "^1.3.1",
@ -14761,11 +14787,10 @@
}
},
"node_modules/p-map": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz",
"integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==",
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.6.tgz",
"integrity": "sha512-I4Prw6ivkd6p8PiYR1tXASOAOBzIJwu0TB7fqaX0c/8c3QAehNYmX57EijyGGGBt3c/BIowGwV03RVBtXvHEVg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
@ -14802,12 +14827,12 @@
}
},
"node_modules/pacote": {
"version": "21.3.1",
"resolved": "https://registry.npmjs.org/pacote/-/pacote-21.3.1.tgz",
"integrity": "sha512-O0EDXi85LF4AzdjG74GUwEArhdvawi/YOHcsW6IijKNj7wm8IvEWNF5GnfuxNpQ/ZpO3L37+v8hqdVh8GgWYhg==",
"version": "21.5.1",
"resolved": "https://registry.npmjs.org/pacote/-/pacote-21.5.1.tgz",
"integrity": "sha512-KvcJ9iy3crysCsgqc4+PknH/w6jkrp8JN36mpZBPwNaDRwTfMZD37YzRazNstiZUOhuF5pno9f78n9mEJBavwg==",
"dev": true,
"license": "ISC",
"dependencies": {
"@gar/promise-retry": "^1.0.0",
"@npmcli/git": "^7.0.0",
"@npmcli/installed-package-contents": "^4.0.0",
"@npmcli/package-json": "^7.0.0",
@ -14821,7 +14846,6 @@
"npm-pick-manifest": "^11.0.1",
"npm-registry-fetch": "^19.0.0",
"proc-log": "^6.0.0",
"promise-retry": "^2.0.1",
"sigstore": "^4.0.0",
"ssri": "^13.0.0",
"tar": "^7.4.3"
@ -15304,7 +15328,6 @@
"resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz",
"integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==",
"dev": true,
"license": "ISC",
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
@ -15316,20 +15339,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/promise-retry": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
"integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
"dev": true,
"license": "MIT",
"dependencies": {
"err-code": "^2.0.2",
"retry": "^0.12.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -15667,16 +15676,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/rfdc": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
@ -16412,7 +16411,6 @@
"resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.1.1.tgz",
"integrity": "sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@sigstore/bundle": "^4.0.0",
"@sigstore/core": "^3.2.1",
@ -16447,7 +16445,6 @@
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
@ -16575,7 +16572,6 @@
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz",
"integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==",
"dev": true,
"license": "MIT",
"dependencies": {
"ip-address": "^10.1.1",
"smart-buffer": "^4.2.0"
@ -16590,7 +16586,6 @@
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
"integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
"dev": true,
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "^4.3.4",
@ -16677,15 +16672,13 @@
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
"integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
"dev": true,
"license": "CC-BY-3.0"
"dev": true
},
"node_modules/spdx-expression-parse": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz",
"integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"spdx-exceptions": "^2.1.0",
"spdx-license-ids": "^3.0.0"
@ -16695,8 +16688,7 @@
"version": "3.0.23",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz",
"integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==",
"dev": true,
"license": "CC0-1.0"
"dev": true
},
"node_modules/spdy": {
"version": "4.0.2",
@ -16735,7 +16727,6 @@
"resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz",
"integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"minipass": "^7.0.3"
},
@ -16873,11 +16864,10 @@
}
},
"node_modules/tar": {
"version": "7.5.16",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz",
"integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==",
"version": "7.5.22",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz",
"integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
"chownr": "^3.0.0",
@ -16894,7 +16884,6 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
@ -17159,7 +17148,6 @@
"resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.1.0.tgz",
"integrity": "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@tufjs/models": "4.1.0",
"debug": "^4.4.3",
@ -17431,7 +17419,6 @@
"resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz",
"integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==",
"dev": true,
"license": "ISC",
"engines": {
"node": "^20.17.0 || >=22.9.0"
}

View File

@ -27,8 +27,8 @@
"@popperjs/core": "2.11.8",
"@progress/kendo-angular-barcodes": "24.0.3",
"@progress/kendo-angular-buttons": "24.0.3",
"@progress/kendo-angular-charts": "24.0.3",
"@progress/kendo-angular-chart-wizard": "24.0.3",
"@progress/kendo-angular-charts": "24.0.3",
"@progress/kendo-angular-common": "24.0.3",
"@progress/kendo-angular-dateinputs": "24.0.3",
"@progress/kendo-angular-dialog": "24.0.3",
@ -73,15 +73,15 @@
"zone.js": "0.16.2"
},
"devDependencies": {
"@angular/cli": "21.2.13",
"@angular/compiler-cli": "21.2.15",
"@angular/language-service": "21.2.15",
"@angular-devkit/build-angular": "21.2.13",
"@angular-eslint/builder": "21.4.0",
"@angular-eslint/eslint-plugin": "21.4.0",
"@angular-eslint/eslint-plugin-template": "21.4.0",
"@angular-eslint/schematics": "21.4.0",
"@angular-eslint/template-parser": "21.4.0",
"@angular/cli": "^21.2.19",
"@angular/compiler-cli": "21.2.15",
"@angular/language-service": "21.2.15",
"@types/crypto-js": "4.2.2",
"@types/jasmine": "6.0.0",
"@types/jasminewd2": "2.0.13",

View File

@ -33,10 +33,7 @@ export class AppBreadcrumbComponent implements OnDestroy
{
if (this.authService?.currentUserValue)
{
if (this.authService.currentUserValue.dbOnStartup)
this.router.navigate(['/dashboard']);
else
this.router.navigate(['/home']);
this.router.navigate(['/home']);
}
}

View File

@ -6,6 +6,7 @@ import { Component, Input, OnInit, AfterViewInit, forwardRef, inject } from '@an
import { AppComponent } from './app.component';
import { UserService } from '../providers/user/user.service.';
import { MenuItemComponent } from './components/menu/menuitem.component';
import { MENU_DEFINITION, PROFILE_MENU, MenuNode, buildPermittedMenu } from './components/models/menu.definition';
@Component({
standalone: true,
@ -28,9 +29,11 @@ export class AppMenuComponent implements OnInit, AfterViewInit
public isAuthenicated: boolean = false;
private menuItems: any;
private routePaths: Set<string>;
ngOnInit()
{
debugger;
this.service.authService.sessionEvent.subscribe(value => this.onAuthenication(value));
if (this.service.authService.currentUserValue)
{
@ -42,15 +45,14 @@ export class AppMenuComponent implements OnInit, AfterViewInit
}
this.reloadMenu();
this.modelUngrouped = [{ label: 'Main Menu', icon: 'fa fa-home', items: this.modelGrouped }];
}
private reloadMenu()
{
if (this.isAuthenicated && this.service?.authService?.currentUserValue?.moduleIds)
const currentUser = this.service?.authService?.currentUserValue;
if (this.isAuthenicated && currentUser?.moduleIds)
{
const dbOnStartup = this.service.authService.currentUserValue.dbOnStartup;
this.app.detailInfo = `Version: ${this.app.swVersion} | User: ${this.service.authService.currentUserValue.userName} | Login Time: ${this.service.authService.currentUserValue.loginTime}`;
this.app.detailInfo = `Version: ${this.app.swVersion} | User: ${currentUser.userName} | Login Time: ${currentUser.loginTime}`;
this.modelGrouped =
[
{
@ -58,7 +60,7 @@ export class AppMenuComponent implements OnInit, AfterViewInit
items:
[
{ label: 'Logout', icon: 'fa fa-power-off', routerLink: ['/logout'] },
{ label: dbOnStartup ? 'Dashboard' : 'Home', icon: dbOnStartup ? 'fa fa-dashboard' : 'fa fa-home', routerLink: [dbOnStartup ? '/dashboard' : '/home'] }
{ label: 'Home', icon: 'fa fa-home', routerLink: ['/home'] }
]
},
{
@ -78,6 +80,8 @@ export class AppMenuComponent implements OnInit, AfterViewInit
items: [{ label: 'Login', icon: 'fa fa-sign-in', routerLink: ['/login'] }]
}];
}
this.modelUngrouped = [{ label: 'Main Menu', icon: 'fa fa-home', items: this.modelGrouped }];
}
ngAfterViewInit()
@ -128,35 +132,18 @@ export class AppMenuComponent implements OnInit, AfterViewInit
private onAuthenication(value: any): void
{
this.isAuthenicated = value.loggedIn;
if (value.loggedIn && value.userId && value.userId !=0)
const currentUser = this.service.authService.currentUserValue;
if (value.loggedIn && value.userId && value.userId != 0 && currentUser)
{
this.service.loadMenu(value.userId).subscribe(
{
next: (resp: any) =>
{
this.menuItems = resp || [];
this.app.userId = this.service.authService.currentUserValue?.id;
this.app.loginId = this.service.authService.currentUserValue?.loginId;
this.app.layoutMode = this.service.authService.currentUserValue?.menuLayout;
this.app.notificationCount = this.service.authService.currentUserValue?.notificationCount;
this.app.autoScrollMenu = (this.app.layoutMode === 'static' || this.app.layoutMode === 'overlay') ? 'auto-scroll-menu' : '';
this.changeTheme(this.service.authService.currentUserValue?.themeName, this.service.authService.currentUserValue?.schemeName);
this.menuItems = { items: this.buildMenuItems(currentUser.moduleIds) };
this.app.userId = currentUser.id;
this.app.loginId = currentUser.loginId;
this.app.layoutMode = currentUser.menuLayout;
this.app.notificationCount = currentUser.notificationCount;
this.app.autoScrollMenu = (this.app.layoutMode === 'static' || this.app.layoutMode === 'overlay') ? 'auto-scroll-menu' : '';
this.changeTheme(currentUser.themeName, currentUser.schemeName);
this.reloadMenu();
},
error: () =>
{
this.menuItems = [];
this.app.layoutMode = 'overlay'
this.app.loginId = this.app.autoScrollMenu = '';
this.app.userId = this.app.notificationCount = 0;
this.service.authService.loggedout(false);
this.reloadMenu();
this.router.navigate(['/login']);
}
});
this.reloadMenu();
}
else
{
@ -171,6 +158,28 @@ export class AppMenuComponent implements OnInit, AfterViewInit
this.router.navigate(['/login']);
}
}
/**
* Builds the activity menu out of the permission keys the login endpoint returned
* in `moduleIds`, the profile pages are always available to a logged in user.
*/
private buildMenuItems(moduleIds: string[]): MenuNode[]
{
const routeExists = (routerLink: string): boolean => this.registeredRoutes.has(routerLink);
const items: MenuNode[] = buildPermittedMenu([PROFILE_MENU], moduleIds, routeExists);
items.push(...buildPermittedMenu(MENU_DEFINITION, moduleIds, routeExists));
return items;
}
/** Paths registered in the router, keeps permitted but not yet implemented pages out of the sidebar. */
private get registeredRoutes(): Set<string>
{
this.routePaths ??= new Set(this.router.config.map(route => route.path).filter(path => !!path && path !== '**'));
return this.routePaths;
}
}
@Component({

View File

@ -19,7 +19,9 @@ 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' } },
{ path: 'uploadPassword', loadComponent: () => import('./users/uploadpwd.component').then(m => m.UploadPwdComponent), canActivate: [AuthGuard], data: { permissionKey: 'ELIT.1.2.5,ELIT.1.2.5_1,ELIT.1.2.5_2,ELIT.1.2.5_3' } },
//Landing Page
{ path: 'home', loadComponent: () => import('./home/home.component').then(m => m.HomeComponent), canActivate: [AuthGuard]},

View File

@ -0,0 +1,425 @@
/**
* Client side mirror of the server menu (GlobalFunctions.BuildMenu).
*
* The login response returns the permission keys of the logged in user in `moduleIds`,
* so the sidebar is built here instead of calling `v1/users/loadMenu`.
*
* `moduleIds` holds one entry per permitted module plus the operation suffixes:
* ELIT.1.2.2 -> select (the page itself)
* ELIT.1.2.2_1 -> add
* ELIT.1.2.2_2 -> edit
* ELIT.1.2.2_3 -> delete
*
* Only items that are visible on the server menu are listed below, permission only
* keys (ELIT.1.1A, ELIT.4.1.10, ...) never render so they are left out.
*/
export interface MenuNode
{
label: string;
icon?: string;
moduleId?: string;
routerLink?: string;
items?: MenuNode[];
}
/** Always available to a logged in user, the server adds these without any permission check. */
export const PROFILE_MENU: MenuNode =
{
label: 'User Profile', icon: 'fa fa-user-md',
items:
[
{ label: 'My Profile', icon: 'fa fa-user', routerLink: 'myprofile' },
{ label: 'Change My Password', icon: 'fa fa-unlock', routerLink: 'changemypwd' },
{ label: 'Change My Theme', icon: 'fa fa-user', routerLink: 'changemytheme' },
{ label: 'WhatsApp Message', icon: 'fa fa-whatsapp', routerLink: 'sendwhatsappmsg' },
{ label: 'Access Log', icon: 'fa fa-list-alt', routerLink: 'accesslog' }
]
};
export const MENU_DEFINITION: MenuNode[] =
[
{
moduleId: 'ELIT.1', label: 'System Setup', icon: 'fa fa-sun-o',
items:
[
{ moduleId: 'ELIT.1.3', label: 'Label Setting', icon: 'fa fa-sliders', routerLink: 'labelsetting' },
{ moduleId: 'ELIT.1.1', label: 'System Information', icon: 'fa fa-cog', routerLink: 'thissystem' },
{
moduleId: 'ELIT.1.2', label: 'User Management', icon: 'fa fa-users',
items:
[
{ 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' }
]
},
{ moduleId: 'ELIT.1.3A', label: 'Lookup Data', icon: 'fa fa-sliders', routerLink: 'lookupdata' },
{ moduleId: 'ELIT.1.4', label: 'Custom Report Setup', icon: 'fa fa-table', routerLink: 'customreports' },
{
moduleId: 'ELIT.1.5', label: 'Basic Setup', icon: 'fa fa-wrench',
items:
[
{ moduleId: 'ELIT.1.5.1', label: 'Clients', icon: 'fa fa-address-card-o', routerLink: 'clients' },
{ moduleId: 'ELIT.1.5.2', label: 'Suppliers', icon: 'fa fa-credit-card', routerLink: 'suppliers' },
{ moduleId: 'ELIT.1.5.3', label: 'Employees', icon: 'fa fa-user-plus', routerLink: 'employees' },
{ moduleId: 'ELIT.1.5.4', label: 'Project Leads', icon: 'fa fa-user-circle-o', routerLink: 'projectleads' },
{ moduleId: 'ELIT.1.5.5', label: 'Project Managers', icon: 'fa fa-user-circle-o', routerLink: 'projectmgts' },
{ moduleId: 'ELIT.1.5.6', label: 'Projects', icon: 'fa fa-product-hunt', routerLink: 'projects' },
{ moduleId: 'ELIT.1.5.7', label: 'Stores', icon: 'fa fa-pencil-square', routerLink: 'stores' },
{ moduleId: 'ELIT.1.5.8', label: 'Banks & Branches', icon: 'fa fa-university', routerLink: 'bankbranches' },
{ moduleId: 'ELIT.1.5.9', label: 'Bank Accounts', icon: 'fa fa-credit-card', routerLink: 'bankaccounts' },
{ moduleId: 'ELIT.1.5.10', label: 'GL Codes', icon: 'fa fa-book', routerLink: 'glcodes' },
{ moduleId: 'ELIT.1.5.11', label: 'User Clients & Projects', icon: 'fa fa-product-hunt', routerLink: 'userprojects' },
{ moduleId: 'ELIT.1.5.12', label: 'User Suppliers', icon: 'fa fa-product-hunt', routerLink: 'usersuppliers' }
]
}
]
},
{
moduleId: 'ELIT.2', label: 'Authentication Process', icon: 'fa fa-cogs',
items:
[
{ moduleId: 'ELIT.2.1', label: 'Authentication', icon: 'fa fa-check', routerLink: 'authentications' },
{ moduleId: 'ELIT.2.2', label: 'Authorization', icon: 'fa fa-check-square-o', routerLink: 'authorizations' },
{ moduleId: 'ELIT.2.3', label: 'Deactivation', icon: 'fa fa-window-close-o', routerLink: 'deactivations' }
]
},
{
moduleId: 'ELIT.3', label: 'General Ledger', icon: 'fa fa-university',
items:
[
{
moduleId: 'ELIT.3.1', label: 'Basic Setup', icon: 'fa fa-wrench',
items:
[
{ moduleId: 'ELIT.3.1.1', label: 'Chart of Accounts', icon: 'fa fa-th-list', routerLink: 'chartofaccounts' },
{ moduleId: 'ELIT.3.1.2', label: 'Fix GL Balance', icon: 'fa fa-wrench', routerLink: 'fixBalance' },
{ moduleId: 'ELIT.3.1.3', label: 'GL Head Identification', icon: 'fa fa-cog', routerLink: 'identifyglhead' }
]
},
{
moduleId: 'ELIT.3.2', label: 'Transactions', icon: 'fa fa-building',
items:
[
{ moduleId: 'ELIT.3.2.1', label: 'GL Transactions', icon: 'fa fa-archive', routerLink: 'gltrans' },
{ moduleId: 'ELIT.3.2.2', label: 'Upload Auto Voucher', icon: 'fa fa-upload', routerLink: 'gltranav' },
{ moduleId: 'ELIT.3.2.3', label: 'Year End Process', icon: 'fa fa-cog', routerLink: 'yearendprocess' },
{ moduleId: 'ELIT.3.2.4', label: 'Undo Year End', icon: 'fa fa-undo', routerLink: 'undoyearend' },
{ moduleId: 'ELIT.3.2.5', label: 'Authorize Transactions', icon: 'fa fa-check-square', routerLink: 'authgltrans' },
{ moduleId: 'ELIT.3.2.7', label: 'Rollback Transactions', icon: 'fa fa-undo', routerLink: 'rollbackgltrans' },
{ moduleId: 'ELIT.3.2.8', label: 'Dashboard Processor', icon: 'fa fa-cog', routerLink: 'dbprocessor' }
]
},
{
moduleId: 'ELIT.3.3', label: 'Reports', icon: 'fa fa-newspaper-o',
items:
[
{ moduleId: 'ELIT.3.3.1', label: 'Ledger', icon: 'fa fa-file-text-o', routerLink: 'glledger' },
{ moduleId: 'ELIT.3.3.2', label: 'Monthly Turnover', icon: 'fa fa-file-text-o', routerLink: 'turnover' },
{ moduleId: 'ELIT.3.3.3', label: 'Trial Balance', icon: 'fa fa-file-text-o', routerLink: 'trialbalance' },
{ moduleId: 'ELIT.3.3.8', label: 'Income & Expenditure', icon: 'fa fa-file-text-o', routerLink: 'incomeexpense' },
{ moduleId: 'ELIT.3.3.4', label: 'Day Book', icon: 'fa fa-file-text-o', routerLink: 'daybook' },
{ moduleId: 'ELIT.3.3.5', label: 'Bank/Cash Book', icon: 'fa fa-file-text-o', routerLink: 'bankcashbook' },
{ moduleId: 'ELIT.3.3.6', label: 'Schedule', icon: 'fa fa-file-text-o', routerLink: 'glschedule' },
{ moduleId: 'ELIT.3.3.7', label: 'Previous Final Reports', icon: 'fa fa-file-text-o', routerLink: 'prvfinalreports' }
]
}
]
},
{
moduleId: 'ELIT.4', label: 'Order To Cash', icon: 'fa fa-money',
items:
[
{
moduleId: 'ELIT.4.1', label: 'Basic Setup', icon: 'fa fa-wrench',
items:
[
{ moduleId: 'ELIT.4.1.1', label: 'Income GL Codes', icon: 'fa fa-pencil-square-o', routerLink: 'incomeglcodes' },
{ moduleId: 'ELIT.4.1.2', label: 'VAT Liability GL Codes', icon: 'fa fa-pencil-square', routerLink: 'vatglcodes' },
{ moduleId: 'ELIT.4.1.3', label: 'AIT GL Codes', icon: 'fa fa-book', routerLink: 'aitglcodes' }
]
},
{
moduleId: 'ELIT.4.2', label: 'Regular Activities', icon: 'fa fa-building',
items:
[
{ moduleId: 'ELIT.4.2.1', label: 'Purchase Orders', icon: 'fa fa-smile-o', routerLink: 'purchaseorders' },
{ moduleId: 'ELIT.4.2.2', label: 'Invoices', icon: 'fa fa-share-square', routerLink: 'invoices' },
{ moduleId: 'ELIT.4.2.3', label: 'Payments', icon: 'fa fa-money', routerLink: 'payments' },
{ moduleId: 'ELIT.4.2.4', label: 'Freeze PO/Invoice', icon: 'fa fa-archive', routerLink: 'archivepoinvs' },
{ moduleId: 'ELIT.4.2.8', label: 'VAT Challan Update', icon: 'fa fa-sliders', routerLink: 'updvatchlano2c' }
]
},
{
moduleId: 'ELIT.4.3', label: 'Reports', icon: 'fa fa-newspaper-o',
items:
[
{ moduleId: 'ELIT.4.3.1', label: 'GL Vouchers', icon: 'fa fa-th-list', routerLink: 'glvouchers' },
{ moduleId: 'ELIT.4.3.2', label: 'Transactional Reports', icon: 'fa fa-file-text-o', routerLink: 'txnreports' },
{ moduleId: 'ELIT.4.3.3', label: 'Contract Expiry Report', icon: 'fa fa-file-text-o', routerLink: 'contexpryreports' },
{ moduleId: 'ELIT.4.3.4', label: 'Customized Report', icon: 'fa fa-file-text-o', routerLink: 'customreporto2c' },
{ moduleId: 'ELIT.4.3.5', label: 'Client Ledger', icon: 'fa fa-file-text-o', routerLink: 'clientledgero2c' },
{ moduleId: 'ELIT.4.3.6', label: 'Monthly Trends', icon: 'fa fa-file-text-o', routerLink: 'monthlytrendso2c' }
]
}
]
},
{
moduleId: 'ELIT.9', label: 'Sales System', icon: 'fa fa-sitemap',
items:
[
{
moduleId: 'ELIT.9.1', label: 'Basic Setup', icon: 'fa fa-wrench',
items:
[
{ moduleId: 'ELIT.9.1.1', label: 'Market Hierarchies', icon: 'fa fa-pencil-square', routerLink: 'markethierarchies' },
{ moduleId: 'ELIT.9.1.2', label: 'Sales Points', icon: 'fa fa-pencil-square', routerLink: 'salespoints' },
{ moduleId: 'ELIT.9.1.3', label: 'Product Hierarchies', icon: 'fa fa-pencil-square', routerLink: 'producthierarchies' },
{ moduleId: 'ELIT.9.1.4', label: 'Brands', icon: 'fa fa-pencil-square', routerLink: 'brands' },
{ moduleId: 'ELIT.9.1.5', label: 'SKUs', icon: 'fa fa-pencil-square', routerLink: 'skus' },
{ moduleId: 'ELIT.9.1.6', label: 'Channel Hierarchies', icon: 'fa fa-pencil-square', routerLink: 'channelhierarchies' },
{ moduleId: 'ELIT.9.1.7', label: 'Customers', icon: 'fa fa-pencil-square', routerLink: 'customers' },
{ moduleId: 'ELIT.9.1.8', label: 'Sales Officers', icon: 'fa fa-pencil-square', routerLink: 'salesofficers' },
{ moduleId: 'ELIT.9.1.9', label: 'Product Prices', icon: 'fa fa-pencil-square', routerLink: 'skupricess' },
{ moduleId: 'ELIT.9.1.10', label: 'Product VAT-SD-Comm', icon: 'fa fa-pencil-square', routerLink: 'skuvattaxes' },
{ moduleId: 'ELIT.9.1.11', label: 'Customer Credit', icon: 'fa fa-pencil-square', routerLink: 'customercredit' },
{ moduleId: 'ELIT.9.1.12', label: 'External Products', icon: 'fa fa-pencil-square', routerLink: 'extnlproducts' },
{ moduleId: 'ELIT.9.1.13', label: 'Sales Promotions', icon: 'fa fa-pencil-square', routerLink: 'salespromotions' },
{ moduleId: 'ELIT.9.1.14', label: 'Order Authorization Limit', icon: 'fa fa-pencil-square', routerLink: 'ordrauthlimit' }
]
},
{
moduleId: 'ELIT.9.2', label: 'Regular Activities', icon: 'fa fa-building',
items:
[
{ moduleId: 'ELIT.9.2.1', label: 'Customer Opening Balance', icon: 'fa fa-puzzle-piece', routerLink: 'slscustsopnbal' },
{ moduleId: 'ELIT.9.2.2', label: 'Customer Adjustment', icon: 'fa fa-pencil-square', routerLink: 'slscustsadjustment' },
{ moduleId: 'ELIT.9.2.3', label: 'Initial Stock', icon: 'fa fa-puzzle-piece', routerLink: 'slsinitstocks' },
{ moduleId: 'ELIT.9.2.4', label: 'Receive Stocks', icon: 'fa fa-reply-all', routerLink: 'slsrcvstocks' },
{ moduleId: 'ELIT.9.2.5', label: 'Sales Orders', icon: 'fa fa-pencil-square', routerLink: 'slsorders' },
{ moduleId: 'ELIT.9.2.6', label: 'Spot Sales', icon: 'fa fa-pencil-square', routerLink: 'slsdirectinvoices' },
{ moduleId: 'ELIT.9.2.7', label: 'Sales Invoices', icon: 'fa fa-pencil-square', routerLink: 'slsinvoices' },
{ moduleId: 'ELIT.9.2.8', label: 'Payment Against Invoices', icon: 'fa fa-archive', routerLink: 'slsinvpayments' },
{ moduleId: 'ELIT.9.2.9', label: 'Stock Transfers', icon: 'fa fa-archive', routerLink: 'slsstockxfers' },
{ moduleId: 'ELIT.9.2.10', label: 'Receive Transferred Stocks', icon: 'fa fa-archive', routerLink: 'slsstockxferrcvs' },
{ moduleId: 'ELIT.9.2.11', label: 'Fix Transfer Anomalies', icon: 'fa fa-archive', routerLink: 'slsstockxferanmls' },
{ moduleId: 'ELIT.9.2.12', label: 'Sales Return', icon: 'fa fa-archive', routerLink: 'slsrtnstocks' },
{ moduleId: 'ELIT.9.2.13', label: 'Damage Stocks', icon: 'fa fa-archive', routerLink: 'slsdmgstocks' },
{ moduleId: 'ELIT.9.2.14', label: 'Stock Take', icon: 'fa fa-archive', routerLink: 'slsstocktakes' }
]
},
{
moduleId: 'ELIT.9.3', label: 'Reports', icon: 'fa fa-newspaper-o',
items:
[
{ moduleId: 'ELIT.9.3.1', label: 'Customer Balance', icon: 'fa fa-th-list', routerLink: 'salescustbalance' },
{ moduleId: 'ELIT.9.3.2', label: 'Customer Ledger', icon: 'fa fa-th-list', routerLink: 'salescustledger' },
{ moduleId: 'ELIT.9.3.3', label: 'Current Stock', icon: 'fa fa-file-text-o', routerLink: 'salescurrentstock' },
{ moduleId: 'ELIT.9.3.4', label: 'Stock Ledger', icon: 'fa fa-th-list', routerLink: 'salesstockledger' }
]
}
]
},
{
moduleId: 'ELIT.5', label: 'Purchase To Pay', icon: 'fa fa-shopping-cart',
items:
[
{
moduleId: 'ELIT.5.1', label: 'Basic Setup', icon: 'fa fa-wrench',
items:
[
{ moduleId: 'ELIT.5.1.1', label: 'Product Types', icon: 'fa fa-address-card-o', routerLink: 'prodtypes' },
{ moduleId: 'ELIT.5.1.2', label: 'Products', icon: 'fa fa-university', routerLink: 'products' },
{ moduleId: 'ELIT.5.1.3', label: 'WO Terms & Conditions', icon: 'fa fa-pencil-square-o', routerLink: 'woterms' },
{ moduleId: 'ELIT.5.1.4', label: 'VAT & AIT Rates', icon: 'fa fa-user-circle-o', routerLink: 'aitvatratep2p' }
]
},
{
moduleId: 'ELIT.5.2', label: 'Regular Activities', icon: 'fa fa-building',
items:
[
{ moduleId: 'ELIT.5.2.0', label: 'Initial Stock', icon: 'fa fa-puzzle-piece', routerLink: 'initstocksp2p' },
{ moduleId: 'ELIT.5.2.13', label: 'Purchase Requisitions', icon: 'fa fa-puzzle-piece', routerLink: 'purchrequisitionsp2p' },
{ moduleId: 'ELIT.5.2.1', label: 'Issue Work Orders', icon: 'fa fa-smile-o', routerLink: 'workordersp2p' },
{ moduleId: 'ELIT.5.2.12', label: 'Advance Payments', icon: 'fa fa-money', routerLink: 'advpaymntsp2p' },
{ moduleId: 'ELIT.5.2.2', label: 'Receive Items at CS', icon: 'fa fa-reply-all', routerLink: 'stksrcvdatcsp2p' },
{ moduleId: 'ELIT.5.2.3', label: 'Receive Invoices', icon: 'fa fa-money', routerLink: 'invoicesp2p' },
{ moduleId: 'ELIT.5.2.4', label: 'Cash Purchase', icon: 'fa fa-archive', routerLink: 'cashpurchases' },
{ moduleId: 'ELIT.5.2.5', label: 'Payment Against Invoice', icon: 'fa fa-archive', routerLink: 'paymentsp2p' },
{ moduleId: 'ELIT.5.2.6', label: 'Goods Transfer', icon: 'fa fa-archive', routerLink: 'gdstransfers' },
{ moduleId: 'ELIT.5.2.7', label: 'Receive Goods Transfer', icon: 'fa fa-archive', routerLink: 'gdsreceives' },
{ moduleId: 'ELIT.5.2.8', label: 'Fix Transfer Anomalies', icon: 'fa fa-archive', routerLink: 'transferanomalies' },
{ moduleId: 'ELIT.5.2.9', label: 'Consumption of Goods', icon: 'fa fa-archive', routerLink: 'gdscnsmpnsp2p' },
{ moduleId: 'ELIT.5.2.10', label: 'Damage of Goods', icon: 'fa fa-archive', routerLink: 'gdsdamagesp2p' },
{ moduleId: 'ELIT.5.2.11', label: 'Stock Take of Goods', icon: 'fa fa-archive', routerLink: 'stocktakes' }
]
},
{
moduleId: 'ELIT.5.3', label: 'Reports', icon: 'fa fa-newspaper-o',
items:
[
{ moduleId: 'ELIT.5.3.1', label: 'GL Vouchers', icon: 'fa fa-th-list', routerLink: 'glvouchersp2p' },
{ moduleId: 'ELIT.5.3.2', label: 'Current Stock', icon: 'fa fa-file-text-o', routerLink: 'currentstockp2p' },
{ moduleId: 'ELIT.5.3.3', label: 'Stock Ledger', icon: 'fa fa-file-text-o', routerLink: 'stockledgerp2p' },
{ moduleId: 'ELIT.5.3.4', label: 'Supplier Ledger', icon: 'fa fa-file-text-o', routerLink: 'supplierledgerp2p' }
]
}
]
},
{
moduleId: 'ELIT.6', label: 'Fixed Assets', icon: 'fa fa-sitemap',
items:
[
{
moduleId: 'ELIT.6.1', label: 'Basic Setup', icon: 'fa fa-wrench',
items:
[
{ moduleId: 'ELIT.6.1.1', label: 'Asset Types', icon: 'fa fa-address-card-o', routerLink: 'assettypes' },
{ moduleId: 'ELIT.6.1.2', label: 'Asset Categories', icon: 'fa fa-university', routerLink: 'assetcategories' },
{ moduleId: 'ELIT.6.1.3', label: 'Asset Items', icon: 'fa fa-credit-card', routerLink: 'assetitems' },
{ moduleId: 'ELIT.6.1.6', label: 'Asset Hierarchies', icon: 'fa fa-credit-card', routerLink: 'assethierarchies' },
{ moduleId: 'ELIT.6.1.4', label: 'Locations', icon: 'fa fa-pencil-square', routerLink: 'locations' },
{ moduleId: 'ELIT.6.1.5', label: 'VAT & AIT Rates', icon: 'fa fa-user-circle-o', routerLink: 'aitvatratefa' }
]
},
{
moduleId: 'ELIT.6.2', label: 'Regular Activities', icon: 'fa fa-building',
items:
[
{ moduleId: 'ELIT.6.2.0', label: 'Initial Fixed Assets', icon: 'fa fa-puzzle-piece', routerLink: 'initstocksfa' },
{ moduleId: 'ELIT.6.2.15', label: 'Purchase Requisitions', icon: 'fa fa-puzzle-piece', routerLink: 'purchrequisitionsfa' },
{ moduleId: 'ELIT.6.2.1', label: 'Issue Work Orders', icon: 'fa fa-smile-o', routerLink: 'workordersfa' },
{ moduleId: 'ELIT.6.2.14', label: 'Advance Payments', icon: 'fa fa-money', routerLink: 'advpaymntsfa' },
{ moduleId: 'ELIT.6.2.2', label: 'Received Items At CS', icon: 'fa fa-reply-all', routerLink: 'stksrcvdatcsfa' },
{ moduleId: 'ELIT.6.2.3', label: 'Receive Invoices', icon: 'fa fa-money', routerLink: 'invoicesfa' },
{ moduleId: 'ELIT.6.2.4', label: 'Payment Against Invoice', icon: 'fa fa-archive', routerLink: 'paymentsfa' },
{ moduleId: 'ELIT.6.2.5', label: 'Item Acquisition', icon: 'fa fa-smile-o', routerLink: 'itemsacquisition' },
{ moduleId: 'ELIT.6.2.5A', label: 'Update Item Data', icon: 'fa fa-smile-o', routerLink: 'updfaitmdescription' },
{ moduleId: 'ELIT.6.2.6', label: 'Item Installation', icon: 'fa fa-share-square', routerLink: 'astinstalns' },
{ moduleId: 'ELIT.6.2.7', label: 'Transfer Fixed Asset', icon: 'fa fa-money', routerLink: 'assetxfers' },
{ moduleId: 'ELIT.6.2.8', label: 'Write Off', icon: 'fa fa-archive', routerLink: 'astwriteoffs' },
{ moduleId: 'ELIT.6.2.9', label: 'Asset Sales', icon: 'fa fa-archive', routerLink: 'astsales' },
{ moduleId: 'ELIT.6.2.10', label: 'Add Asset Value', icon: 'fa fa-archive', routerLink: 'astaddvalues' },
{ moduleId: 'ELIT.6.2.11', label: 'Reduce Asset Value', icon: 'fa fa-archive', routerLink: 'assetreducesvalue' },
{ moduleId: 'ELIT.6.2.12', label: 'No Depreciation Mark', icon: 'fa fa-user-circle-o', routerLink: 'nodeprnmark' },
{ moduleId: 'ELIT.6.2.13', label: 'Depreciation Calculation', icon: 'fa fa-user-circle-o', routerLink: 'calcdepreciation' }
]
},
{
moduleId: 'ELIT.6.3', label: 'Reports', icon: 'fa fa-newspaper-o',
items:
[
{ moduleId: 'ELIT.6.3.1', label: 'GL Vouchers', icon: 'fa fa-th-list', routerLink: 'glvouchersfa' },
{ moduleId: 'ELIT.6.3.2', label: 'Current Stock', icon: 'fa fa-file-text-o', routerLink: 'currentstockfa' },
{ moduleId: 'ELIT.6.3.3', label: 'Stock Ledger', icon: 'fa fa-file-text-o', routerLink: 'stockledgerfa' },
{ moduleId: 'ELIT.6.3.4', label: 'Item Details', icon: 'fa fa-file-text-o', routerLink: 'faitemdetail' },
{ moduleId: 'ELIT.6.3.5', label: 'Fixed Asset List', icon: 'fa fa-file-text-o', routerLink: 'faitemsatglance' },
{ moduleId: 'ELIT.6.3.6', label: 'Waiting Acquisition', icon: 'fa fa-file-text-o', routerLink: 'faitemswtngaqstn' },
{ moduleId: 'ELIT.6.3.7', label: 'Acquired Assets', icon: 'fa fa-file-text-o', routerLink: 'faitemsacquire' },
{ moduleId: 'ELIT.6.3.8', label: 'Waiting Installation', icon: 'fa fa-file-text-o', routerLink: 'faitemswtnginstall' },
{ moduleId: 'ELIT.6.3.9', label: 'No Depreciation Marked', icon: 'fa fa-file-text-o', routerLink: 'faitemsnodprn' },
{ moduleId: 'ELIT.6.3.10', label: 'Depreciation Report', icon: 'fa fa-file-text-o', routerLink: 'faitemsdprnrpt' },
{ moduleId: 'ELIT.6.3.11', label: 'Supplier Ledger', icon: 'fa fa-file-text-o', routerLink: 'fasupplierledger' }
]
}
]
},
{
moduleId: 'ELIT.7', label: 'Task Management', icon: 'fa fa-users',
items:
[
{
moduleId: 'ELIT.7.1', label: 'Basic Setup', icon: 'fa fa-wrench',
items:
[
{ moduleId: 'ELIT.7.1.1', label: 'Developers', icon: 'fa fa-user-circle-o', routerLink: 'developers' },
{ moduleId: 'ELIT.7.1.2', label: 'Support Teams', icon: 'fa fa-user-circle-o', routerLink: 'supportteams' },
{ moduleId: 'ELIT.7.1.2A', label: 'QA Teams', icon: 'fa fa-user-circle-o', routerLink: 'qateams' },
{ moduleId: 'ELIT.7.1.3', label: 'Priorities', icon: 'fa fa-check-square-o', routerLink: 'priorities' },
{ moduleId: 'ELIT.7.1.4', label: 'Task Statuses', icon: 'fa fa-pencil-square', routerLink: 'taskstatuses' },
{ moduleId: 'ELIT.7.1.5', label: 'Task Categories', icon: 'fa fa-check-square-o', routerLink: 'taskcategories' },
{ moduleId: 'ELIT.7.1.6', label: 'Task Types', icon: 'fa fa-check-square-o', routerLink: 'tasktypes' },
{ moduleId: 'ELIT.7.1.7', label: 'Team Spaces', icon: 'fa fa-user-circle-o', routerLink: 'teamspaces' },
{ moduleId: 'ELIT.7.1.9', label: 'User Projects', icon: 'fa fa-product-hunt', routerLink: 'tsuserprojects' }
]
},
{
moduleId: 'ELIT.7.2', label: 'Regular Activities', icon: 'fa fa-building',
items:
[
{ moduleId: 'ELIT.7.2.1', label: 'Tasks', icon: 'fa fa-smile-o', routerLink: 'tasks' },
{ moduleId: 'ELIT.7.2.2', label: 'My Tasks', icon: 'fa fa-tasks', routerLink: 'mytasks' }
]
},
{
moduleId: 'ELIT.7.3', label: 'Reports', icon: 'fa fa-newspaper-o',
items: [{ moduleId: 'ELIT.7.3.1', label: 'Task Reports', icon: 'fa fa-file-text-o', routerLink: 'taskreport' }]
}
]
},
{
moduleId: 'ELIT.8', label: 'Attendance System', icon: 'fa fa-calendar',
items:
[
{
moduleId: 'ELIT.8.1', label: 'Basic Setup', icon: 'fa fa-wrench',
items:
[
{ moduleId: 'ELIT.8.1.1', label: 'Ip/Mac Entries', icon: 'fa fa-user-circle-o', routerLink: 'ipmacentries' },
{ moduleId: 'ELIT.8.1.2', label: 'Holiday Calendar', icon: 'fa fa-calendar', routerLink: 'calendar' },
{ moduleId: 'ELIT.8.1.3', label: 'Ramadan Calendar', icon: 'fa fa-calendar', routerLink: 'ramadan' }
]
},
{
moduleId: 'ELIT.8.2', label: 'Regular Activities', icon: 'fa fa-building',
items:
[
{ moduleId: 'ELIT.8.2.1', label: 'Employee Leaves', icon: 'fa fa-smile-o', routerLink: 'empleaves' },
{ moduleId: 'ELIT.8.2.2', label: 'Late Entries', icon: 'fa fa-tasks', routerLink: 'emplates' },
{ moduleId: 'ELIT.8.2.3', label: 'Client Visits', icon: 'fa fa-tasks', routerLink: 'empclntvsts' },
{ moduleId: 'ELIT.8.2.4', label: 'Home Offices', icon: 'fa fa-tasks', routerLink: 'emphmofcs' },
{ moduleId: 'ELIT.8.2.6', label: 'Asset Bookings', icon: 'fa fa-tasks', routerLink: 'assetbookings' }
]
},
{
moduleId: 'ELIT.8.3', label: 'Reports', icon: 'fa fa-newspaper-o',
items:
[
{ moduleId: 'ELIT.8.3.1', label: 'Login History', icon: 'fa fa-tasks', routerLink: 'loginhistory' },
{ moduleId: 'ELIT.8.3.2', label: 'Attendance Reports', icon: 'fa fa-file-text-o', routerLink: 'atnreports' }
]
}
]
}
];
/**
* Keeps the nodes the user is permitted to see.
*
* A group is kept only when its own key is permitted and at least one of its children survives,
* a page is kept only when its key is permitted and the route is actually registered in the app,
* so a permitted but not yet implemented page never lands in the sidebar as a dead link.
*/
export function buildPermittedMenu(nodes: MenuNode[], moduleIds: string[], routeExists: (routerLink: string) => boolean): MenuNode[]
{
const permitted: MenuNode[] = [];
for (const node of nodes ?? [])
{
if (node.moduleId && !moduleIds?.includes(node.moduleId))
continue;
if (node.items?.length)
{
const items = buildPermittedMenu(node.items, moduleIds, routeExists);
if (items.length)
permitted.push({ moduleId: node.moduleId, label: node.label, icon: node.icon, items: items });
}
else if (node.routerLink && routeExists(node.routerLink))
{
permitted.push({ moduleId: node.moduleId, label: node.label, icon: node.icon, routerLink: node.routerLink });
}
}
return permitted;
}

View File

@ -1,35 +1,39 @@
export class UserModel
{
id: number;
logId: number;
expires?: Date;
loginId: string;
userName: string;
systemDate: Date;
idleTime: number;
pingTime: number;
loginTime: string;
logoutTime?: Date;
validUser: boolean;
employeeId: number;
timeoutTime: number;
loginStatus: number;
bmProcessId: number;
prProcessId: number;
dbOnStartup: boolean;
batchEnabled: boolean;
viewOwnTaskOnly: boolean;
authMethod: number;
notificationCount: number;
pwdChangeRequired: boolean;
authenticationToken?: string;
authRequiredAtLogin: boolean;
idsValue: string;
moduleIds: any[] = [];
//Returned by the login endpoint since the token based authentication
refreshToken?: string;
userPlatFormType?: number;
userRoleType?: string;
employeeNumber?: string;
//Permission keys of the logged in user, "<moduleId>" = select, "_1" = add, "_2" = edit, "_3" = delete
moduleIds: string[] = [];
returnStatus: number;
returnMessage: any[] = [];
validationErrors: any[] = [];
//Not returned by the login endpoint anymore, defaults are applied on the client
logId?: number;
idleTime?: number;
pingTime?: number;
employeeId?: number;
timeoutTime?: number;
dbOnStartup?: boolean;
schemeName: string = 'dark';
themeName: string = 'yellow';
menuLayout: string = 'overlay';

View File

@ -1,7 +1,7 @@
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()" style="background-image:linear-gradient(360deg, #17c3f0,#1D81b1);">
<div class="mb-2 login-body">
<div class="login-content">
<h1 style="color:green;">Ease Taskforce</h1>
<h1 style="color:green;">Auto Crop</h1>
<p style="color: black;">Please use the form to Sign-in.</p>
<div class="mb-3 col-lg-4 col-md-8 col-sm-12">
@ -41,38 +41,7 @@
<input type="checkbox" id="remberMe" name="remberMe" formControlName="remberMe" class="form-check-input" />
<label class="form-check-label" for="remberMe" style="font-weight:500;color:black;">Remember me</label>
</div>
@if (attendanceEnabled)
{
<div class="mb-1 form-check form-switch">
<input type="checkbox" id="atndnceLogin" name="atndnceLogin" formControlName="atndnceLogin" class="form-check-input" (change)="eventCheck($event)" />
<label class="form-check-label" for="atndnceLogin" style="font-weight:500;color:maroon;">Login into Attendance System</label>
</div>
}
@if (remarksRequired && f.atndnceLogin.value)
{
<div class="mb-2 col-lg-4 col-md-8 col-sm-12">
<span class="required" style="color: black;">Late Login Remarks:</span>
<br />
<kendo-combobox [data]="lateLoginRemarks"
id="loginRemarks"
name="loginRemarks"
formControlName="loginRemarks"
[textField]="'itemValue'"
[valueField]="'itemValue'"
[valuePrimitive]="true"
[placeholder]="'Late Login Remarks...'"
[kendoDropDownFilter]="filterSettings"
[ngClass]="{ 'k-invalid': submitted && f.loginRemarks.errors}">
</kendo-combobox>
@if (submitted && f.loginRemarks.errors)
{
<div class="invalid-feedback">
Late Login Remarks is required
</div>
}
</div>
}
<div class="mt-3 mb-4">
<button style="min-width:6.2em;" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-raised ui-button-text-only">

View File

@ -27,6 +27,7 @@ import { AppConfig, ComputerConfig } from '../components/models/app.config.model
})
export class LoginComponent implements OnInit
{
private readonly router = inject(Router);
private readonly route = inject(ActivatedRoute);
private readonly modalService = inject(NgbModal);
@ -58,17 +59,15 @@ export class LoginComponent implements OnInit
constructor()
{
debugger;
this.alertService.clear();
const configSvc = inject(ConfigurationService);
this.appConfig = configSvc.configuration?.appConfig;
this.compConfig = configSvc.configuration?.computerConfig;
debugger;
if (this.authService.currentUserValue?.id)
{
if (this.authService.currentUserValue.dbOnStartup)
this.router.navigate(['/dashboard']);
else
this.router.navigate(['/home']);
this.router.navigate(['/home']);
}
else
{
@ -79,6 +78,7 @@ export class LoginComponent implements OnInit
ngOnInit(): void
{
debugger;
this.alertService.clear();
this.spinnerService.hide();
this.appVersion = packageJson.version;
@ -132,6 +132,7 @@ export class LoginComponent implements OnInit
private loadLoginRemarks(): void
{
debugger;
const autId = this.fnSvc.cipherData(Api.cipherSecretKey ?? '');
this.authService.getLoginRemarks(autId).subscribe(
{
@ -266,110 +267,107 @@ export class LoginComponent implements OnInit
{
this.loading = false;
this.spinnerService.hide();
//The endpoint answers 200 only for a valid user, guard anyway so a partial
//response never leaves a half logged in session behind
if (!data?.validUser || !data.id || !data.authenticationToken)
{
this.abortLogin(this.parseReturnMessage(data) || 'Login failed, please try again.');
return;
}
if (data.authRequiredAtLogin)
{
this.authService.loggedIn(data, false);
const modalOptions: NgbModalOptions = { backdrop: 'static', keyboard: false, backdropClass: 'customBackdrop', centered: true, windowClass: 'misc-width' };
const modalRef = this.modalService.open(OtpComponent, modalOptions);
modalRef.componentInstance.fromParent = { userId: data.id, authMethod: data.authMethod };
modalRef.result.then((result) =>
{
if (result)
{
if (data.pwdChangeRequired)
{
this.authService.loggedIn(data, false);
const modalOptions1: NgbModalOptions = { backdrop: 'static', keyboard: false, backdropClass: 'customBackdrop', centered: true };
const modalRef1 = this.modalService.open(ChangePwdComponent, modalOptions1);
modalRef1.componentInstance.fromParent = { userId: data.id, loginId: data.loginId, userName: data.userName };
modalRef1.result.then(() =>
{
this.authService.loggedout();
});
}
else
{
this.authService.refreshToken().subscribe(
{
next: () =>
{
localStorage.setItem('OnlineSalesAutoCrop.sysdate', sysDate);
if (this.f.remberMe.value)
localStorage.setItem('OnlineSalesAutoCrop.rlap', loginInf);
else
localStorage.removeItem('OnlineSalesAutoCrop.rlap');
this.idleSvc.startMonitor();
this.authService.raiseLoginEvent(data.id, true);
const returnUrl = this.authService.currentUserValue?.dbOnStartup ? this.route.snapshot.queryParams['returnUrl'] ?? '/dashboard' : this.route.snapshot.queryParams['returnUrl'] ?? '/home';
this.router.navigate([returnUrl]);
},
error: () =>
{
this.authService.loggedout();
}
});
}
}
else
{
localStorage.removeItem('OnlineSalesAutoCrop.rlap');
localStorage.removeItem('OnlineSalesAutoCrop.sysdate');
this.authService.loggedout();
}
});
this.verifyOtp(data, sysDate, loginInf);
}
else if (data.pwdChangeRequired)
{
this.authService.loggedIn(data, false);
const modalOptions: NgbModalOptions = { backdrop: 'static', keyboard: false, backdropClass: 'customBackdrop', centered: true };
const modalRef = this.modalService.open(ChangePwdComponent, modalOptions);
modalRef.componentInstance.fromParent = { userId: data.id, loginId: data.loginId, userName: data.userName };;
modalRef.result.then(() =>
{
this.authService.loggedout();
});
this.changePassword(data);
}
else
{
this.authService.refreshToken().subscribe(
{
next: () =>
{
localStorage.setItem('OnlineSalesAutoCrop.sysdate', sysDate);
if (this.f.remberMe.value)
localStorage.setItem('OnlineSalesAutoCrop.rlap', loginInf);
else
localStorage.removeItem('OnlineSalesAutoCrop.rlap');
this.idleSvc.startMonitor();
this.authService.raiseLoginEvent(data.id, true);
const returnUrl = this.authService.currentUserValue?.dbOnStartup ? this.route.snapshot.queryParams['returnUrl'] ?? '/dashboard' : this.route.snapshot.queryParams['returnUrl'] ?? '/home';
this.router.navigate([returnUrl]);
},
error: () =>
{
localStorage.removeItem('OnlineSalesAutoCrop.rlap');
localStorage.removeItem('OnlineSalesAutoCrop.sysdate');
this.authService.loggedout();
}
});
this.completeLogin(data, sysDate, loginInf);
}
},
error: (errorMessage: string) =>
{
this.loading = false;
this.spinnerService.hide();
localStorage.removeItem('OnlineSalesAutoCrop.rlap');
localStorage.removeItem('OnlineSalesAutoCrop.sysdate');
this.alertService.error(errorMessage);
this.abortLogin(errorMessage);
}
});
}
/** Second factor, the user is cached already so the otp call travels with the token. */
private verifyOtp(data: any, sysDate: string, loginInf: string): void
{
this.authService.loggedIn(data, false);
const modalOptions: NgbModalOptions = { backdrop: 'static', keyboard: false, backdropClass: 'customBackdrop', centered: true, windowClass: 'misc-width' };
const modalRef = this.modalService.open(OtpComponent, modalOptions);
modalRef.componentInstance.fromParent = { userId: data.id, authMethod: data.authMethod };
modalRef.result.then((result) =>
{
if (!result)
{
this.abortLogin('');
return;
}
if (data.pwdChangeRequired)
this.changePassword(data);
else
this.completeLogin(data, sysDate, loginInf);
},
() => this.abortLogin(''));
}
/** Expired/first time password, the user has to sign in again with the new one. */
private changePassword(data: any): void
{
this.authService.loggedIn(data, false);
const modalOptions: NgbModalOptions = { backdrop: 'static', keyboard: false, backdropClass: 'customBackdrop', centered: true };
const modalRef = this.modalService.open(ChangePwdComponent, modalOptions);
modalRef.componentInstance.fromParent = { userId: data.id, loginId: data.loginId, userName: data.userName };
modalRef.result.then(() => this.authService.loggedout(), () => this.authService.loggedout());
}
/**
* The login response already carries the authentication token, the refresh token and the
* permission keys (moduleIds), so nothing else has to be fetched before entering the app.
* Raising the login event makes the sidebar rebuild itself from those permission keys.
*/
private completeLogin(data: any, sysDate: string, loginInf: string): void
{
localStorage.setItem('OnlineSalesAutoCrop.sysdate', sysDate);
if (this.f.remberMe.value)
localStorage.setItem('OnlineSalesAutoCrop.rlap', loginInf);
else
localStorage.removeItem('OnlineSalesAutoCrop.rlap');
this.idleSvc.startMonitor();
this.authService.raiseLoginEvent(data.id, true);
const returnUrl = this.route.snapshot.queryParams['returnUrl'] ?? '/home';
this.router.navigate([returnUrl]);
}
private abortLogin(errorMessage: string): void
{
this.loading = false;
this.spinnerService.hide();
localStorage.removeItem('OnlineSalesAutoCrop.rlap');
localStorage.removeItem('OnlineSalesAutoCrop.sysdate');
this.authService.loggedout();
if (errorMessage)
this.alertService.error(errorMessage);
}
private parseReturnMessage(data: any): string
{
return Array.isArray(data?.returnMessage) ? data.returnMessage.join('<br/>') : '';
}
}

View File

@ -0,0 +1,58 @@
<form [formGroup]="uploadPwdForm" (ngSubmit)="onSubmit()">
<div class="mb-2">
<span>Excel file must have <b>EmployeeCode</b> and <b>Password</b> columns in the first row.</span>
</div>
<div class="mb-2 row g-0">
<span class="required">Excel File:</span>
<br />
<div class="col-lg-4 col-md-8 col-sm-12">
<div class="custom-file">
<input type="file" class="custom-file-input" accept="{{acceptFileType}}" formControlName="uploadFile" name="uploadFile" id="uploadFile" (change)="onFileSelected($event)" [ngClass]="{ 'is-invalid': submitted && f.fileName.errors }">
<label class="custom-file-label" #uploadFileLabel for="uploadFile" data-browse="Browse"><i class="fa fa-search button-search"></i>&nbsp;Choose file</label>
</div>
@if (submitted && f.fileName.errors)
{
<div class="invalid-feedback d-block">
<span>Please select an excel file to upload.</span>
</div>
}
</div>
</div>
<div class="mb-2 col-lg-4 col-md-8 col-sm-12">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="forcePasswordChange" formControlName="forcePasswordChange">
<label class="form-check-label" for="forcePasswordChange">Force user to change password at next login</label>
</div>
</div>
@if (canSave)
{
<div class="mb-2">
<hr class="osds" />
<button class="ui-button ui-widget ui-state-default ui-corner-all ui-button-raised ui-button-text-only" [disabled]="loading">
<span class="ui-button-text-only ui-c"><i class="fa fa-upload button-edit" aria-hidden="true"></i>&nbsp;&nbsp;Upload &amp; Update</span>
</button>
</div>
}
@if (totalRows > 0)
{
<div class="mb-2">
<span>Total Row(s): <b>{{totalRows}}</b> | Updated: <b>{{successCount}}</b> | Failed: <b>{{failedCount}}</b></span>
<br />
<kendo-grid id="grdUploadPwd"
[data]="items"
[resizable]="true"
[loading]="loading"
[rowClass]="rowCallback"
class="grid-font-900">
<kendo-grid-column field="rowNo" title="Row" [width]="70" class="cell-padding" [headerStyle]="{'font-weight':'bold'}" [style]="{'text-align':'center'}"></kendo-grid-column>
<kendo-grid-column field="employeeCode" title="Employee Code" [width]="150" class="cell-padding" [headerStyle]="{'font-weight':'bold'}"></kendo-grid-column>
<kendo-grid-column field="loginId" title="Login Id" [width]="150" class="cell-padding" [headerStyle]="{'font-weight':'bold'}"></kendo-grid-column>
<kendo-grid-column field="message" title="Remarks" class="cell-padding" [headerStyle]="{'font-weight':'bold'}"></kendo-grid-column>
</kendo-grid>
</div>
}
</form>

View File

@ -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);
}
});
}
}

View File

@ -25,7 +25,7 @@ export class AuthGuard
let keys: string[] = permissionkey.split(',');
for (const key of keys)
{
found = currentUser.moduleIds.includes(key);
found = currentUser.moduleIds?.includes(key) === true;
if (found)
break;
}
@ -33,7 +33,7 @@ export class AuthGuard
if (found === false)
this.router.navigate(['/accessdenied']);
return true;
return found;
}
}
else

View File

@ -21,6 +21,7 @@ export class AuthenticationService
constructor()
{
debugger;
this.sessionEvent = new EventEmitter();
this.taskStatusEvent = new EventEmitter();
this.notificationEvent = new EventEmitter();
@ -58,12 +59,7 @@ export class AuthenticationService
return this.api.postAsync<any>('v1/users/login', params)
.pipe(map(user =>
{
user.moduleIds = JSON.parse(this.functionService.decipherData(user.idsValue));
user.idsValue = '';
const encString: string = this.functionService.encrypt(JSON.stringify(user));
localStorage.setItem('OnlineSalesAutoCrop.currentUser', encString);
this.currentUserSubject.next(user);
this.storeCurrentUser(user);
return user;
}));
}
@ -73,6 +69,31 @@ export class AuthenticationService
}
}
/**
* The login endpoint returns the permission keys in `moduleIds` and no longer returns the
* theme/idle settings, so the client defaults are applied here before the user is cached.
*/
public storeCurrentUser(user: any): void
{
user.moduleIds = user.moduleIds ?? [];
const defaults = new UserModel();
user.themeName = user.themeName || defaults.themeName;
user.schemeName = user.schemeName || defaults.schemeName;
user.menuLayout = user.menuLayout || defaults.menuLayout;
const encString: string = this.functionService.encrypt(JSON.stringify(user));
localStorage.setItem('OnlineSalesAutoCrop.currentUser', encString);
this.currentUserSubject.next(user);
}
/** True when the permission key is granted, pass "<moduleId>_1|_2|_3" to check add/edit/delete. */
public hasPermission(moduleId: string): boolean
{
return this.currentUserValue?.moduleIds?.includes(moduleId) === true;
}
public refreshToken()
{
try
@ -98,13 +119,13 @@ export class AuthenticationService
{
try
{
if (!this.currentUserValue?.logId)
if (!this.currentUserValue?.id)
{
localStorage.removeItem('OnlineSalesAutoCrop.currentUser');
return;
}
const params: any = { logId: this.currentUserValue.logId, attendanceLogout: attendanceLogout, ipAddress: ipAddress, macAddress: macAddress, hostName: hostName, logoutRemarks: logoutRemarks };
const params: any = { logId: this.currentUserValue.logId ?? 0, attendanceLogout: attendanceLogout, ipAddress: ipAddress, macAddress: macAddress, hostName: hostName, logoutRemarks: logoutRemarks };
return this.api.postAsync<any>('v1/users/logout', params)
.pipe(map(value =>
{
@ -141,7 +162,7 @@ export class AuthenticationService
{
try
{
const params: any = { logId: this.currentUserValue.logId };
const params: any = { logId: this.currentUserValue?.logId ?? 0 };
return this.api.postAsync<any>('v1/users/sessionExpired', params)
.pipe(map(value =>
{

View File

@ -2,7 +2,9 @@ import { inject, Injectable } from '@angular/core';
import { Base } from '../base/base';
@Injectable()
@Injectable({
providedIn: 'root'
})
export class UserService extends Base
{
public addUser(params: any)
@ -112,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 };

View File

@ -0,0 +1,3 @@
{
"routes": {}
}