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