diff --git a/.github/workflows/dotnet-developPublish.yml b/.github/workflows/dotnet-developPublish.yml index a594b21a..4053dc8c 100644 --- a/.github/workflows/dotnet-developPublish.yml +++ b/.github/workflows/dotnet-developPublish.yml @@ -5,8 +5,6 @@ on: branches: - Main -env: - DOTNET_ENVIRONMENT: Development jobs: build-and-deploy: @@ -37,12 +35,11 @@ jobs: & "C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe" ` -verb:sync ` -source:contentPath="$publishFolder" ` - -dest:contentPath="dadmehrg",computerName="https://171.22.24.15:8172/msdeploy.axd?site=dadmehrg",userName=".\deployuser",password="R2rNpdnetP3j>q5b18",authType="Basic" ` + -dest:contentPath="dadmehrg",computerName="https://$env:SERVER_HOST:8172/msdeploy.axd?site=gozareshgir",userName="$env:DEPLOY_USER",password="$env:DEPLOY_PASSWORD",authType="Basic" ` -allowUntrusted ` -enableRule:AppOffline - - + env: - SERVER_HOST: your-server-ip-or-domain + SERVER_HOST: 171.22.24.15 DEPLOY_USER: ${{ secrets.DEPLOY_USER }} DEPLOY_PASSWORD: ${{ secrets.DEPLOY_PASSWORD }} diff --git a/0_Framework/0_Framework.csproj b/0_Framework/0_Framework.csproj index 1de86aff..66522232 100644 --- a/0_Framework/0_Framework.csproj +++ b/0_Framework/0_Framework.csproj @@ -1,24 +1,44 @@  - net8.0 + net10.0 _0_Framework - - - - - - + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + diff --git a/0_Framework/Application/AppSettingConfiguration.cs b/0_Framework/Application/AppSettingConfiguration.cs index 414949f1..127f11d5 100644 --- a/0_Framework/Application/AppSettingConfiguration.cs +++ b/0_Framework/Application/AppSettingConfiguration.cs @@ -3,4 +3,6 @@ public class AppSettingConfiguration { public string Domain { get; set; } + public string ClientDomain =>"client"+Domain; + public string AdminDomain =>"admin"+Domain; } \ No newline at end of file diff --git a/0_Framework/Application/AuthHelper.cs b/0_Framework/Application/AuthHelper.cs index ef510830..2af2188f 100644 --- a/0_Framework/Application/AuthHelper.cs +++ b/0_Framework/Application/AuthHelper.cs @@ -56,6 +56,11 @@ public class AuthHelper : IAuthHelper return Tools.DeserializeFromBsonList(permissions); //Mahan } + public bool HasPermission(int permission) + { + return GetPermissions().Any(x => x == permission); + } + public long CurrentAccountId() { return IsAuthenticated() @@ -198,7 +203,8 @@ public class AuthHelper : IAuthHelper new("workshopList",workshopBson), new("WorkshopSlug",slug), new("WorkshopId", account.WorkshopId.ToString()), - new("WorkshopName",account.WorkshopName??"") + new("WorkshopName",account.WorkshopName??""), + new("pm.userId", account.PmUserId.ToString()), }; diff --git a/0_Framework/Application/AuthViewModel.cs b/0_Framework/Application/AuthViewModel.cs index d419bd67..aebda696 100644 --- a/0_Framework/Application/AuthViewModel.cs +++ b/0_Framework/Application/AuthViewModel.cs @@ -27,10 +27,12 @@ public class AuthViewModel #endregion public long SubAccountId { get; set; } + public long? PmUserId { get; set; } public AuthViewModel(long id, long roleId, string fullname, string username, string mobile,string profilePhoto, - List permissions, string roleName, string adminAreaPermission, string clientAriaPermission, int? positionValue, long subAccountId = 0) + List permissions, string roleName, string adminAreaPermission, string clientAriaPermission, int? positionValue, + long subAccountId = 0,long? pmUserId = null) { Id = id; RoleId = roleId; @@ -44,6 +46,7 @@ public class AuthViewModel ClientAriaPermission = clientAriaPermission; PositionValue = positionValue; SubAccountId = subAccountId; + PmUserId = pmUserId; } public AuthViewModel() diff --git a/0_Framework/Application/Enums/InstitutionContractVerificationStatus.cs b/0_Framework/Application/Enums/InstitutionContractVerificationStatus.cs new file mode 100644 index 00000000..2739524a --- /dev/null +++ b/0_Framework/Application/Enums/InstitutionContractVerificationStatus.cs @@ -0,0 +1,22 @@ +namespace _0_Framework.Application.Enums; + +/// +/// وضعیت تایید قرادا مالی +/// +public enum InstitutionContractVerificationStatus +{ + /// + /// در انتظار تایید + /// + PendingForVerify = 0, + + /// + /// در انتظار کارپوشه + /// + PendingWorkflow = 1, + + /// + /// تایید شده + /// + Verified = 2 +} \ No newline at end of file diff --git a/0_Framework/Application/Enums/TypeOfCheckoutWarning.cs b/0_Framework/Application/Enums/TypeOfCheckoutWarning.cs new file mode 100644 index 00000000..45c82a53 --- /dev/null +++ b/0_Framework/Application/Enums/TypeOfCheckoutWarning.cs @@ -0,0 +1,15 @@ +namespace _0_Framework.Application.Enums; + +public enum TypeOfCheckoutWarning +{ + /// + /// هشدار های متفرقه + /// + OthersWarning, + /// + /// هشدار سهم بیمه کارگر + /// + InsuranceEmployeeShare, + + +} \ No newline at end of file diff --git a/0_Framework/Application/Enums/TypeOfSmsSetting.cs b/0_Framework/Application/Enums/TypeOfSmsSetting.cs new file mode 100644 index 00000000..6d66fc47 --- /dev/null +++ b/0_Framework/Application/Enums/TypeOfSmsSetting.cs @@ -0,0 +1,41 @@ +namespace _0_Framework.Application.Enums; + +public enum TypeOfSmsSetting +{ + + /// + /// پیامک + /// یادآور بدهی ماهیانه قرارداد مالی + /// + InstitutionContractDebtReminder, + + /// + /// پیامک + /// صورت حساب ماهانه قرارداد مالی + /// + MonthlyInstitutionContract, + + /// + /// پیامک + /// اعلام مسدودی طرف حساب + /// + BlockContractingParty, + + /// + /// پیامک + /// هشدار اول + /// + Warning, + + + /// + ///پیامک اقدام قضائی + /// + LegalAction, + + /// + /// پیامک تایید قراداد + /// + InstitutionContractConfirm, + +} \ No newline at end of file diff --git a/0_Framework/Application/FaceEmbedding/IFaceEmbeddingNotificationService.cs b/0_Framework/Application/FaceEmbedding/IFaceEmbeddingNotificationService.cs new file mode 100644 index 00000000..0c00577f --- /dev/null +++ b/0_Framework/Application/FaceEmbedding/IFaceEmbeddingNotificationService.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; + +namespace _0_Framework.Application.FaceEmbedding; + +/// +/// سرویس اطلاع‌رسانی تغییرات Face Embedding +/// +public interface IFaceEmbeddingNotificationService +{ + /// + /// اطلاع‌رسانی ایجاد یا به‌روزرسانی Embedding + /// + Task NotifyEmbeddingCreatedAsync(long workshopId, long employeeId, string employeeFullName); + + /// + /// اطلاع‌رسانی حذف Embedding + /// + Task NotifyEmbeddingDeletedAsync(long workshopId, long employeeId); + + /// + /// اطلاع‌رسانی بهبود Embedding + /// + Task NotifyEmbeddingRefinedAsync(long workshopId, long employeeId); +} + diff --git a/0_Framework/Application/FaceEmbedding/IFaceEmbeddingService.cs b/0_Framework/Application/FaceEmbedding/IFaceEmbeddingService.cs new file mode 100644 index 00000000..78dbf735 --- /dev/null +++ b/0_Framework/Application/FaceEmbedding/IFaceEmbeddingService.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace _0_Framework.Application.FaceEmbedding; + +public interface IFaceEmbeddingService +{ + Task GenerateEmbeddingsAsync(long employeeId, long workshopId, string employeeFullName, string picture1Path, string picture2Path); + Task GenerateEmbeddingsFromStreamAsync(long employeeId, long workshopId, string employeeFullName, Stream picture1Stream, Stream picture2Stream); + Task RefineEmbeddingAsync(long employeeId, long workshopId, float[] embedding, float confidence, Dictionary metadata = null); + Task DeleteEmbeddingAsync(long employeeId, long workshopId); + Task> GetEmbeddingAsync(long employeeId, long workshopId); + Task UpdateEmbeddingFullNameAsync(long employeeId, long workshopId, string newFullName); +} + +public class FaceEmbeddingResponse +{ + public long EmployeeId { get; set; } + public long WorkshopId { get; set; } + public string EmployeeFullName { get; set; } + public float[] Embedding { get; set; } + public float Confidence { get; set; } + public Dictionary Metadata { get; set; } +} diff --git a/0_Framework/Application/IAuthHelper.cs b/0_Framework/Application/IAuthHelper.cs index ab9cf572..d43ac069 100644 --- a/0_Framework/Application/IAuthHelper.cs +++ b/0_Framework/Application/IAuthHelper.cs @@ -12,6 +12,7 @@ public interface IAuthHelper string CurrentAccountRole(); AuthViewModel CurrentAccountInfo(); List GetPermissions(); + bool HasPermission(int permission); long CurrentAccountId(); string CurrentAccountMobile(); diff --git a/0_Framework/Application/PaymentGateway/AqayePardakhtPaymentGateway.cs b/0_Framework/Application/PaymentGateway/AqayePardakhtPaymentGateway.cs index b71f8681..c68790b9 100644 --- a/0_Framework/Application/PaymentGateway/AqayePardakhtPaymentGateway.cs +++ b/0_Framework/Application/PaymentGateway/AqayePardakhtPaymentGateway.cs @@ -39,7 +39,7 @@ public class AqayePardakhtPaymentGateway:IPaymentGateway amount = command.Amount, callback = command.CallBackUrl, card_number = command.CardNumber, - invoice_id = command.InvoiceId, + invoice_id = command.TransactionId, mobile = command.Mobile, email = command.Email??"", description = command.Description, @@ -73,7 +73,7 @@ public class AqayePardakhtPaymentGateway:IPaymentGateway amount = command.Amount, callback = command.CallBackUrl, card_number = command.Amount, - invoice_id = command.InvoiceId, + invoice_id = command.TransactionId, mobile = command.Mobile, email = command.Email, description = command.Email, diff --git a/0_Framework/Application/PaymentGateway/IPaymentGateway.cs b/0_Framework/Application/PaymentGateway/IPaymentGateway.cs index 306a75a2..46f43528 100644 --- a/0_Framework/Application/PaymentGateway/IPaymentGateway.cs +++ b/0_Framework/Application/PaymentGateway/IPaymentGateway.cs @@ -29,9 +29,11 @@ public class PaymentGatewayResponse public int? ErrorCode { get; set; } [JsonPropertyName("transid")] - public string TransactionId { get; set; } + public string Token { get; set; } - public bool IsSuccess => Status == "success"; + public bool IsSuccess { get; set; } + + public string Message { get; set; } } public class WalletAmountResponse @@ -47,16 +49,19 @@ public class WalletAmountResponse public class CreatePaymentGatewayRequest { public double Amount { get; set; } + public string TransactionId { get; set; } public string CallBackUrl { get; set; } - public string InvoiceId { get; set; } public string CardNumber { get; set; } public string Mobile { get; set; } public string Email { get; set; } public string Description { get; set; } + public long FinancialInvoiceId { get; set; } + public IDictionary ExtraData { get; set; } } public class VerifyPaymentGateWayRequest { - public string TransactionId { get; set; } + public string DigitalReceipt { get; set; } + public string TransactionId { get; set; } public double Amount { get; set; } } \ No newline at end of file diff --git a/0_Framework/Application/PaymentGateway/SepehrPaymentGateway.cs b/0_Framework/Application/PaymentGateway/SepehrPaymentGateway.cs new file mode 100644 index 00000000..9a6cf10e --- /dev/null +++ b/0_Framework/Application/PaymentGateway/SepehrPaymentGateway.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace _0_Framework.Application.PaymentGateway; + +public class SepehrPaymentGateway:IPaymentGateway +{ + private readonly HttpClient _httpClient; + private const long TerminalId = 99213700; + private readonly ILogger _logger; + + public SepehrPaymentGateway(IHttpClientFactory httpClient, ILogger logger) + { + _logger = logger; + _httpClient = httpClient.CreateClient(); + _httpClient.BaseAddress = new Uri("https://sepehr.shaparak.ir/Rest/V1/PeymentApi/"); + } + + public async Task Create(CreatePaymentGatewayRequest command, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Create payment started. TransactionId: {TransactionId}, Amount: {Amount}", command.TransactionId, command.Amount); + command.ExtraData ??= new Dictionary(); + _logger.LogInformation("Initializing extra data with FinancialInvoiceId: {FinancialInvoiceId}", command.FinancialInvoiceId); + command.ExtraData.Add("financialInvoiceId", command.FinancialInvoiceId); + var extraData = JsonConvert.SerializeObject(command.ExtraData); + _logger.LogInformation("Serialized extra data payload: {Payload}", extraData); + + var res = await _httpClient.PostAsJsonAsync("GetToken", new + { + TerminalID = TerminalId, + Amount = command.Amount, + InvoiceID = command.TransactionId, + callbackURL = command.CallBackUrl, + payload = extraData + }, cancellationToken: cancellationToken); + _logger.LogInformation("Create payment request sent. StatusCode: {StatusCode}", res.StatusCode); + // خواندن محتوای پاسخ + var content = await res.Content.ReadAsStringAsync(cancellationToken); + _logger.LogInformation("Create payment response content: {Content}", content); + + // تبدیل پاسخ JSON به آبجکت دات‌نت + var json = System.Text.Json.JsonDocument.Parse(content); + _logger.LogInformation("Create payment JSON parsed successfully."); + + // گرفتن مقدار AccessToken + var accessToken = json.RootElement.GetProperty("Accesstoken").ToString(); + var status = json.RootElement.GetProperty("Status").ToString(); + _logger.LogInformation("Create payment parsed values. Status: {Status}, AccessToken: {AccessToken}", status, accessToken); + + return new PaymentGatewayResponse + { + Status = status, + IsSuccess = status == "0", + Token = accessToken, + }; + } + + public string GetStartPayUrl(string token)=> + $"https://sepehr.shaparak.ir/Payment/Pay?token={token}&terminalId={TerminalId}"; + + public async Task Verify(VerifyPaymentGateWayRequest command, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Verify payment started. DigitalReceipt: {DigitalReceipt}", command.DigitalReceipt); + var res = await _httpClient.PostAsJsonAsync("Advice", new + { + digitalreceipt = command.DigitalReceipt, + Tid = TerminalId, + }, cancellationToken: cancellationToken); + _logger.LogInformation("Verify payment request sent. StatusCode: {StatusCode}", res.StatusCode); + // خواندن محتوای پاسخ + var content = await res.Content.ReadAsStringAsync(cancellationToken); + _logger.LogInformation("Verify payment response content: {Content}", content); + + // تبدیل پاسخ JSON به آبجکت دات‌نت + var json = System.Text.Json.JsonDocument.Parse(content); + _logger.LogInformation("Verify payment JSON parsed successfully."); + + var message = json.RootElement.GetProperty("Message").GetString(); + var status = json.RootElement.GetProperty("Status").GetString(); + _logger.LogInformation("Verify payment parsed values. Status: {Status}, Message: {Message}", status, message); + return new PaymentGatewayResponse + { + Status = status, + IsSuccess = status.ToLower() == "ok", + Message = message + }; + + + + } + + public Task CreateSandBox(CreatePaymentGatewayRequest command, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public string GetStartPaySandBoxUrl(string transactionId) + { + throw new System.NotImplementedException(); + } + + public Task GetWalletAmount(CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } +} \ No newline at end of file diff --git a/0_Framework/Application/SecretKeys.cs b/0_Framework/Application/SecretKeys.cs new file mode 100644 index 00000000..02a9934d --- /dev/null +++ b/0_Framework/Application/SecretKeys.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace _0_Framework.Application; + +public static class SecretKeys +{ + + public static string ProgramManagerInternalApi => "JOb09$Ic3NJd0siLCJtYWMiOiI2%dmODJmNDV"; +} \ No newline at end of file diff --git a/0_Framework/Application/Sms/ISmsService.cs b/0_Framework/Application/Sms/ISmsService.cs index 36a38030..9590bf27 100644 --- a/0_Framework/Application/Sms/ISmsService.cs +++ b/0_Framework/Application/Sms/ISmsService.cs @@ -26,6 +26,79 @@ public interface ISmsService #region Mahan Task GetCreditAmount(); + + public Task SendInstitutionCreationVerificationLink(string number, string fullName, Guid institutionId, long contractingPartyId, long institutionContractId, string typeOfSms = null); + + public Task SendInstitutionVerificationCode(string number, string code, string contractingPartyFullName, + long contractingPartyId, long institutionContractId); + + SmsResult TaskReminderSms(string number, string taskCount); + + #endregion + + + #region InstitutionContractSMS + /// + /// پیامک اهانه جدید + /// + /// + /// + /// + /// + /// + /// + /// + Task<(byte status, string message, int messaeId, bool isSucceded)> MonthlyBillNew(string number, int tamplateId, string fullname, string amount, string code1, + string code2); + /// + /// پیامک ماهانه قدیم + /// + /// + /// + /// + /// + /// + /// + /// + Task<(byte status, string message, int messaeId, bool isSucceded)> MonthlyBill(string number, int tamplateId, string fullname, string amount, string id, string aprove); + + /// + /// پیامک مسدودی طرف حساب + /// قراردادهای قدیم + /// + /// + /// + /// + /// + /// + /// + /// + Task<(byte status, string message, int messaeId, bool isSucceded)> BlockMessage(string number, string fullname, string amount, string accountType, string id, string aprove); + + /// + /// پیامک مسدودی طرف حساب + /// قرارداد های جدید + /// + /// + /// + /// + /// + /// + /// + Task<(byte status, string message, int messaeId, bool isSucceded)> BlockMessageForElectronicContract(string number, + string fullname, + string amount, string code1, string code2); + #endregion + + #region AlarmMessage + + /// + /// ارسال پیامک های خطا یا اعمال ارسال + /// + /// + /// + /// + Task Alarm(string number, string message); #endregion diff --git a/0_Framework/Application/Sms/OtpResultViewModel.cs b/0_Framework/Application/Sms/OtpResultViewModel.cs new file mode 100644 index 00000000..ea85d622 --- /dev/null +++ b/0_Framework/Application/Sms/OtpResultViewModel.cs @@ -0,0 +1,7 @@ +namespace _0_Framework.Application.Sms; + +public class OtpResultViewModel +{ + public int ExpireTimeSec { get; set; } + public int ReSendTimeSec { get; set; } +} \ No newline at end of file diff --git a/0_Framework/Application/Sms/SmsResult.cs b/0_Framework/Application/Sms/SmsResult.cs new file mode 100644 index 00000000..266a76d4 --- /dev/null +++ b/0_Framework/Application/Sms/SmsResult.cs @@ -0,0 +1,32 @@ +namespace _0_Framework.Application.Sms; + +public class SmsResult +{ + public SmsResult() + { + IsSuccedded = false; + } + + public bool IsSuccedded { get; set; } + public string Message { get; set; } + public byte StatusCode { get; set; } + public int MessageId { get; set; } + + public SmsResult Succedded(byte statusCode, string message, int messageId) + { + IsSuccedded = true; + Message = message; + StatusCode = statusCode; + MessageId = messageId; + return this; + } + + public SmsResult Failed(byte statusCode, string message, int messageId) + { + IsSuccedded = false; + Message = message; + StatusCode = statusCode; + MessageId = messageId; + return this; + } +} \ No newline at end of file diff --git a/0_Framework/Application/Sms/SmsService.cs b/0_Framework/Application/Sms/SmsService.cs deleted file mode 100644 index 9513791c..00000000 --- a/0_Framework/Application/Sms/SmsService.cs +++ /dev/null @@ -1,337 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices.ComTypes; -using System.Security.AccessControl; -using System.Threading; -using System.Threading.Tasks; -using IPE.SmsIrClient; -using IPE.SmsIrClient.Models.Requests; -using IPE.SmsIrClient.Models.Results; -using Microsoft.Extensions.Configuration; -using static System.Runtime.InteropServices.JavaScript.JSType; - - -namespace _0_Framework.Application.Sms; - -public class SmsService : ISmsService -{ - private readonly IConfiguration _configuration; - public SmsIr SmsIr { get; set; } - - - public SmsService(IConfiguration configuration) - { - _configuration = configuration; - SmsIr = new SmsIr("Og5M562igmzJRhQPnq0GdtieYdLgtfikjzxOmeQBPxJjZtyge5Klc046Lfw1mxSa"); - - } - - public void Send(string number, string message) - { - //var token = GetToken(); - //var lines = new SmsLine().GetSmsLines(token); - //if (lines == null) return; - - //var line = lines.SMSLines.Last().LineNumber.ToString(); - //var data = new MessageSendObject - //{ - // Messages = new List - // {message}.ToArray(), - // MobileNumbers = new List {number}.ToArray(), - // LineNumber = line, - // SendDateTime = DateTime.Now, - // CanContinueInCaseOfError = true - //}; - //var messageSendResponseObject = - // new MessageSend().Send(token, data); - - //if (messageSendResponseObject.IsSuccessful) return; - - //line = lines.SMSLines.First().LineNumber.ToString(); - //data.LineNumber = line; - //new MessageSend().Send(token, data); - } - public bool VerifySend(string number, string message) - { - - SmsIr smsIr = new SmsIr("Og5M562igmzJRhQPnq0GdtieYdLgtfikjzxOmeQBPxJjZtyge5Klc046Lfw1mxSa"); - - //var bulkSendResult = smsIr.BulkSendAsync(95007079000006, "your text message", new string[] { "9120000000" }); - - var verificationSendResult = smsIr.VerifySendAsync(number, 768382, new VerifySendParameter[] { new VerifySendParameter("VerificationCode", message) }); - Thread.Sleep(2000); - if (verificationSendResult.IsCompletedSuccessfully) - { - - var resStartStatus = verificationSendResult.Result; - var b = resStartStatus.Status; - var resResult = verificationSendResult.Status; - var a = verificationSendResult.IsCompleted; - var reseExceptiont = verificationSendResult.Exception; - return true; - } - else - { - - var resStartStatus = verificationSendResult.Status; - var resResult = verificationSendResult.Status; - var reseExceptiont = verificationSendResult.Exception; - - return false; - } - - - - - } - - public bool LoginSend(string number, string message) - { - SmsIr smsIr = new SmsIr("Og5M562igmzJRhQPnq0GdtieYdLgtfikjzxOmeQBPxJjZtyge5Klc046Lfw1mxSa"); - - //var bulkSendResult = smsIr.BulkSendAsync(95007079000006, "your text message", new string[] { "9120000000" }); - - var verificationSendResult = smsIr.VerifySendAsync(number, 635330, new VerifySendParameter[] { new VerifySendParameter("LOGINCODE", message) }); - Thread.Sleep(2000); - if (verificationSendResult.IsCompletedSuccessfully) - { - - var resStartStatus = verificationSendResult.Result; - var b = resStartStatus.Status; - var resResult = verificationSendResult.Status; - var a = verificationSendResult.IsCompleted; - var reseExceptiont = verificationSendResult.Exception; - return true; - } - else - { - - var resStartStatus = verificationSendResult.Status; - var resResult = verificationSendResult.Status; - var reseExceptiont = verificationSendResult.Exception; - - return false; - } - } - - public async Task SendVerifyCodeToClient(string number, string code) - { - SmsIr smsIr = new SmsIr("Og5M562igmzJRhQPnq0GdtieYdLgtfikjzxOmeQBPxJjZtyge5Klc046Lfw1mxSa"); - var result = new SentSmsViewModel(); - //var bulkSendResult = smsIr.BulkSendAsync(95007079000006, "your text message", new string[] { "9120000000" }); - - var sendResult = await smsIr.VerifySendAsync(number, 768382, new VerifySendParameter[] { new VerifySendParameter("VerificationCode", code) }); - Thread.Sleep(2000); - - if (sendResult.Message == "موفق") - { - var status = sendResult.Status; - var message = sendResult.Message; - var messaeId = sendResult.Data.MessageId; - return result.Succedded(status, message, messaeId); - } - else - { - var status = sendResult.Status; - var message = sendResult.Message; - var messaeId = sendResult.Data.MessageId; - return result.Failed(status, message, messaeId); - } - } - - public bool SendAccountsInfo(string number, string fullName, string userName) - { - - var checkLength = fullName.Length; - if (checkLength > 25) - fullName = fullName.Substring(0, 24); - SmsIr smsIr = new SmsIr("Og5M562igmzJRhQPnq0GdtieYdLgtfikjzxOmeQBPxJjZtyge5Klc046Lfw1mxSa"); - - var sendResult = smsIr.VerifySendAsync(number, 725814, new VerifySendParameter[] { new VerifySendParameter("FULLNAME", fullName), new VerifySendParameter("USERNAME", userName), new VerifySendParameter("PASSWORD", userName) }); - - - Console.WriteLine(userName + " - " + sendResult.Result.Status); - if (sendResult.IsCompletedSuccessfully) - { - return true; - } - else - { - return false; - } - - } - - public async Task GetByMessageId(int messId) - { - - SmsIr smsIr = new SmsIr("Og5M562igmzJRhQPnq0GdtieYdLgtfikjzxOmeQBPxJjZtyge5Klc046Lfw1mxSa"); - var response = await smsIr.GetReportAsync(messId); - MessageReportResult messages = response.Data; - - var appendData = new ApiResultViewModel() - { - MessageId = messages.MessageId, - LineNumber = messages.LineNumber, - Mobile = messages.Mobile, - MessageText = messages.MessageText, - SendUnixTime = UnixTimeStampToDateTime(messages.SendDateTime), - DeliveryState = DeliveryStatus(messages.DeliveryState), - DeliveryUnixTime = UnixTimeStampToDateTime(messages.DeliveryDateTime), - DeliveryColor = DeliveryColorStatus(messages.DeliveryState), - }; - return appendData; - } - public async Task> GetApiResult(string startDate, string endDate) - { - var st = new DateTime(2024, 6, 2); - var ed = new DateTime(2024, 7, 1); - if (!string.IsNullOrWhiteSpace(startDate) && startDate.Length == 10) - { - st = startDate.ToGeorgianDateTime(); - } - if (!string.IsNullOrWhiteSpace(endDate) && endDate.Length == 10) - { - ed = endDate.ToGeorgianDateTime(); - } - var res = new List(); - Int32 unixTimestamp = (int)st.Subtract(new DateTime(1970,1,1)).TotalSeconds; - Int32 unixTimestamp2 = (int)ed.Subtract(new DateTime(1970, 1, 1)).TotalSeconds; - // int? fromDateUnixTime = null; // unix time - for instance: 1700598600 - //int? toDateUnixTime = null; // unix time - for instance: 1703190600 - int pageNumber = 2; - int pageSize = 100; // max: 100 - SmsIr smsIr = new SmsIr("Og5M562igmzJRhQPnq0GdtieYdLgtfikjzxOmeQBPxJjZtyge5Klc046Lfw1mxSa"); - var response = await smsIr.GetArchivedReportAsync(pageNumber, pageSize, unixTimestamp, unixTimestamp2); - - - MessageReportResult[] messages = response.Data; - foreach (var message in messages) - { - var appendData = new ApiResultViewModel() - { - MessageId = message.MessageId, - LineNumber = message.LineNumber, - Mobile = message.Mobile, - MessageText = message.MessageText, - SendUnixTime = UnixTimeStampToDateTime(message.SendDateTime), - DeliveryState = DeliveryStatus(message.DeliveryState), - DeliveryUnixTime = UnixTimeStampToDateTime(message.DeliveryDateTime), - DeliveryColor = DeliveryColorStatus(message.DeliveryState), - }; - res.Add(appendData); - } - - - return res; - } - - public string DeliveryStatus(byte? dv) - { - string mess = ""; - switch (dv) - { - case 1: - mess = "رسیده به گوشی"; - break; - case 2: - mess = "نرسیده به گوشی"; - break; - case 3: - mess = "پردازش در مخابرات"; - break; - case 4: - mess = "نرسیده به مخابرات"; - break; - case 5: - mess = "سیده به مخابرات"; - break; - case 6: - mess = "خطا"; - break; - case 7: - mess = "لیست سیاه"; - break; - default: - mess=""; - break; - - } - - return mess; - } - public string DeliveryColorStatus(byte? dv) - { - string mess = ""; - switch (dv) - { - case 1: - mess = "successSend"; - break; - case 2: - mess = "errSend"; - break; - case 3: - mess = "pSend"; - break; - case 4: - mess = "noSend"; - break; - case 5: - mess = "itcSend"; - break; - case 6: - mess = "redSend"; - break; - case 7: - mess = "blockSend"; - break; - default: - mess = ""; - break; - - } - - return mess; - } - public string UnixTimeStampToDateTime(int? unixTimeStamp) - { - if (unixTimeStamp != null) - { - // Unix timestamp is seconds past epoch - DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - dateTime = dateTime.AddSeconds(Convert.ToDouble(unixTimeStamp)).ToLocalTime(); - var time = dateTime.ToFarsiFull(); - return time; - } - - return ""; - } - private string GetToken() - { - return ""; - //var smsSecrets = _configuration.GetSection("SmsSecrets"); - //var tokenService = new Token(); - //return tokenService.GetToken("x-api-key", "Og5M562igmzJRhQPnq0GdtieYdLgtfikjzxOmeQBPxJjZtyge5Klc046Lfw1mxSa"); - } - - #region Mahan - - public async Task GetCreditAmount() - { - try - { - var credit = await SmsIr.GetCreditAsync(); - return (double)credit.Data; - } - catch - { - return -1; - } - - } - - - #endregion -} diff --git a/0_Framework/Application/StaticWorkshopAccounts.cs b/0_Framework/Application/StaticWorkshopAccounts.cs index f0746e01..44491a69 100644 --- a/0_Framework/Application/StaticWorkshopAccounts.cs +++ b/0_Framework/Application/StaticWorkshopAccounts.cs @@ -32,11 +32,22 @@ public static class StaticWorkshopAccounts /// 392 - عمار حسن دوست /// 20 - سمیرا الهی نیا /// - public static List StaticAccountIds = [2, 3, 380, 381, 392, 20]; + public static List StaticAccountIds = [2, 3, 380, 381, 392, 20, 476]; /// /// این تاریخ در جدول اکانت لفت ورک به این معنیست /// که کاربر همچنان به کارگاه دسترسی دارد /// public static DateTime ContinuesWorkingDate = new DateTime(2150, 1, 1); + + + /// + /// لیستی آی دی نقش هایی که مسئول بیمه کارگاه هستند + /// 7 : بیمه ارشد + /// 8 : بیمه ساده + /// + public static List InsuranceAccountsRoleIds = [7, 8]; + + + } \ No newline at end of file diff --git a/0_Framework/Application/Tools.cs b/0_Framework/Application/Tools.cs index 746529f9..9cec9606 100644 --- a/0_Framework/Application/Tools.cs +++ b/0_Framework/Application/Tools.cs @@ -1541,6 +1541,14 @@ public static class Tools #region Mahan + public static bool IsvalidIban(this string iban) + { + return Regex.IsMatch(iban, @"^IR[0-9]{24}$"); + } + public static bool IsValidCardNumber(this string cardNumber) + { + return Regex.IsMatch(cardNumber, @"^[0-9]{16}$"); + } /// /// این متد حروف عربی را به فارسی در میاورد. مثال: علي را به علی تبدیل میکند /// diff --git a/0_Framework/Application/UID/IUidService.cs b/0_Framework/Application/UID/IUidService.cs index c2c26bd5..a3350dfa 100644 --- a/0_Framework/Application/UID/IUidService.cs +++ b/0_Framework/Application/UID/IUidService.cs @@ -110,6 +110,53 @@ public interface IUidService { Task GetPersonalInfo(string nationalCode , string birthDate); Task IsMachPhoneWithNationalCode(string nationalCode , string phoneNumber); + Task IbanInquiry (string iban); + Task AccountToIban(string accountNumber, UidBanks bank); + Task CardToIban(string cardNumber); +} + +public class CardToNumberResponse:UidBaseResponse +{ + public string Iban { get; set; } + public string CardNumber { get; set; } +} + +public class AccountToIbanResponse:UidBaseResponse +{ + public string Iban { get; set; } +} + +public class IbanInquiryResponse:UidBaseResponse +{ + public IbanInquiryAccountBasicInformation AccountBasicInformation { get; set; } + [JsonProperty("owners")] + public List Owners { get; set; } +} + +public class IbanInquiryAccountBasicInformation +{ + public string Iban { get; set; } + public string AccountNumber { get; set; } + public IbanInquiryBankInformation BankInformation { get; set; } + public string AccountStatus { get; set; } + +} + +public class IbanInquiryBankInformation +{ + public string BankName { get; set; } +} + +public class IbanInquiryOwner +{ + [JsonProperty("firstName")] + public string FirstName { get; set; } + [JsonProperty("lastName")] + public string LastName { get; set; } + [JsonProperty("nationalIdentifier")] + public string NationalIdentifier { get; set; } + [JsonProperty("customerType")] + public string CustomerType { get; set; } } public class MatchMobileWithNationalCodeResponse @@ -118,4 +165,7 @@ public class MatchMobileWithNationalCodeResponse public ResponseContext ResponseContext { get; set; } } - +public class UidBaseResponse +{ + public ResponseContext ResponseContext { get; set; } +} diff --git a/0_Framework/Application/UID/UidBanks.cs b/0_Framework/Application/UID/UidBanks.cs new file mode 100644 index 00000000..16e0c3d4 --- /dev/null +++ b/0_Framework/Application/UID/UidBanks.cs @@ -0,0 +1,117 @@ +using System.ComponentModel; + +namespace _0_Framework.Application.UID; + +public enum UidBanks +{ + [Description("بانک دی")] + BANK_DEY = 66, + + [Description("بانک سپه")] + BANK_SEPAH = 15, + + [Description("بانک شهر")] + BANK_SHAHR = 61, + + [Description("بانک ملت")] + BANK_MELAT = 12, + + [Description("بانک ملی")] + BANK_MELLI = 17, + + [Description("بانک رفاه کارگران")] + BANK_REFAH = 13, + + [Description("بانک سینا")] + BANK_SINA = 59, + + [Description("بانک مسکن")] + BANK_MASKAN = 14, + + [Description("بانک آینده")] + BANK_AYANDEH = 62, + + [Description("بانک انصار")] + BANK_ANSAR = 63, + + [Description("بانک تجارت")] + BANK_TEJARAT = 18, + + [Description("بانک رسالت")] + BANK_RESALAT = 70, + + [Description("بانک سامان")] + BANK_SAMAN = 56, + + [Description("بانک مرکزی")] + BANK_MARKAZI = 10, + + [Description("بانک سرمایه")] + BANK_SARMAYEH = 58, + + [Description("بانک صادرات")] + BANK_SADERAT = 19, + + [Description("بانک قوامین")] + BANK_GHAVAMIN = 52, + + [Description("بانک پارسیان")] + BANK_PARSIAN = 54, + + [Description("بانک کشاورزی")] + BANK_KESHAVARZI = 16, + + [Description("بانک گردشگری")] + BANK_GARDESHGARI = 64, + + [Description("پست بانک")] + BANK_POST_BANK = 21, + + [Description("بانک پاسارگاد")] + BANK_PASARGAD = 57, + + [Description("بانک کارآفرین")] + BANK_KARAFARIN = 53, + + [Description("بانک خاورمیانه")] + BANK_KHAVARMIANEH = 78, + + [Description("بانک ایران زمین")] + BANK_IRAN_ZAMIN = 69, + + [Description("بانک مهر اقتصاد")] + BANK_MEHR_EQTESAD = 79, + + [Description("بانک صنعت و معدن")] + BANK_SANAT_MADAN = 11, + + [Description("بانک اقتصاد نوین")] + BANK_EGHTESAD_NOVIN = 55, + + [Description("بانک توسعه تعاون")] + BANK_TOSSE_TAAVON = 22, + + [Description("بانک توسعه صادرات")] + BANK_TOSSE_SADERAT = 20, + + [Description("بانک ایران و ونزوئلا")] + BANK_IRAN_VENEZUELA = 95, + + [Description("بانک حکمت ایرانیان")] + BANK_HEKMAT_IRANIAN = 65, + + [Description("بانک قرض الحسنه مهر")] + BANK_GHARZOLHASANEH_MEHR = 60, + + [Description("موسسه مالی و اعتباری ملل")] + BANK_MOASSASE_MELLAL = 75, + + [Description("موسسه مالی و اعتباری نور")] + BANK_MOASSASE_NOOR = 80, + + [Description("موسسه مالی و اعتباری کوثر")] + BANK_MOASSASE_KOSAR = 73, + + [Description("موسسه مالی و اعتباری توسعه")] + BANK_MOASSASE_TOSSE = 51 +} \ No newline at end of file diff --git a/0_Framework/Application/UID/UidBanksExtension.cs b/0_Framework/Application/UID/UidBanksExtension.cs new file mode 100644 index 00000000..98a46f16 --- /dev/null +++ b/0_Framework/Application/UID/UidBanksExtension.cs @@ -0,0 +1,27 @@ +using System; +using System.ComponentModel; +using System.Reflection; + +namespace _0_Framework.Application.UID +{ + public static class UidBanksExtension + { + /// + /// دریافت نام فارسی بانک + /// + /// بانک + /// نام فارسی بانک + public static string GetPersianName(this UidBanks bank) + { + var fieldInfo = bank.GetType().GetField(bank.ToString()); + + if (fieldInfo == null) + return string.Empty; + + var attribute = (DescriptionAttribute)Attribute.GetCustomAttribute( + fieldInfo, typeof(DescriptionAttribute)); + + return attribute?.Description ?? bank.ToString(); + } + } +} diff --git a/0_Framework/Application/UID/UidService.cs b/0_Framework/Application/UID/UidService.cs deleted file mode 100644 index ccf3c0ef..00000000 --- a/0_Framework/Application/UID/UidService.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Net.Http; -using System.Net.Http.Json; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; -using Newtonsoft.Json; - -namespace _0_Framework.Application.UID; - -public class UidService : IUidService -{ - private readonly HttpClient _httpClient; - private const string BaseUrl = "https://json-api.uid.ir/api/inquiry/"; - - public UidService() - { - _httpClient = new HttpClient() - { - BaseAddress = new Uri(BaseUrl) - }; - } - - public async Task GetPersonalInfo(string nationalCode, string birthDate) - { - var request = new PersonalInfoRequest - { - BirthDate = birthDate, - NationalId = nationalCode, - RequestContext = new UidRequestContext() - }; - var json = JsonConvert.SerializeObject(request); - var contentType = new StringContent(json, Encoding.UTF8, "application/json"); - - try - { - var requestResult = await _httpClient.PostAsync("person/v2", contentType); - if (!requestResult.IsSuccessStatusCode) - return null; - var responseResult = await requestResult.Content.ReadFromJsonAsync(); - if (responseResult.BasicInformation != null) - { - responseResult.BasicInformation.FirstName = responseResult.BasicInformation.FirstName?.ToPersian(); - responseResult.BasicInformation.LastName = responseResult.BasicInformation.LastName?.ToPersian(); - responseResult.BasicInformation.FatherName = responseResult.BasicInformation.FatherName?.ToPersian(); - } - - return responseResult; - } - catch - { - - return new PersonalInfoResponse(new UidBasicInformation(), - new IdentificationInformation(default, default, default, default, default), new RegistrationStatus(), - new ResponseContext(new UidStatus(14, ""))); - } - - } - - - public async Task IsMachPhoneWithNationalCode(string nationalCode, string phoneNumber) - { - var request = new PersonalInfoRequest - { - MobileNumber = phoneNumber, - NationalId = nationalCode, - RequestContext = new UidRequestContext() - }; - var json = JsonConvert.SerializeObject(request); - var contentType = new StringContent(json, Encoding.UTF8, "application/json"); - - var requestResult = await _httpClient.PostAsync("mobile/owner/v2", contentType); - if (!requestResult.IsSuccessStatusCode) - return null; - - var responseResult = await requestResult.Content.ReadFromJsonAsync(); - return responseResult; - } -} \ No newline at end of file diff --git a/0_Framework/Domain/CustomizeCheckoutShared/ValueObjects/CheckoutDynamicDeductionItem.cs b/0_Framework/Domain/CustomizeCheckoutShared/ValueObjects/CheckoutDynamicDeductionItem.cs new file mode 100644 index 00000000..911f3c87 --- /dev/null +++ b/0_Framework/Domain/CustomizeCheckoutShared/ValueObjects/CheckoutDynamicDeductionItem.cs @@ -0,0 +1,9 @@ +namespace _0_Framework.Application.Enums +{ + public class CheckoutDynamicDeductionItem + { + public string Name { get; set; } + public int Count { get; set; } + public string Amount { get; set; } + } +} diff --git a/0_Framework/Excel/ExcelGenerator.cs b/0_Framework/Excel/ExcelGenerator.cs index 23d6382b..fb172b1f 100644 --- a/0_Framework/Excel/ExcelGenerator.cs +++ b/0_Framework/Excel/ExcelGenerator.cs @@ -17,7 +17,7 @@ public class ExcelGenerator { public ExcelGenerator() { - ExcelPackage.LicenseContext = LicenseContext.NonCommercial; + OfficeOpenXml.ExcelPackage.License.SetNonCommercialOrganization("Gozareshgir Noncommercial organization"); } public static byte[] GenerateExcel(List obj, string date = "") where T : class { diff --git a/0_Framework/Exceptions/BadRequestException.cs b/0_Framework/Exceptions/BadRequestException.cs index ac8324b7..7a85e96f 100644 --- a/0_Framework/Exceptions/BadRequestException.cs +++ b/0_Framework/Exceptions/BadRequestException.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace _0_Framework.Exceptions; @@ -14,5 +15,13 @@ public class BadRequestException:Exception Details = details; } + public BadRequestException(string message, Dictionary extra) : + base(message) + { + Extra = extra; + } + public string Details { get; } + public Dictionary Extra { get; set; } + } \ No newline at end of file diff --git a/0_Framework/Exceptions/Handler/CustomExceptionHandler.cs b/0_Framework/Exceptions/Handler/CustomExceptionHandler.cs index a3a8fc4c..0d57a94f 100644 --- a/0_Framework/Exceptions/Handler/CustomExceptionHandler.cs +++ b/0_Framework/Exceptions/Handler/CustomExceptionHandler.cs @@ -1,6 +1,10 @@ -using System; +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using FluentValidation; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -20,41 +24,62 @@ public class CustomExceptionHandler : IExceptionHandler public async ValueTask TryHandleAsync(HttpContext context, Exception exception, CancellationToken cancellationToken) { - _logger.LogError( - "Error Message: {exceptionMessage}, Time of occurrence {time}", - exception.Message, DateTime.UtcNow); + _logger.LogError(exception, + "Error Message: {exceptionMessage}, Type: {exceptionType}, Time: {time}, Path: {path}, TraceId: {traceId}", + exception.Message, + exception.GetType().FullName, + DateTime.UtcNow, + context.Request.Path, + context.TraceIdentifier); - (string Detail, string Title, int StatusCode) details = exception switch + (string Detail, string Title, int StatusCode, Dictionary? Extra) details = exception switch { + ValidationException validationException => + ( + validationException.Errors.FirstOrDefault()?.ErrorMessage ?? "One or more validation errors occurred.", + "Validation Error", + context.Response.StatusCode = StatusCodes.Status400BadRequest, + null + ), + InternalServerException => ( exception.Message, exception.GetType().Name, - context.Response.StatusCode = StatusCodes.Status500InternalServerError + context.Response.StatusCode = StatusCodes.Status500InternalServerError, + null ), - BadRequestException => + + BadRequestException bre => ( exception.Message, exception.GetType().Name, - context.Response.StatusCode = StatusCodes.Status400BadRequest + context.Response.StatusCode = StatusCodes.Status400BadRequest, + bre.Extra ), + NotFoundException => ( exception.Message, exception.GetType().Name, - context.Response.StatusCode = StatusCodes.Status404NotFound + context.Response.StatusCode = StatusCodes.Status404NotFound, + null ), + UnAuthorizeException => ( exception.Message, exception.GetType().Name, - context.Response.StatusCode = StatusCodes.Status401Unauthorized + context.Response.StatusCode = StatusCodes.Status401Unauthorized, + null ), + _ => ( exception.Message, exception.GetType().Name, - context.Response.StatusCode = StatusCodes.Status500InternalServerError + context.Response.StatusCode = StatusCodes.Status500InternalServerError, + null ) }; @@ -63,9 +88,10 @@ public class CustomExceptionHandler : IExceptionHandler Title = details.Title, Detail = details.Detail, Status = details.StatusCode, - Instance = context.Request.Path + Instance = context.Request.Path, + Extensions = details.Extra ?? new Dictionary() }; - + problemDetails.Extensions.Add("traceId", context.TraceIdentifier); await context.Response.WriteAsJsonAsync(problemDetails, cancellationToken: cancellationToken); diff --git a/0_Framework/InfraStructure/FaceEmbeddingService.cs b/0_Framework/InfraStructure/FaceEmbeddingService.cs new file mode 100644 index 00000000..148adc80 --- /dev/null +++ b/0_Framework/InfraStructure/FaceEmbeddingService.cs @@ -0,0 +1,404 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text.Json; +using _0_Framework.Application; +using _0_Framework.Application.FaceEmbedding; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Http; +using System.Threading.Tasks; + +namespace _0_Framework.Infrastructure; + +/// +/// پیاده‌سازی سرویس ارتباط با API پایتون برای مدیریت Embeddings چهره +/// +public class FaceEmbeddingService : IFaceEmbeddingService +{ + private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + private readonly IFaceEmbeddingNotificationService _notificationService; + private readonly string _apiBaseUrl; + + public FaceEmbeddingService(IHttpClientFactory httpClientFactory, ILogger logger, + IFaceEmbeddingNotificationService notificationService = null) + { + _httpClientFactory = httpClientFactory; + _logger = logger; + _notificationService = notificationService; + _apiBaseUrl = "http://localhost:8000"; + } + + public async Task GenerateEmbeddingsAsync(long employeeId, long workshopId, + string employeeFullName, string picture1Path, string picture2Path) + { + try + { + var httpClient = _httpClientFactory.CreateClient(); + httpClient.BaseAddress = new Uri(_apiBaseUrl); + httpClient.Timeout = TimeSpan.FromSeconds(30); + + using var content = new MultipartFormDataContent(); + + // Add form fields + content.Add(new StringContent(employeeId.ToString()), "employee_id"); + content.Add(new StringContent(workshopId.ToString()), "workshop_id"); + content.Add(new StringContent(employeeFullName ?? ""), "employee_full_name"); + + // Add picture files + if (File.Exists(picture1Path)) + { + var picture1Bytes = await File.ReadAllBytesAsync(picture1Path); + var picture1Content = new ByteArrayContent(picture1Bytes); + picture1Content.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg"); + content.Add(picture1Content, "picture1", "1.jpg"); + } + else + { + _logger.LogWarning("Picture1 not found at path: {Path}", picture1Path); + return new OperationResult { IsSuccedded = false, Message = "تصویر اول یافت نشد" }; + } + + if (File.Exists(picture2Path)) + { + var picture2Bytes = await File.ReadAllBytesAsync(picture2Path); + var picture2Content = new ByteArrayContent(picture2Bytes); + picture2Content.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg"); + content.Add(picture2Content, "picture2", "2.jpg"); + } + else + { + _logger.LogWarning("Picture2 not found at path: {Path}", picture2Path); + return new OperationResult { IsSuccedded = false, Message = "تصویر دوم یافت نشد" }; + } + + // Send request to Python API + var response = await httpClient.PostAsync("embeddings", content); + + if (response.IsSuccessStatusCode) + { + var responseContent = await response.Content.ReadAsStringAsync(); + _logger.LogInformation( + "Embeddings generated successfully for Employee {EmployeeId}, Workshop {WorkshopId}", + employeeId, workshopId); + + // ارسال اطلاع‌رسانی به سایر سیستم‌ها + if (_notificationService != null) + { + await _notificationService.NotifyEmbeddingCreatedAsync(workshopId, employeeId, employeeFullName); + } + + return new OperationResult + { + IsSuccedded = true, + Message = "Embedding با موفقیت ایجاد شد" + }; + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + _logger.LogError("Failed to generate embeddings. Status: {StatusCode}, Error: {Error}", + response.StatusCode, errorContent); + + return new OperationResult + { + IsSuccedded = false, + Message = $"خطا در تولید Embedding: {response.StatusCode}" + }; + } + } + catch (HttpRequestException ex) + { + _logger.LogError(ex, "HTTP error while calling embeddings API for Employee {EmployeeId}", employeeId); + return new OperationResult + { + IsSuccedded = false, + Message = "خطا در ارتباط با سرور Embedding" + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while calling embeddings API for Employee {EmployeeId}", employeeId); + return new OperationResult + { + IsSuccedded = false, + Message = "خطای غیرمنتظره در تولید Embedding" + }; + } + } + + public async Task GenerateEmbeddingsFromStreamAsync(long employeeId, long workshopId, + string employeeFullName, Stream picture1Stream, Stream picture2Stream) + { + try + { + var httpClient = _httpClientFactory.CreateClient(); + httpClient.BaseAddress = new Uri(_apiBaseUrl); + httpClient.Timeout = TimeSpan.FromSeconds(30); + + using var content = new MultipartFormDataContent(); + + // Add form fields + content.Add(new StringContent(employeeId.ToString()), "employee_id"); + content.Add(new StringContent(workshopId.ToString()), "workshop_id"); + content.Add(new StringContent(employeeFullName ?? ""), "employee_full_name"); + + // Add picture streams + var picture1Content = new StreamContent(picture1Stream); + picture1Content.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg"); + content.Add(picture1Content, "picture1", "1.jpg"); + + var picture2Content = new StreamContent(picture2Stream); + picture2Content.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg"); + content.Add(picture2Content, "picture2", "2.jpg"); + + // Send request to Python API + var response = await httpClient.PostAsync("embeddings", content); + + if (response.IsSuccessStatusCode) + { + _logger.LogInformation("Embeddings generated successfully from streams for Employee {EmployeeId}", + employeeId); + + // ارسال اطلاع‌رسانی به سایر سیستم‌ها + if (_notificationService != null) + { + await _notificationService.NotifyEmbeddingCreatedAsync(workshopId, employeeId, employeeFullName); + } + + return new OperationResult { IsSuccedded = true, Message = "Embedding با موفقیت ایجاد شد" }; + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + _logger.LogError("Failed to generate embeddings from streams. Status: {StatusCode}, Error: {Error}", + response.StatusCode, errorContent); + + return new OperationResult + { + IsSuccedded = false, + Message = $"خطا در تولید Embedding: {response.StatusCode}" + }; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while generating embeddings from streams for Employee {EmployeeId}", + employeeId); + return new OperationResult + { + IsSuccedded = false, + Message = "خطا در تولید Embedding" + }; + } + } + + public async Task RefineEmbeddingAsync(long employeeId, long workshopId, float[] embedding, + float confidence, Dictionary metadata = null) + { + try + { + var httpClient = _httpClientFactory.CreateClient(); + httpClient.BaseAddress = new Uri(_apiBaseUrl); + httpClient.Timeout = TimeSpan.FromSeconds(30); + + var requestBody = new + { + employeeId, + workshopId, + embedding, + confidence, + metadata = metadata ?? new Dictionary() + }; + + var response = await httpClient.PostAsJsonAsync("embeddings/refine", requestBody); + + if (response.IsSuccessStatusCode) + { + _logger.LogInformation("Embedding refined successfully for Employee {EmployeeId}", employeeId); + + // ارسال اطلاع‌رسانی به سایر سیستم‌ها + if (_notificationService != null) + { + await _notificationService.NotifyEmbeddingRefinedAsync(workshopId, employeeId); + } + + return new OperationResult { IsSuccedded = true, Message = "Embedding بهبود یافت" }; + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + _logger.LogError("Failed to refine embedding. Status: {StatusCode}, Error: {Error}", + response.StatusCode, errorContent); + + return new OperationResult + { + IsSuccedded = false, + Message = $"خطا در بهبود Embedding: {response.StatusCode}" + }; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while refining embedding for Employee {EmployeeId}", employeeId); + return new OperationResult + { + IsSuccedded = false, + Message = "خطا در بهبود Embedding" + }; + } + } + + public async Task DeleteEmbeddingAsync(long employeeId, long workshopId) + { + try + { + var httpClient = _httpClientFactory.CreateClient(); + httpClient.BaseAddress = new Uri(_apiBaseUrl); + httpClient.Timeout = TimeSpan.FromSeconds(30); + + var response = await httpClient.DeleteAsync($"embeddings/{workshopId}/{employeeId}"); + + if (response.IsSuccessStatusCode) + { + _logger.LogInformation("Embedding deleted successfully for Employee {EmployeeId}", employeeId); + + // ارسال اطلاع‌رسانی به سایر سیستم‌ها + if (_notificationService != null) + { + await _notificationService.NotifyEmbeddingDeletedAsync(workshopId, employeeId); + } + + return new OperationResult { IsSuccedded = true, Message = "Embedding حذف شد" }; + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + _logger.LogError("Failed to delete embedding. Status: {StatusCode}, Error: {Error}", + response.StatusCode, errorContent); + + return new OperationResult + { + IsSuccedded = false, + Message = $"خطا در حذف Embedding: {response.StatusCode}" + }; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while deleting embedding for Employee {EmployeeId}", employeeId); + return new OperationResult + { + IsSuccedded = false, + Message = "خطا در حذف Embedding" + }; + } + } + + public async Task> GetEmbeddingAsync(long employeeId, long workshopId) + { + try + { + var httpClient = _httpClientFactory.CreateClient(); + httpClient.BaseAddress = new Uri(_apiBaseUrl); + httpClient.Timeout = TimeSpan.FromSeconds(30); + + var response = await httpClient.GetAsync($"embeddings/{workshopId}/{employeeId}"); + + if (response.IsSuccessStatusCode) + { + var content = await response.Content.ReadAsStringAsync(); + var embeddingData = JsonSerializer.Deserialize(content, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + _logger.LogInformation("Embedding retrieved successfully for Employee {EmployeeId}", employeeId); + + return new OperationResult + { + IsSuccedded = true, + Message = "Embedding دریافت شد", + Data = embeddingData + }; + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + _logger.LogError("Failed to get embedding. Status: {StatusCode}, Error: {Error}", + response.StatusCode, errorContent); + + return new OperationResult + { + IsSuccedded = false, + Message = $"خطا در دریافت Embedding: {response.StatusCode}" + }; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while getting embedding for Employee {EmployeeId}", employeeId); + return new OperationResult + { + IsSuccedded = false, + Message = "خطا در دریافت Embedding" + }; + } + } + + public async Task UpdateEmbeddingFullNameAsync(long employeeId, long workshopId, + string newFullName) + { + try + { + var httpClient = _httpClientFactory.CreateClient(); + httpClient.BaseAddress = new Uri(_apiBaseUrl); + httpClient.Timeout = TimeSpan.FromSeconds(30); + + var requestBody = new + { + employee_id = employeeId, + workshop_id = workshopId, + employee_full_name = newFullName + }; + + var response = await httpClient.PutAsJsonAsync("embeddings/update-name", requestBody); + + if (response.IsSuccessStatusCode) + { + _logger.LogInformation("Employee Name Changed For {EmployeeId} In workshop ={WorkshopId}", employeeId, + workshopId); + + // ارسال اطلاع‌رسانی به سایر سیستم‌ها + if (_notificationService != null) + { + //await _notificationService.NotifyEmbeddingRefinedAsync(workshopId, employeeId); + } + + return new OperationResult { IsSuccedded = true, Message = "عملیات با موفقیت انجام شد" }; + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + _logger.LogError("Failed to refine embedding. Status: {StatusCode}, Error: {Error}", + response.StatusCode, errorContent); + + return new OperationResult + { + IsSuccedded = false, + Message = $"خطا در بهبود Embedding: {response.StatusCode}" + }; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while Changing EmployeeFullName for Employee {EmployeeId}", employeeId); + return new OperationResult + { + IsSuccedded = false, + Message = "خطا در بهبود Embedding" + }; + } + } +} \ No newline at end of file diff --git a/0_Framework/InfraStructure/Mongo/MongoDbConfig.cs b/0_Framework/InfraStructure/Mongo/MongoDbConfig.cs new file mode 100644 index 00000000..bcb351bd --- /dev/null +++ b/0_Framework/InfraStructure/Mongo/MongoDbConfig.cs @@ -0,0 +1,8 @@ +namespace _0_Framework.InfraStructure.Mongo; + +public class MongoDbConfig +{ + public string ConnectionString { get; set; } = null!; + + public string DatabaseName { get; set; } = null!; +} \ No newline at end of file diff --git a/0_Framework/InfraStructure/NullFaceEmbeddingNotificationService.cs b/0_Framework/InfraStructure/NullFaceEmbeddingNotificationService.cs new file mode 100644 index 00000000..5ce50b51 --- /dev/null +++ b/0_Framework/InfraStructure/NullFaceEmbeddingNotificationService.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; +using _0_Framework.Application.FaceEmbedding; + +namespace _0_Framework.InfraStructure; + +/// +/// پیاده‌سازی پیش‌فرض (بدون عملیات) برای IFaceEmbeddingNotificationService +/// این کلاس زمانی استفاده می‌شود که SignalR در دسترس نباشد +/// +public class NullFaceEmbeddingNotificationService : IFaceEmbeddingNotificationService +{ + public Task NotifyEmbeddingCreatedAsync(long workshopId, long employeeId, string employeeFullName) + { + // هیچ عملیاتی انجام نمی‌دهد + return Task.CompletedTask; + } + + public Task NotifyEmbeddingDeletedAsync(long workshopId, long employeeId) + { + // هیچ عملیاتی انجام نمی‌دهد + return Task.CompletedTask; + } + + public Task NotifyEmbeddingRefinedAsync(long workshopId, long employeeId) + { + // هیچ عملیاتی انجام نمی‌دهد + return Task.CompletedTask; + } +} diff --git a/ANDROID_SIGNALR_GUIDE.md b/ANDROID_SIGNALR_GUIDE.md new file mode 100644 index 00000000..576aba02 --- /dev/null +++ b/ANDROID_SIGNALR_GUIDE.md @@ -0,0 +1,624 @@ +# راهنمای اتصال اپلیکیشن Android به SignalR برای Face Embedding + +## 1. افزودن کتابخانه SignalR به پروژه Android + +در فایل `build.gradle` (Module: app) خود، dependency زیر را اضافه کنید: + +```gradle +dependencies { + // SignalR for Android + implementation 'com.microsoft.signalr:signalr:7.0.0' + + // اگر از Kotlin استفاده می‌کنید: + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1' + + // برای JSON پردازش: + implementation 'com.google.code.gson:gson:2.10.1' +} +``` + +## 2. اضافه کردن Permission در AndroidManifest.xml + +```xml + + +``` + +## 3. کد Java/Kotlin برای اتصال به SignalR + +### نسخه Java: + +```java +import com.microsoft.signalr.HubConnection; +import com.microsoft.signalr.HubConnectionBuilder; +import com.microsoft.signalr.HubConnectionState; +import com.google.gson.JsonObject; +import android.util.Log; + +public class FaceEmbeddingSignalRClient { + private static final String TAG = "FaceEmbeddingHub"; + private HubConnection hubConnection; + private String serverUrl = "http://YOUR_SERVER_IP:PORT/trackingFaceEmbeddingHub"; // آدرس سرور خود را وارد کنید + private long workshopId; + + public FaceEmbeddingSignalRClient(long workshopId) { + this.workshopId = workshopId; + initializeSignalR(); + } + + private void initializeSignalR() { + // ایجاد اتصال SignalR + hubConnection = HubConnectionBuilder + .create(serverUrl) + .build(); + + // دریافت رویداد ایجاد Embedding + hubConnection.on("EmbeddingCreated", (data) -> { + JsonObject jsonData = (JsonObject) data; + long employeeId = jsonData.get("employeeId").getAsLong(); + String employeeFullName = jsonData.get("employeeFullName").getAsString(); + String timestamp = jsonData.get("timestamp").getAsString(); + + Log.d(TAG, "Embedding Created - Employee: " + employeeFullName + " (ID: " + employeeId + ")"); + + // اینجا می‌توانید داده‌های جدید را از سرور بگیرید یا UI را بروزرسانی کنید + onEmbeddingCreated(employeeId, employeeFullName, timestamp); + + }, JsonObject.class); + + // دریافت رویداد حذف Embedding + hubConnection.on("EmbeddingDeleted", (data) -> { + JsonObject jsonData = (JsonObject) data; + long employeeId = jsonData.get("employeeId").getAsLong(); + String timestamp = jsonData.get("timestamp").getAsString(); + + Log.d(TAG, "Embedding Deleted - Employee ID: " + employeeId); + onEmbeddingDeleted(employeeId, timestamp); + + }, JsonObject.class); + + // دریافت رویداد بهبود Embedding + hubConnection.on("EmbeddingRefined", (data) -> { + JsonObject jsonData = (JsonObject) data; + long employeeId = jsonData.get("employeeId").getAsLong(); + String timestamp = jsonData.get("timestamp").getAsString(); + + Log.d(TAG, "Embedding Refined - Employee ID: " + employeeId); + onEmbeddingRefined(employeeId, timestamp); + + }, JsonObject.class); + } + + public void connect() { + if (hubConnection.getConnectionState() == HubConnectionState.DISCONNECTED) { + hubConnection.start() + .doOnComplete(() -> { + Log.d(TAG, "Connected to SignalR Hub"); + joinWorkshopGroup(); + }) + .doOnError(error -> { + Log.e(TAG, "Error connecting to SignalR: " + error.getMessage()); + }) + .subscribe(); + } + } + + private void joinWorkshopGroup() { + // عضویت در گروه مخصوص این کارگاه + hubConnection.send("JoinWorkshopGroup", workshopId); + Log.d(TAG, "Joined workshop group: " + workshopId); + } + + public void disconnect() { + if (hubConnection.getConnectionState() == HubConnectionState.CONNECTED) { + // خروج از گروه + hubConnection.send("LeaveWorkshopGroup", workshopId); + + hubConnection.stop(); + Log.d(TAG, "Disconnected from SignalR Hub"); + } + } + + // این متدها را در Activity/Fragment خود override کنید + protected void onEmbeddingCreated(long employeeId, String employeeFullName, String timestamp) { + // اینجا UI را بروزرسانی کنید یا داده جدید را بگیرید + } + + protected void onEmbeddingDeleted(long employeeId, String timestamp) { + // اینجا UI را بروزرسانی کنید + } + + protected void onEmbeddingRefined(long employeeId, String timestamp) { + // اینجا UI را بروزرسانی کنید + } +} +``` + +### نسخه Kotlin: + +```kotlin +import com.microsoft.signalr.HubConnection +import com.microsoft.signalr.HubConnectionBuilder +import com.microsoft.signalr.HubConnectionState +import com.google.gson.JsonObject +import android.util.Log +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class FaceEmbeddingSignalRClient(private val workshopId: Long) { + + companion object { + private const val TAG = "FaceEmbeddingHub" + } + + private lateinit var hubConnection: HubConnection + private val serverUrl = "http://YOUR_SERVER_IP:PORT/trackingFaceEmbeddingHub" // آدرس سرور خود را وارد کنید + + init { + initializeSignalR() + } + + private fun initializeSignalR() { + hubConnection = HubConnectionBuilder + .create(serverUrl) + .build() + + // دریافت رویداد ایجاد Embedding + hubConnection.on("EmbeddingCreated", { data: JsonObject -> + val employeeId = data.get("employeeId").asLong + val employeeFullName = data.get("employeeFullName").asString + val timestamp = data.get("timestamp").asString + + Log.d(TAG, "Embedding Created - Employee: $employeeFullName (ID: $employeeId)") + onEmbeddingCreated(employeeId, employeeFullName, timestamp) + }, JsonObject::class.java) + + // دریافت رویداد حذف Embedding + hubConnection.on("EmbeddingDeleted", { data: JsonObject -> + val employeeId = data.get("employeeId").asLong + val timestamp = data.get("timestamp").asString + + Log.d(TAG, "Embedding Deleted - Employee ID: $employeeId") + onEmbeddingDeleted(employeeId, timestamp) + }, JsonObject::class.java) + + // دریافت رویداد بهبود Embedding + hubConnection.on("EmbeddingRefined", { data: JsonObject -> + val employeeId = data.get("employeeId").asLong + val timestamp = data.get("timestamp").asString + + Log.d(TAG, "Embedding Refined - Employee ID: $employeeId") + onEmbeddingRefined(employeeId, timestamp) + }, JsonObject::class.java) + } + + fun connect() { + if (hubConnection.connectionState == HubConnectionState.DISCONNECTED) { + CoroutineScope(Dispatchers.IO).launch { + try { + hubConnection.start().blockingAwait() + Log.d(TAG, "Connected to SignalR Hub") + joinWorkshopGroup() + } catch (e: Exception) { + Log.e(TAG, "Error connecting to SignalR: ${e.message}") + } + } + } + } + + private fun joinWorkshopGroup() { + hubConnection.send("JoinWorkshopGroup", workshopId) + Log.d(TAG, "Joined workshop group: $workshopId") + } + + fun disconnect() { + if (hubConnection.connectionState == HubConnectionState.CONNECTED) { + hubConnection.send("LeaveWorkshopGroup", workshopId) + hubConnection.stop() + Log.d(TAG, "Disconnected from SignalR Hub") + } + } + + // این متدها را override کنید + open fun onEmbeddingCreated(employeeId: Long, employeeFullName: String, timestamp: String) { + // اینجا UI را بروزرسانی کنید یا داده جدید را بگیرید + } + + open fun onEmbeddingDeleted(employeeId: Long, timestamp: String) { + // اینجا UI را بروزرسانی کنید + } + + open fun onEmbeddingRefined(employeeId: Long, timestamp: String) { + // اینجا UI را بروزرسانی کنید + } +} +``` + +## 4. استفاده در Activity یا Fragment + +### مثال با Login و دریافت WorkshopId + +#### Java: +```java +public class LoginActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login); + + Button btnLogin = findViewById(R.id.btnLogin); + btnLogin.setOnClickListener(v -> performLogin()); + } + + private void performLogin() { + // فراخوانی API لاگین + // فرض کنید response شامل workshopId است + + // مثال ساده (باید از Retrofit یا کتابخانه مشابه استفاده کنید): + // LoginResponse response = apiService.login(username, password); + // long workshopId = response.getWorkshopId(); + + long workshopId = 123; // این را از response دریافت کنید + + // ذخیره workshopId + SharedPreferences prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE); + prefs.edit().putLong("workshopId", workshopId).apply(); + + // رفتن به صفحه اصلی + Intent intent = new Intent(this, MainActivity.class); + startActivity(intent); + finish(); + } +} + +public class MainActivity extends AppCompatActivity { + private FaceEmbeddingSignalRClient signalRClient; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + // دریافت workshopId از SharedPreferences + SharedPreferences prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE); + long workshopId = prefs.getLong("workshopId", 0); + + if (workshopId == 0) { + // اگر workshopId وجود نداره، برگرد به صفحه لاگین + Intent intent = new Intent(this, LoginActivity.class); + startActivity(intent); + finish(); + return; + } + + // ایجاد و اتصال SignalR + signalRClient = new FaceEmbeddingSignalRClient(workshopId) { + @Override + protected void onEmbeddingCreated(long employeeId, String employeeFullName, String timestamp) { + runOnUiThread(() -> { + // بروزرسانی UI + Toast.makeText(MainActivity.this, + "Embedding ایجاد شد برای: " + employeeFullName, + Toast.LENGTH_SHORT).show(); + + // دریافت داده‌های جدید از API + refreshEmployeeList(); + }); + } + + @Override + protected void onEmbeddingDeleted(long employeeId, String timestamp) { + runOnUiThread(() -> { + // بروزرسانی UI + refreshEmployeeList(); + }); + } + + @Override + protected void onEmbeddingRefined(long employeeId, String timestamp) { + runOnUiThread(() -> { + // بروزرسانی UI + refreshEmployeeList(); + }); + } + }; + + signalRClient.connect(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (signalRClient != null) { + signalRClient.disconnect(); + } + } + + private void refreshEmployeeList() { + // دریافت لیست جدید کارمندان از API + } +} +``` + +#### Kotlin: +```kotlin +class LoginActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_login) + + val btnLogin = findViewById