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/TypeOfSmsSetting.cs b/0_Framework/Application/Enums/TypeOfSmsSetting.cs
new file mode 100644
index 00000000..9b4512b7
--- /dev/null
+++ b/0_Framework/Application/Enums/TypeOfSmsSetting.cs
@@ -0,0 +1,36 @@
+namespace _0_Framework.Application.Enums;
+
+public enum TypeOfSmsSetting
+{
+
+ ///
+ /// پیامک
+ /// یادآور بدهی ماهیانه قرارداد مالی
+ ///
+ InstitutionContractDebtReminder,
+
+ ///
+ /// پیامک
+ /// صورت حساب ماهانه قرارداد مالی
+ ///
+ MonthlyInstitutionContract,
+
+ ///
+ /// پیامک
+ /// اعلام مسدودی طرف حساب
+ ///
+ BlockContractingParty,
+
+ ///
+ /// پیامک
+ /// هشدار اول
+ ///
+ Warning,
+
+
+ ///
+ ///پیامک اقدام قضائی
+ ///
+ LegalAction,
+
+}
\ 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..b5aab83b
--- /dev/null
+++ b/0_Framework/Application/FaceEmbedding/IFaceEmbeddingService.cs
@@ -0,0 +1,24 @@
+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);
+}
+
+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/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..792df36b
--- /dev/null
+++ b/0_Framework/Application/PaymentGateway/SepehrPaymentGateway.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Net.Http.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+
+namespace _0_Framework.Application.PaymentGateway;
+
+public class SepehrPaymentGateway:IPaymentGateway
+{
+ private readonly HttpClient _httpClient;
+ private const long TerminalId = 99213700;
+
+ public SepehrPaymentGateway(IHttpClientFactory httpClient)
+ {
+ _httpClient = httpClient.CreateClient();
+ _httpClient.BaseAddress = new Uri("https://sepehr.shaparak.ir/Rest/V1/PeymentApi/");
+ }
+
+ public async Task Create(CreatePaymentGatewayRequest command, CancellationToken cancellationToken = default)
+ {
+ command.ExtraData ??= new Dictionary();
+ command.ExtraData.Add("financialInvoiceId", command.FinancialInvoiceId);
+ var extraData = JsonConvert.SerializeObject(command.ExtraData);
+ var res = await _httpClient.PostAsJsonAsync("GetToken", new
+ {
+ TerminalID = TerminalId,
+ Amount = command.Amount,
+ InvoiceID = command.TransactionId,
+ callbackURL = command.CallBackUrl,
+ payload = extraData
+ }, cancellationToken: cancellationToken);
+ // خواندن محتوای پاسخ
+ var content = await res.Content.ReadAsStringAsync(cancellationToken);
+
+ // تبدیل پاسخ JSON به آبجکت داتنت
+ var json = System.Text.Json.JsonDocument.Parse(content);
+
+ // گرفتن مقدار AccessToken
+ var accessToken = json.RootElement.GetProperty("Accesstoken").ToString();
+ var status = json.RootElement.GetProperty("Status").ToString();
+
+ 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)
+ {
+ var res = await _httpClient.PostAsJsonAsync("Advice", new
+ {
+ digitalreceipt = command.DigitalReceipt,
+ Tid = TerminalId,
+ }, cancellationToken: cancellationToken);
+
+ // خواندن محتوای پاسخ
+ var content = await res.Content.ReadAsStringAsync(cancellationToken);
+
+ // تبدیل پاسخ JSON به آبجکت داتنت
+ var json = System.Text.Json.JsonDocument.Parse(content);
+
+
+ var message = json.RootElement.GetProperty("Message").GetString();
+ var status = json.RootElement.GetProperty("Status").GetString();
+ 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/Sms/ISmsService.cs b/0_Framework/Application/Sms/ISmsService.cs
index bf55eb00..f0056ba1 100644
--- a/0_Framework/Application/Sms/ISmsService.cs
+++ b/0_Framework/Application/Sms/ISmsService.cs
@@ -32,6 +32,60 @@ public interface ISmsService
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);
+
+ #endregion
+
+ #region AlarmMessage
+
+ ///
+ /// ارسال پیامک های خطا یا اعمال ارسال
+ ///
+ ///
+ ///
+ ///
+ Task Alarm(string number, string message);
+
#endregion
}
\ 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/InfraStructure/FaceEmbeddingService.cs b/0_Framework/InfraStructure/FaceEmbeddingService.cs
new file mode 100644
index 00000000..0b709ed5
--- /dev/null
+++ b/0_Framework/InfraStructure/FaceEmbeddingService.cs
@@ -0,0 +1,346 @@
+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"
+ };
+ }
+ }
+}
diff --git a/0_Framework/InfraStructure/NullFaceEmbeddingNotificationService.cs b/0_Framework/InfraStructure/NullFaceEmbeddingNotificationService.cs
new file mode 100644
index 00000000..c760d2cd
--- /dev/null
+++ b/0_Framework/InfraStructure/NullFaceEmbeddingNotificationService.cs
@@ -0,0 +1,30 @@
+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