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/IPaymentGateway.cs b/0_Framework/Application/PaymentGateway/IPaymentGateway.cs index 306a75a2..3565785a 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 @@ -53,10 +55,12 @@ public class CreatePaymentGatewayRequest public string Mobile { get; set; } public string Email { get; set; } public string Description { 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..f11d1b46 --- /dev/null +++ b/0_Framework/Application/PaymentGateway/SepehrPaymentGateway.cs @@ -0,0 +1,97 @@ +using System; +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) + { + var extraData = JsonConvert.SerializeObject(command.ExtraData); + var res = await _httpClient.PostAsJsonAsync("GetToken", new + { + TerminalID = TerminalId, + Amount = command.Amount, + InvoiceID = command.InvoiceId, + 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/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 + + - - +
diff --git a/ServiceHost/Areas/AdminNew/Pages/Company/AndroidApk/Index.cshtml.cs b/ServiceHost/Areas/AdminNew/Pages/Company/AndroidApk/Index.cshtml.cs index 04d71de8..d23e28f8 100644 --- a/ServiceHost/Areas/AdminNew/Pages/Company/AndroidApk/Index.cshtml.cs +++ b/ServiceHost/Areas/AdminNew/Pages/Company/AndroidApk/Index.cshtml.cs @@ -2,6 +2,7 @@ using _0_Framework.Domain.CustomizeCheckoutShared.Enums; using AccountManagement.Domain.AccountLeftWorkAgg; using AccountMangement.Infrastructure.EFCore; +using Company.Domain.AndroidApkVersionAgg; using Company.Domain.CustomizeCheckoutAgg.ValueObjects; using Company.Domain.CustomizeCheckoutTempAgg.ValueObjects; using Company.Domain.RewardAgg; @@ -18,9 +19,15 @@ using System.Text.Json.Serialization; using _0_Framework.Application.PaymentGateway; using Company.Domain.InstitutionContractAgg; using Company.Domain.InstitutionPlanAgg; +using Company.Domain.PaymentTransactionAgg; using CompanyManagment.App.Contracts.InstitutionContract; +using CompanyManagment.App.Contracts.PaymentTransaction; using CompanyManagment.App.Contracts.TemporaryClientRegistration; using Microsoft.Extensions.Options; +using Parbad; +using Parbad.AspNetCore; +using Parbad.Gateway.Sepehr; +using System.ComponentModel.DataAnnotations; using static ServiceHost.Areas.AdminNew.Pages.Company.AndroidApk.IndexModel2; namespace ServiceHost.Areas.AdminNew.Pages.Company.AndroidApk @@ -34,21 +41,38 @@ namespace ServiceHost.Areas.AdminNew.Pages.Company.AndroidApk private readonly AccountContext _accountContext; private readonly IPaymentGateway _paymentGateway; private readonly ITemporaryClientRegistrationApplication _clientRegistrationApplication; + private readonly IHttpClientFactory _httpClientFactory; + private readonly IOnlinePayment _onlinePayment; + [BindProperty] public IFormFile File { get; set; } + + [BindProperty] + [Required(ErrorMessage = "لطفا نام ورژن را وارد کنید")] + [Display(Name = "نام ورژن")] + public string VersionName { get; set; } + + [BindProperty] + [Required(ErrorMessage = "لطفا کد ورژن را وارد کنید")] + [Display(Name = "کد ورژن")] + public string VersionCode { get; set; } + [BindProperty] public ApkType SelectedApkType { get; set; } + [BindProperty] public bool IsForce { get; set; } public IndexModel(IAndroidApkVersionApplication application, IRollCallDomainService rollCallDomainService, CompanyContext context, AccountContext accountContext, IHttpClientFactory httpClientFactory, IOptions appSetting, - ITemporaryClientRegistrationApplication clientRegistrationApplication) + ITemporaryClientRegistrationApplication clientRegistrationApplication, IOnlinePayment onlinePayment) { _application = application; _rollCallDomainService = rollCallDomainService; _context = context; _accountContext = accountContext; + _httpClientFactory = httpClientFactory; _clientRegistrationApplication = clientRegistrationApplication; - _paymentGateway = new AqayePardakhtPaymentGateway(httpClientFactory, appSetting); + _onlinePayment = onlinePayment; + _paymentGateway = new SepehrPaymentGateway(httpClientFactory); } public void OnGet() @@ -57,7 +81,7 @@ namespace ServiceHost.Areas.AdminNew.Pages.Company.AndroidApk public async Task OnPostUpload() { - var result = await _application.CreateAndActive(File); + var result = await _application.CreateAndActive(File,SelectedApkType, VersionName, VersionCode, IsForce); ViewData["message"] = result.Message; return Page(); } @@ -73,11 +97,34 @@ namespace ServiceHost.Areas.AdminNew.Pages.Company.AndroidApk //var notEndedRollCalls = rollCalls.Where(x => x.EndDate == null).ToList(); //RefactorAllTheRollCallsOnEsfand(endedRollCalls, notEndedRollCalls); //CreateRewardForKebabMahdi().GetAwaiter().GetResult(); - // SetEntityIdForCheckoutValues(); - // SetEntityIdForCheckoutValuesTemp(); - await CreateDadmehrWorkshopFaceEmbedding(); - ViewData["message"] = "ایجاد شد"; - return Page(); + + var callBack = Url.Action("Verify", "General", null, Request.Scheme); + + + var amount = 10000; + var transaction = new PaymentTransaction(30427, amount, "سید حسن مصباح", "https://client.gozareshgir.ir", PaymentTransactionGateWay.SepehrPay); + + _context.PaymentTransactions.Add(transaction); + await _context.SaveChangesAsync(); + + var command = new CreatePaymentGatewayRequest() + { + InvoiceId = transaction.id.ToString(), + Amount = amount, + CallBackUrl = callBack + }; + + var createRes = await _paymentGateway.Create(command); + + if (createRes.IsSuccess) + { + var payUrl = _paymentGateway.GetStartPayUrl(createRes.Token); + return Redirect(payUrl); + } + else + { + return BadRequest(createRes.Status + "خطا در ارسال به درگاه پرداخت"); + } } private async System.Threading.Tasks.Task CreateDadmehrWorkshopFaceEmbedding() @@ -211,7 +258,7 @@ namespace ServiceHost.Areas.AdminNew.Pages.Company.AndroidApk if (createResponse.Status == "success") { - return Redirect(_paymentGateway.GetStartPayUrl(createResponse.TransactionId)); + return Redirect(_paymentGateway.GetStartPayUrl(createResponse.Token)); } //TranslateCode(result?.ErrorCode); diff --git a/ServiceHost/Areas/Camera/Controllers/CameraController.cs b/ServiceHost/Areas/Camera/Controllers/CameraController.cs index 4578d4d4..579eafd0 100644 --- a/ServiceHost/Areas/Camera/Controllers/CameraController.cs +++ b/ServiceHost/Areas/Camera/Controllers/CameraController.cs @@ -9,6 +9,7 @@ using _0_Framework.Exceptions; using AccountManagement.Application.Contracts.Account; using AccountManagement.Application.Contracts.CameraAccount; using AccountManagement.Domain.TaskAgg; +using CompanyManagment.App.Contracts.AndroidApkVersion; using CompanyManagment.App.Contracts.PersonnleCode; using CompanyManagment.App.Contracts.RollCall; using CompanyManagment.App.Contracts.RollCallEmployee; @@ -36,6 +37,7 @@ public class CameraController : CameraBaseController private long _workshopId; private readonly IHttpClientFactory _httpClientFactory; private readonly HttpClient _faceEmbeddingHttpClient; + private readonly IAndroidApkVersionApplication _androidApkVersionApplication; public CameraController(IWebHostEnvironment webHostEnvironment, IConfiguration configuration, @@ -48,7 +50,7 @@ public class CameraController : CameraBaseController IAccountApplication accountApplication, IPasswordHasher passwordHasher, ICameraAccountApplication cameraAccountApplication, - IEmployeeFaceEmbeddingApplication employeeFaceEmbeddingApplication, IHttpClientFactory httpClientFactory) + IEmployeeFaceEmbeddingApplication employeeFaceEmbeddingApplication, IHttpClientFactory httpClientFactory, IAndroidApkVersionApplication androidApkVersionApplication) { _webHostEnvironment = webHostEnvironment; _configuration = configuration; @@ -63,6 +65,7 @@ public class CameraController : CameraBaseController _cameraAccountApplication = cameraAccountApplication; _employeeFaceEmbeddingApplication = employeeFaceEmbeddingApplication; _httpClientFactory = httpClientFactory; + _androidApkVersionApplication = androidApkVersionApplication; _faceEmbeddingHttpClient = httpClientFactory.CreateClient(); _faceEmbeddingHttpClient.BaseAddress = new Uri("http://localhost:8000/"); _workshopId= authHelper.GetWorkshopId(); @@ -74,7 +77,10 @@ public class CameraController : CameraBaseController public IActionResult CameraLogin([FromBody] CameraLoginRequest request) { _accountApplication.CameraLogin(request); - return Ok(); + return Ok(new + { + WorkshopId = _workshopId, + }); } @@ -214,6 +220,50 @@ public class CameraController : CameraBaseController } } + [AllowAnonymous] + [HttpGet("check-update")] + public async Task CheckUpdate([FromQuery] ApkType type, [FromQuery] int currentVersionCode = 0) + { + + var info = await _androidApkVersionApplication.GetLatestActiveInfo(type, currentVersionCode); + return Ok(new + { + latestVersionCode = info.LatestVersionCode, + latestVersionName = info.LatestVersionName, + shouldUpdate = info.ShouldUpdate, + isForceUpdate = info.IsForceUpdate, + downloadUrl = info.DownloadUrl, + releaseNotes = info.ReleaseNotes + }); + } + + [AllowAnonymous] + [HttpGet("download")] + public async Task Download([FromQuery] ApkType type) + { + // Check if APK exists + if (!_androidApkVersionApplication.HasAndroidApkToDownload(type)) + { + return NotFound(new { message = $"هیچ فایل APK فعالی برای {type} یافت نشد" }); + } + + // Get the path to the latest active APK + var path = await _androidApkVersionApplication.GetLatestActiveVersionPath(type); + + if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) + { + return NotFound(new { message = "فایل APK یافت نشد" }); + } + + // Set appropriate file name for download + var fileName = type == ApkType.WebView + ? "Gozareshgir.apk" + : "Gozareshgir-FaceDetection.apk"; + + // Return the file for download + return PhysicalFile(path, "application/vnd.android.package-archive", fileName); + } + [HttpGet("SendPersonelCodeToGetEmployeeId")] public IActionResult SendPersonelCodeToGetEmployeeId(long personelCode, long workshopId) diff --git a/ServiceHost/Areas/Client/Controllers/FinancialController.cs b/ServiceHost/Areas/Client/Controllers/FinancialController.cs index cdae7c2d..6b1d0f11 100644 --- a/ServiceHost/Areas/Client/Controllers/FinancialController.cs +++ b/ServiceHost/Areas/Client/Controllers/FinancialController.cs @@ -93,8 +93,8 @@ public class FinancialController : ClientBaseController if (gatewayResponse.IsSuccess) { - _ = await _paymentTransactionApplication.SetTransactionId(transaction.SendId, gatewayResponse.TransactionId); - return Redirect(_paymentGateway.GetStartPayUrl(gatewayResponse.TransactionId)); + _ = await _paymentTransactionApplication.SetTransactionId(transaction.SendId, gatewayResponse.Token); + return Redirect(_paymentGateway.GetStartPayUrl(gatewayResponse.Token)); } if (gatewayResponse.ErrorCode.HasValue) diff --git a/ServiceHost/Areas/Client/Pages/Company/RollCall/EmployeeUploadPicture.cshtml.cs b/ServiceHost/Areas/Client/Pages/Company/RollCall/EmployeeUploadPicture.cshtml.cs index f451487c..a1a432c6 100644 --- a/ServiceHost/Areas/Client/Pages/Company/RollCall/EmployeeUploadPicture.cshtml.cs +++ b/ServiceHost/Areas/Client/Pages/Company/RollCall/EmployeeUploadPicture.cshtml.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using System.Diagnostics; using System.Security.Claims; using System.Transactions; +using _0_Framework.Application.FaceEmbedding; using _0_Framework.Domain.CustomizeCheckoutShared.Enums; using _0_Framework.Infrastructure; using CompanyManagment.App.Contracts.CustomizeWorkshopSettings; @@ -24,47 +25,49 @@ using Microsoft.AspNetCore.Builder; namespace ServiceHost.Areas.Client.Pages.Company.RollCall { - [Authorize] - [NeedsPermission(SubAccountPermissionHelper.UploadEmployeePicturePermissionCode)] - public class EmployeeUploadPictureModel : PageModel - { - public string WorkshopFullName; - public RollCallEmployeeViewModel Employees; - public RollCallServiceViewModel RollCallService; - public bool HasEmployees; + [Authorize] + [NeedsPermission(SubAccountPermissionHelper.UploadEmployeePicturePermissionCode)] + public class EmployeeUploadPictureModel : PageModel + { + public string WorkshopFullName; + public RollCallEmployeeViewModel Employees; + public RollCallServiceViewModel RollCallService; + public bool HasEmployees; - //[BindProperty] - public int MaxPersonValid { get; set; } - public long WorkshopId; - public int PageIndex; + //[BindProperty] + public int MaxPersonValid { get; set; } + public long WorkshopId; + public int PageIndex; - private readonly IWorkshopApplication _workshopApplication; - private readonly IEmployeeApplication _employeeApplication; - private readonly IRollCallEmployeeApplication _rollCallEmployeeApplication; - private readonly IRollCallServiceApplication _rollCallServiceApplication; - private readonly IWebHostEnvironment _webHostEnvironment; - private readonly IPasswordHasher _passwordHasher; - private readonly IRollCallEmployeeStatusApplication _rollCallEmployeeStatusApplication; - private readonly ICustomizeWorkshopSettingsApplication _customizeWorkshopSettingsApplication; - private readonly IHttpContextAccessor _contextAccessor; - private readonly IPersonnelCodeApplication _personnelCodeApplication; - private readonly IEmployeeClientTempApplication _employeeClientTemp; - private readonly IEmployeeDocumentsApplication _employeeDocumentsApplication; - private readonly IJobApplication _jobApplication; + private readonly IWorkshopApplication _workshopApplication; + private readonly IEmployeeApplication _employeeApplication; + private readonly IRollCallEmployeeApplication _rollCallEmployeeApplication; + private readonly IRollCallServiceApplication _rollCallServiceApplication; + private readonly IWebHostEnvironment _webHostEnvironment; + private readonly IPasswordHasher _passwordHasher; + private readonly IRollCallEmployeeStatusApplication _rollCallEmployeeStatusApplication; + private readonly ICustomizeWorkshopSettingsApplication _customizeWorkshopSettingsApplication; + private readonly IHttpContextAccessor _contextAccessor; + private readonly IPersonnelCodeApplication _personnelCodeApplication; + private readonly IEmployeeClientTempApplication _employeeClientTemp; + private readonly IEmployeeDocumentsApplication _employeeDocumentsApplication; + private readonly IJobApplication _jobApplication; + private readonly IHttpClientFactory _httpClientFactory; + private readonly IFaceEmbeddingService _faceEmbeddingService; - - - - private readonly long _workshopId; + private readonly long _workshopId; public EmployeeUploadPictureModel(IWorkshopApplication workshopApplication, IPasswordHasher passwordHasher, IRollCallEmployeeApplication rollCallEmployeeApplication, - IJobApplication jobApplication, + IJobApplication jobApplication, IRollCallServiceApplication rollCallServiceApplication, IWebHostEnvironment webHostEnvironment, IEmployeeApplication employeeApplication, IRollCallEmployeeStatusApplication rollCallEmployeeStatusApplication, ICustomizeWorkshopSettingsApplication customizeWorkshopSettingsApplication, - IHttpContextAccessor contextAccessor, IPersonnelCodeApplication personnelCodeApplication, IEmployeeClientTempApplication employeeClientTempApplication, IEmployeeDocumentsApplication employeeDocumentsApplication) + IHttpContextAccessor contextAccessor, IPersonnelCodeApplication personnelCodeApplication, + IEmployeeClientTempApplication employeeClientTempApplication, + IEmployeeDocumentsApplication employeeDocumentsApplication, + IHttpClientFactory httpClientFactory, IFaceEmbeddingService faceEmbeddingService) { _workshopApplication = workshopApplication; _passwordHasher = passwordHasher; @@ -76,699 +79,769 @@ namespace ServiceHost.Areas.Client.Pages.Company.RollCall _customizeWorkshopSettingsApplication = customizeWorkshopSettingsApplication; _contextAccessor = contextAccessor; _personnelCodeApplication = personnelCodeApplication; - _employeeClientTemp = employeeClientTempApplication; - _employeeDocumentsApplication = employeeDocumentsApplication; - _jobApplication = jobApplication; + _employeeClientTemp = employeeClientTempApplication; + _employeeDocumentsApplication = employeeDocumentsApplication; + _jobApplication = jobApplication; + _httpClientFactory = httpClientFactory; + _faceEmbeddingService = faceEmbeddingService; + + var workshopHash = _contextAccessor.HttpContext?.User.FindFirstValue("WorkshopSlug"); + _workshopId = _passwordHasher.SlugDecrypt(workshopHash); + + if (_workshopId < 1) + throw new InvalidDataException("اختلال در کارگاه"); + } + + public IActionResult OnGet() + { + RollCallService = _rollCallServiceApplication.GetActiveServiceByWorkshopId(_workshopId); + if (RollCallService == null) + return Redirect("/Client/Company/RollCall"); + + var workshop = _workshopApplication.GetWorkshopInfo(_workshopId); + + WorkshopFullName = workshop.WorkshopFullName; + + // if (string.IsNullOrEmpty(HttpContext.Session.GetString("MaxPersonValid"))) + // { + // MaxPersonValid = RollCallService.MaxPersonValid; + //HttpContext.Session.SetString("MaxPersonValid", MaxPersonValid.ToString()); + // } + // else + // { + // MaxPersonValid = Convert.ToInt32(HttpContext.Session.GetString("MaxPersonValid")); + // } + + MaxPersonValid = RollCallService.MaxPersonValid; + //var distinctEmployees = _rollCallEmployeeApplication.GetPersonnelRollCallListPaginate(new RollCallEmployeeSearchModel() + //{ + + //}); + WorkshopId = _workshopId; + PageIndex = 0; + + HasEmployees = _rollCallEmployeeApplication.HasEmployees(_workshopId); + + return Page(); + } + + public IActionResult OnGetEmployeeUploadDataAjax(int pageIndex, string searchName) + { + //MaxPersonValid = _rollCallServiceApplication.GetActiveServiceByWorkshopId(workshopId).MaxPersonValid; + var distinctEmployees = _rollCallEmployeeApplication.GetPersonnelRollCallListPaginate( + new RollCallEmployeeSearchModel() + { + WorkshopId = _workshopId, + PageIndex = pageIndex, + Name = searchName + }); + + Employees = new RollCallEmployeeViewModel + { + PersonnelInfoViewModels = distinctEmployees + }; + return new JsonResult(new + { + isSuccedded = true, + data = Employees, + pageIndex = Employees.PersonnelInfoViewModels.Count() + }); + } + + public IActionResult OnGetLoadInfoCount() + { + //MaxPersonValid = Convert.ToInt32(HttpContext.Session.GetString("MaxPersonValid")); + var activeService = _rollCallServiceApplication.GetActiveServiceByWorkshopId(_workshopId); + MaxPersonValid = activeService.MaxPersonValid; - - var workshopHash = _contextAccessor.HttpContext?.User.FindFirstValue("WorkshopSlug"); - _workshopId = _passwordHasher.SlugDecrypt(workshopHash); - - if (_workshopId < 1) - throw new InvalidDataException("اختلال در کارگاه"); - } - - public IActionResult OnGet() - { - RollCallService = _rollCallServiceApplication.GetActiveServiceByWorkshopId(_workshopId); - if (RollCallService == null) - return Redirect("/Client/Company/RollCall"); - - var workshop = _workshopApplication.GetWorkshopInfo(_workshopId); - - WorkshopFullName = workshop.WorkshopFullName; - - // if (string.IsNullOrEmpty(HttpContext.Session.GetString("MaxPersonValid"))) - // { - // MaxPersonValid = RollCallService.MaxPersonValid; - //HttpContext.Session.SetString("MaxPersonValid", MaxPersonValid.ToString()); - // } - // else - // { - // MaxPersonValid = Convert.ToInt32(HttpContext.Session.GetString("MaxPersonValid")); - // } - - MaxPersonValid = RollCallService.MaxPersonValid; - //var distinctEmployees = _rollCallEmployeeApplication.GetPersonnelRollCallListPaginate(new RollCallEmployeeSearchModel() - //{ - - //}); - WorkshopId = _workshopId; - PageIndex = 0; - - HasEmployees = _rollCallEmployeeApplication.HasEmployees(_workshopId); - - return Page(); - } - - public IActionResult OnGetEmployeeUploadDataAjax(int pageIndex, string searchName) - { - //MaxPersonValid = _rollCallServiceApplication.GetActiveServiceByWorkshopId(workshopId).MaxPersonValid; - var distinctEmployees = _rollCallEmployeeApplication.GetPersonnelRollCallListPaginate( - new RollCallEmployeeSearchModel() - { - WorkshopId = _workshopId, - PageIndex = pageIndex, - Name = searchName - }); - - Employees = new RollCallEmployeeViewModel - { - PersonnelInfoViewModels = distinctEmployees - }; - return new JsonResult(new - { - isSuccedded = true, - data = Employees, - pageIndex = Employees.PersonnelInfoViewModels.Count() - }); - } - - public IActionResult OnGetLoadInfoCount() - { - //MaxPersonValid = Convert.ToInt32(HttpContext.Session.GetString("MaxPersonValid")); - var activeService = _rollCallServiceApplication.GetActiveServiceByWorkshopId(_workshopId); - MaxPersonValid = activeService.MaxPersonValid; + var employeesCount = _rollCallEmployeeApplication.GetActiveAndDeActiveRollCallEmployees(_workshopId); - var employeesCount = _rollCallEmployeeApplication.GetActiveAndDeActiveRollCallEmployees(_workshopId); + return new JsonResult(new + { + isSuccedded = true, + maxPersonValid = MaxPersonValid == -1 ? "نامحدود" : MaxPersonValid.ToString(), + isTrueActiveCount = employeesCount.activeEmployees, + isFalseActiveCount = employeesCount.deActiveEmployees, + message = "موفق" + }); + } + + public IActionResult OnGetCheckModalTakeImage() + { + //MaxPersonValid = Convert.ToInt32(HttpContext.Session.GetString("MaxPersonValid")); + + var plan = _rollCallServiceApplication.GetActiveServiceByWorkshopId(_workshopId); + + if (plan == null) + { + return new JsonResult(new + { + isSuccedded = false, + message = "شما سرویس خریداری شده ندارید" + }); + } + + if (plan.IsActiveString != "true") + { + return new JsonResult(new + { + isSuccedded = false, + message = "سرویس شما فعال نیست" + }); + } + + //var maxValid = RollCallService.MaxPersonValid; + + if (plan.MaxPersonValid == -1 || + _rollCallEmployeeApplication.activedPerson(_workshopId) < plan.MaxPersonValid) + { + return new JsonResult(new + { + isSuccedded = true, + message = "موفق" + }); + } + + return new JsonResult(new + { + isSuccedded = false, + message = "محدودیت افزودن پرسنل" + }); + } + + public IActionResult OnGetWorkshopSettingList() + { + var resultData = _customizeWorkshopSettingsApplication.GetWorkshopIncludeGroupsByWorkshopId(_workshopId); + resultData.GroupSettings = resultData.GroupSettings.Where(x => !x.MainGroup).ToList(); + + return new JsonResult(new + { + success = true, + data = resultData, + }); + } + + public IActionResult OnGetModalTakeImages(long employeeId) + { + var employeeWorkshopInfo = + _rollCallEmployeeApplication.GetByEmployeeIdAndWorkshopId(employeeId, _workshopId); + + bool hasPicture = false; + if (employeeWorkshopInfo != null && !string.IsNullOrEmpty(employeeWorkshopInfo.HasUploadedImage)) + hasPicture = bool.Parse(employeeWorkshopInfo.HasUploadedImage); + + var employeeDetails = _employeeApplication.GetDetails(employeeId); + + string employeeName = string.Empty; + if (employeeDetails != null) + employeeName = employeeDetails.EmployeeFullName; + + string pic1 = ""; + string pic2 = ""; + if (hasPicture) + { + string path1 = $"{_webHostEnvironment.ContentRootPath}\\Faces\\{_workshopId}\\{employeeId}\\{1}.jpg"; + string path2 = $"{_webHostEnvironment.ContentRootPath}\\Faces\\{_workshopId}\\{employeeId}\\{2}.jpg"; + + if (System.IO.File.Exists(path1)) + { + byte[] fileContent1 = System.IO.File.ReadAllBytes(path1); + pic1 = Convert.ToBase64String(fileContent1); + } + + if (System.IO.File.Exists(path2)) + { + byte[] fileContent2 = System.IO.File.ReadAllBytes(path2); + pic2 = Convert.ToBase64String(fileContent2); + } + + //byte[] fileContent1 = System.IO.File.ReadAllBytes($"{_webHostEnvironment.ContentRootPath}\\Faces\\{workshopId}\\{employeeId}\\{1}.jpg"); + //pic1 = Convert.ToBase64String(fileContent1); + //byte[] fileContent2 = System.IO.File.ReadAllBytes($"{_webHostEnvironment.ContentRootPath}\\Faces\\{workshopId}\\{employeeId}\\{2}.jpg"); + //pic2 = Convert.ToBase64String(fileContent2); + } - return new JsonResult(new - { - isSuccedded = true, - maxPersonValid = MaxPersonValid == -1 ? "نامحدود" : MaxPersonValid.ToString(), - isTrueActiveCount = employeesCount.activeEmployees, - isFalseActiveCount = employeesCount.deActiveEmployees, - message = "موفق" - }); - } + var workshopGroupSettings = + _customizeWorkshopSettingsApplication.GetWorkshopIncludeGroupsByWorkshopId(_workshopId); - public IActionResult OnGetCheckModalTakeImage() - { - //MaxPersonValid = Convert.ToInt32(HttpContext.Session.GetString("MaxPersonValid")); - - var plan = _rollCallServiceApplication.GetActiveServiceByWorkshopId(_workshopId); - - if (plan == null) - { - return new JsonResult(new - { - isSuccedded = false, - message = "شما سرویس خریداری شده ندارید" - }); - } - - if (plan.IsActiveString != "true") - { - return new JsonResult(new - { - isSuccedded = false, - message = "سرویس شما فعال نیست" - }); - } - - //var maxValid = RollCallService.MaxPersonValid; - - if (plan.MaxPersonValid == -1 || - _rollCallEmployeeApplication.activedPerson(_workshopId) < plan.MaxPersonValid) - { - return new JsonResult(new - { - isSuccedded = true, - message = "موفق" - }); - } - - return new JsonResult(new - { - isSuccedded = false, - message = "محدودیت افزودن پرسنل" - }); - } - - public IActionResult OnGetWorkshopSettingList() - { - var resultData = _customizeWorkshopSettingsApplication.GetWorkshopIncludeGroupsByWorkshopId(_workshopId); - resultData.GroupSettings = resultData.GroupSettings.Where(x => !x.MainGroup).ToList(); - - return new JsonResult(new - { - success = true, - data = resultData, - }); - } - - public IActionResult OnGetModalTakeImages(long employeeId) - { - var employeeWorkshopInfo = - _rollCallEmployeeApplication.GetByEmployeeIdAndWorkshopId(employeeId, _workshopId); - - bool hasPicture = false; - if (employeeWorkshopInfo != null && !string.IsNullOrEmpty(employeeWorkshopInfo.HasUploadedImage)) - hasPicture = bool.Parse(employeeWorkshopInfo.HasUploadedImage); - - var employeeDetails = _employeeApplication.GetDetails(employeeId); - - string employeeName = string.Empty; - if (employeeDetails != null) - employeeName = employeeDetails.EmployeeFullName; - - string pic1 = ""; - string pic2 = ""; - if (hasPicture) - { - string path1 = $"{_webHostEnvironment.ContentRootPath}\\Faces\\{_workshopId}\\{employeeId}\\{1}.jpg"; - string path2 = $"{_webHostEnvironment.ContentRootPath}\\Faces\\{_workshopId}\\{employeeId}\\{2}.jpg"; - - if (System.IO.File.Exists(path1)) - { - byte[] fileContent1 = System.IO.File.ReadAllBytes(path1); - pic1 = Convert.ToBase64String(fileContent1); - } - - if (System.IO.File.Exists(path2)) - { - byte[] fileContent2 = System.IO.File.ReadAllBytes(path2); - pic2 = Convert.ToBase64String(fileContent2); - } - - //byte[] fileContent1 = System.IO.File.ReadAllBytes($"{_webHostEnvironment.ContentRootPath}\\Faces\\{workshopId}\\{employeeId}\\{1}.jpg"); - //pic1 = Convert.ToBase64String(fileContent1); - //byte[] fileContent2 = System.IO.File.ReadAllBytes($"{_webHostEnvironment.ContentRootPath}\\Faces\\{workshopId}\\{employeeId}\\{2}.jpg"); - //pic2 = Convert.ToBase64String(fileContent2); - } + var employeeSettings = + _customizeWorkshopSettingsApplication.GetByEmployeeIdAndWorkshopIdIncludeGroupSettings(_workshopId, + employeeId); - var workshopGroupSettings = - _customizeWorkshopSettingsApplication.GetWorkshopIncludeGroupsByWorkshopId(_workshopId); + var employeeClientTemp = _employeeClientTemp.GetDetails(employeeId, _workshopId); + if (employeeClientTemp != null) + { + employeeDetails.FName = employeeClientTemp.FName; + employeeDetails.LName = employeeClientTemp.LName; - var employeeSettings = - _customizeWorkshopSettingsApplication.GetByEmployeeIdAndWorkshopIdIncludeGroupSettings(_workshopId, - employeeId); - + } - var employeeClientTemp = _employeeClientTemp.GetDetails(employeeId, _workshopId); - if (employeeClientTemp != null) - { - employeeDetails.FName = employeeClientTemp.FName; - employeeDetails.LName = employeeClientTemp.LName; - - } - - var rollCallEmployee = _rollCallEmployeeApplication.GetByEmployeeIdAndWorkshopId(employeeId, _workshopId); + var rollCallEmployee = _rollCallEmployeeApplication.GetByEmployeeIdAndWorkshopId(employeeId, _workshopId); - var res = new TakePictureModel() - { - HasPicture = hasPicture, - EmployeeId = employeeId, - WorkshopId = _workshopId, - Name = employeeName, - FirstNickName = rollCallEmployee?.EmployeeFName ?? employeeDetails.FName, - LastNickName = rollCallEmployee?.EmployeeLName ?? employeeDetails.LName, - Pic1 = pic1, - Pic2 = pic2, - GroupSettings = workshopGroupSettings, - EmployeeSettings = employeeSettings, - HasUploadedImage = rollCallEmployee?.HasUploadedImage == "true" - }; + var res = new TakePictureModel() + { + HasPicture = hasPicture, + EmployeeId = employeeId, + WorkshopId = _workshopId, + Name = employeeName, + FirstNickName = rollCallEmployee?.EmployeeFName ?? employeeDetails.FName, + LastNickName = rollCallEmployee?.EmployeeLName ?? employeeDetails.LName, + Pic1 = pic1, + Pic2 = pic2, + GroupSettings = workshopGroupSettings, + EmployeeSettings = employeeSettings, + HasUploadedImage = rollCallEmployee?.HasUploadedImage == "true" + }; - if (res.HasUploadedImage && res.EmployeeSettings != null) - { - return Partial("ModalTakeImagesEdit", res); - } - else - { - return Partial("ModalTakeImages", res); - } - } + if (res.HasUploadedImage && res.EmployeeSettings != null) + { + return Partial("ModalTakeImagesEdit", res); + } + else + { + return Partial("ModalTakeImages", res); + } + } - public IActionResult OnPostTakePicture(string base64pic1, string base64pic2, long workshopId, long employeeId, EditCustomizeEmployeeSettings command) - { + public async Task OnPostTakePicture(string base64pic1, string base64pic2, + long workshopId, long employeeId, EditCustomizeEmployeeSettings command) + { - try - { - using var transactionScope = new TransactionScope(); + try + { + using var transactionScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); - var directoryPath = $"{_webHostEnvironment.ContentRootPath}\\Faces\\{workshopId}\\{employeeId}"; - if (!Directory.Exists(directoryPath)) - Directory.CreateDirectory(directoryPath); + var directoryPath = $"{_webHostEnvironment.ContentRootPath}\\Faces\\{workshopId}\\{employeeId}"; + if (!Directory.Exists(directoryPath)) + Directory.CreateDirectory(directoryPath); - var filePath1 = Path.Combine(directoryPath) + $@"\1.jpg"; - CreateImageFromBase64(base64pic1, filePath1); - var filePath2 = Path.Combine(directoryPath) + $@"\2.jpg"; - CreateImageFromBase64(base64pic2, filePath2); + var filePath1 = Path.Combine(directoryPath) + $@"\1.jpg"; + CreateImageFromBase64(base64pic1, filePath1); + var filePath2 = Path.Combine(directoryPath) + $@"\2.jpg"; + CreateImageFromBase64(base64pic2, filePath2); - var employee = _employeeApplication.GetDetailsForClient(employeeId, workshopId); - var rollCallEmployee = - _rollCallEmployeeApplication.GetByEmployeeIdAndWorkshopId(employeeId, workshopId); + // Call Python API to generate embeddings + await SendEmbeddingsToApi(employeeId, workshopId, filePath1, filePath2); - var result = new OperationResult() - { - IsSuccedded = false, - Message = "هنوز عملیاتی انجام نشده است" - }; - if (rollCallEmployee == null) - { - var createCommand = new CreateRollCallEmployee() - { - EmployeeId = employeeId, - WorkshopId = workshopId, - EmployeeFullName = employee.EmployeeFullName, - HasUploadedImage = "true", - }; - result = _rollCallEmployeeApplication.Create(createCommand); - var createRollCallEmployeeStatus = _rollCallEmployeeStatusApplication.Create(new CreateRollCallEmployeeStatus() - { - RollCallEmployeeId = result.SendId - }); - if (result.IsSuccedded == false) - { - return new JsonResult(new - { - isSuccedded = result.IsSuccedded, - message = result.Message, - }); - } - if (createRollCallEmployeeStatus.IsSuccedded == false) - { - return new JsonResult(new - { - isSuccedded = createRollCallEmployeeStatus.IsSuccedded, - message = createRollCallEmployeeStatus.Message, - }); - } - _rollCallEmployeeStatusApplication.SyncRollCallEmployeeWithLeftWork(result.SendId); + var employee = _employeeApplication.GetDetailsForClient(employeeId, workshopId); + var rollCallEmployee = + _rollCallEmployeeApplication.GetByEmployeeIdAndWorkshopId(employeeId, workshopId); + + var result = new OperationResult() + { + IsSuccedded = false, + Message = "هنوز عملیاتی انجام نشده است" + }; + if (rollCallEmployee == null) + { + var createCommand = new CreateRollCallEmployee() + { + EmployeeId = employeeId, + WorkshopId = workshopId, + EmployeeFullName = employee.EmployeeFullName, + HasUploadedImage = "true", + }; + result = _rollCallEmployeeApplication.Create(createCommand); + var createRollCallEmployeeStatus = _rollCallEmployeeStatusApplication.Create(new CreateRollCallEmployeeStatus() + { + RollCallEmployeeId = result.SendId + }); + if (result.IsSuccedded == false) + { + return new JsonResult(new + { + isSuccedded = result.IsSuccedded, + message = result.Message, + }); + } + if (createRollCallEmployeeStatus.IsSuccedded == false) + { + return new JsonResult(new + { + isSuccedded = createRollCallEmployeeStatus.IsSuccedded, + message = createRollCallEmployeeStatus.Message, + }); + } + _rollCallEmployeeStatusApplication.SyncRollCallEmployeeWithLeftWork(result.SendId); } else - { - if ( rollCallEmployee.Statuses == null || rollCallEmployee.Statuses?.Any(x => x.StartDateGr <= DateTime.Now.Date && x.EndDateGr >= DateTime.Now.Date)== false) - { - var createRollCallEmployeeStatus = _rollCallEmployeeStatusApplication.Create(new CreateRollCallEmployeeStatus() - { - RollCallEmployeeId = rollCallEmployee.Id - }); - if (createRollCallEmployeeStatus.IsSuccedded ==false) - { - return new JsonResult(new - { - isSuccedded = createRollCallEmployeeStatus.IsSuccedded, - message = createRollCallEmployeeStatus.Message, - }); - } - } + { + if (rollCallEmployee.Statuses == null || rollCallEmployee.Statuses?.Any(x => x.StartDateGr <= DateTime.Now.Date && x.EndDateGr >= DateTime.Now.Date) == false) + { + var createRollCallEmployeeStatus = _rollCallEmployeeStatusApplication.Create(new CreateRollCallEmployeeStatus() + { + RollCallEmployeeId = rollCallEmployee.Id + }); + if (createRollCallEmployeeStatus.IsSuccedded == false) + { + return new JsonResult(new + { + isSuccedded = createRollCallEmployeeStatus.IsSuccedded, + message = createRollCallEmployeeStatus.Message, + }); + } + } - result = _rollCallEmployeeApplication.UploadedImage(employeeId, workshopId); - if (result.IsSuccedded == false) - { - return new JsonResult(new - { - isSuccedded = result.IsSuccedded, - message = result.Message, - }); - } - } + result = _rollCallEmployeeApplication.UploadedImage(employeeId, workshopId); + + + if (result.IsSuccedded == false) + { + return new JsonResult(new + { + isSuccedded = result.IsSuccedded, + message = result.Message, + }); + } + } - if (command.GroupId != 0) - { - command.EmployeeIds = [employeeId]; - command.WorkshopId = workshopId; - var employeeSettingsResult = _customizeWorkshopSettingsApplication.CreateEmployeesSettingsAndSetChanges(command); - if (employeeSettingsResult.IsSuccedded == false) - { - return new JsonResult(new - { - isSuccedded = employeeSettingsResult.IsSuccedded, - message = employeeSettingsResult.Message, - }); - } - } + var faceEmbeddingRes = _faceEmbeddingService.GenerateEmbeddingsAsync(employeeId, workshopId, employee.EmployeeFullName, filePath1, filePath2).GetAwaiter().GetResult(); + if (!faceEmbeddingRes.IsSuccedded) + { + return new JsonResult(new + { + isSuccedded = faceEmbeddingRes.IsSuccedded, + message = faceEmbeddingRes.Message, + }); + } - transactionScope.Complete(); - return new JsonResult(new - { - IsSuccedded = result.IsSuccedded, - Message = result.Message, - src = Tools.ResizeImage( - Path.Combine(_webHostEnvironment.ContentRootPath, "Faces", workshopId.ToString(), - employeeId.ToString(), "1.jpg"), 150, 150), - Id = result.SendId - }); + if (command.GroupId != 0) + { + command.EmployeeIds = [employeeId]; + command.WorkshopId = workshopId; + var employeeSettingsResult = _customizeWorkshopSettingsApplication.CreateEmployeesSettingsAndSetChanges(command); + if (employeeSettingsResult.IsSuccedded == false) + { + return new JsonResult(new + { + isSuccedded = employeeSettingsResult.IsSuccedded, + message = employeeSettingsResult.Message, + }); + } + } + + transactionScope.Complete(); + return new JsonResult(new + { + IsSuccedded = result.IsSuccedded, + Message = result.Message, + src = Tools.ResizeImage( + Path.Combine(_webHostEnvironment.ContentRootPath, "Faces", workshopId.ToString(), + employeeId.ToString(), "1.jpg"), 150, 150), + Id = result.SendId + }); - } - catch (Exception e) - { - Console.WriteLine(e); - return new JsonResult(new - { - IsSuccedded = false, - Message = e.Message, - }); - } - } + } + catch (Exception e) + { + Console.WriteLine(e); + return new JsonResult(new + { + IsSuccedded = false, + Message = e.Message, + }); + } + } - public void CreateImageFromBase64(string base64, string imagePathWithExtension) - { - var subBase64 = base64.Substring(base64.LastIndexOf(',') + 1); - byte[] bytes = Convert.FromBase64String(subBase64); - System.IO.File.WriteAllBytes(imagePathWithExtension, bytes); - } + public void CreateImageFromBase64(string base64, string imagePathWithExtension) + { + var subBase64 = base64.Substring(base64.LastIndexOf(',') + 1); + byte[] bytes = Convert.FromBase64String(subBase64); + System.IO.File.WriteAllBytes(imagePathWithExtension, bytes); + } - public IActionResult OnPostActivePersonnel(long id) - { - var hasRollCallEmployee = _rollCallEmployeeApplication.GetDetails(id); + public IActionResult OnPostActivePersonnel(long id) + { + var hasRollCallEmployee = _rollCallEmployeeApplication.GetDetails(id); - if (hasRollCallEmployee == null) - { - return new JsonResult(new - { - isSuccedded = false, - message = - "برای این پرسنل، هنوز هیچ عکسی آپلود نشده است. بعد از آپلود عکس بطور خودکار فعال خواهد شد", - }); - } - else - { - if (hasRollCallEmployee.HasUploadedImage == "false") - return new JsonResult(new - { - isSuccedded = false, - message = - "برای این پرسنل، هنوز هیچ عکسی آپلود نشده است. بعد از آپلود عکس بطور خودکار فعال خواهد شد", - }); - else - { - var employeeSettings = - _customizeWorkshopSettingsApplication.GetByEmployeeIdAndWorkshopIdIncludeGroupSettings( - _workshopId, hasRollCallEmployee.EmployeeId); - if (employeeSettings == null || employeeSettings.Id == 0) - { - return new JsonResult(new - { - isSuccedded = false, - HasEmployeeSetting = false, - message = "برای فعال سازی پرسنل می بایست حتما گروهبندی پرسنل را مشخص کنید" + if (hasRollCallEmployee == null) + { + return new JsonResult(new + { + isSuccedded = false, + message = + "برای این پرسنل، هنوز هیچ عکسی آپلود نشده است. بعد از آپلود عکس بطور خودکار فعال خواهد شد", + }); + } + else + { + if (hasRollCallEmployee.HasUploadedImage == "false") + return new JsonResult(new + { + isSuccedded = false, + message = + "برای این پرسنل، هنوز هیچ عکسی آپلود نشده است. بعد از آپلود عکس بطور خودکار فعال خواهد شد", + }); + else + { + var employeeSettings = + _customizeWorkshopSettingsApplication.GetByEmployeeIdAndWorkshopIdIncludeGroupSettings( + _workshopId, hasRollCallEmployee.EmployeeId); + if (employeeSettings == null || employeeSettings.Id == 0) + { + return new JsonResult(new + { + isSuccedded = false, + HasEmployeeSetting = false, + message = "برای فعال سازی پرسنل می بایست حتما گروهبندی پرسنل را مشخص کنید" - }); - } + }); + } - } + } - var result = _rollCallEmployeeApplication.Active(hasRollCallEmployee.Id); - if (result.IsSuccedded) - { - return new JsonResult(new - { - isSuccedded = result.IsSuccedded, - message = result.Message, - }); - } - else - { - return new JsonResult(new - { - isSuccedded = result.IsSuccedded, - message = result.Message, - }); - } - } + var result = _rollCallEmployeeApplication.Active(hasRollCallEmployee.Id); + if (result.IsSuccedded) + { + return new JsonResult(new + { + isSuccedded = result.IsSuccedded, + message = result.Message, + }); + } + else + { + return new JsonResult(new + { + isSuccedded = result.IsSuccedded, + message = result.Message, + }); + } + } - return new JsonResult(new - { - isSuccedded = false, - message = "خطایی رخ آمده است", - }); - } + return new JsonResult(new + { + isSuccedded = false, + message = "خطایی رخ آمده است", + }); + } - public IActionResult OnPostDeActivePersonnel(long id) - { - var result = _rollCallEmployeeApplication.DeActive(id); - if (result.IsSuccedded) - { - return new JsonResult(new - { - isSuccedded = result.IsSuccedded, - message = result.Message, - }); - } + public IActionResult OnPostDeActivePersonnel(long id) + { + var result = _rollCallEmployeeApplication.DeActive(id); + if (result.IsSuccedded) + { + return new JsonResult(new + { + isSuccedded = result.IsSuccedded, + message = result.Message, + }); + } - return new JsonResult(new - { - isSuccedded = result.IsSuccedded, - message = result.Message, - }); - } + return new JsonResult(new + { + isSuccedded = result.IsSuccedded, + message = result.Message, + }); + } - public IActionResult OnGetLoadFirstImage(long employeeId, long workshopId) - { - var directoryPath = $"{_webHostEnvironment.ContentRootPath}\\Faces\\{workshopId}\\{employeeId}"; - var imageDir = Path.Combine(directoryPath, "1.jpg"); - return PhysicalFile(imageDir, "image/jpeg"); - } + public IActionResult OnGetLoadFirstImage(long employeeId, long workshopId) + { + var directoryPath = $"{_webHostEnvironment.ContentRootPath}\\Faces\\{workshopId}\\{employeeId}"; + var imageDir = Path.Combine(directoryPath, "1.jpg"); + return PhysicalFile(imageDir, "image/jpeg"); + } - public IActionResult OnGetLoadSecondImage(long employeeId, long workshopId) - { - var directoryPath = $"{_webHostEnvironment.ContentRootPath}\\Faces\\{workshopId}\\{employeeId}"; - var imageDir = Path.Combine(directoryPath, "2.jpg"); - return PhysicalFile(imageDir, "image/jpeg"); - } + public IActionResult OnGetLoadSecondImage(long employeeId, long workshopId) + { + var directoryPath = $"{_webHostEnvironment.ContentRootPath}\\Faces\\{workshopId}\\{employeeId}"; + var imageDir = Path.Combine(directoryPath, "2.jpg"); + return PhysicalFile(imageDir, "image/jpeg"); + } - public IActionResult OnGetModalChangeName(long employeeId) - { - var command = _rollCallEmployeeApplication.GetByEmployeeIdAndWorkshopId(employeeId, _workshopId); + public IActionResult OnGetModalChangeName(long employeeId) + { + var command = _rollCallEmployeeApplication.GetByEmployeeIdAndWorkshopId(employeeId, _workshopId); - return Partial("ModalChangeName", command); - } + return Partial("ModalChangeName", command); + } - public IActionResult OnPostChangeName(long rollCallEmployeeId, string fName, string lName) - { - var result = _rollCallEmployeeApplication.ChangeEmployeeRollCallName(rollCallEmployeeId, fName, lName); - return new JsonResult(new - { - isSuccedded = result.IsSuccedded, - message = result.Message, - }); - } + public IActionResult OnPostChangeName(long rollCallEmployeeId, string fName, string lName) + { + var result = _rollCallEmployeeApplication.ChangeEmployeeRollCallName(rollCallEmployeeId, fName, lName); + return new JsonResult(new + { + isSuccedded = result.IsSuccedded, + message = result.Message, + }); + } - public IActionResult OnPostCreateEmployeeSettingsAndChangeNameAndCreateRollCallEmployeeStatus(EditCustomizeEmployeeSettings command, string fName, string lName) - { - using var transactionScope = new TransactionScope(); + public IActionResult OnPostCreateEmployeeSettingsAndChangeNameAndCreateRollCallEmployeeStatus(EditCustomizeEmployeeSettings command, string fName, string lName) + { + using var transactionScope = new TransactionScope(); - if (command.GroupId != 0) - { - var employeeSettingsResult = _customizeWorkshopSettingsApplication.CreateEmployeeSettings(command); - if (employeeSettingsResult.IsSuccedded == false) - { - return new JsonResult(new - { - isSuccedded = employeeSettingsResult.IsSuccedded, - message = employeeSettingsResult.Message, - }); - } - } - var rollCallEmployee = - _rollCallEmployeeApplication.GetByEmployeeIdAndWorkshopId(command.EmployeeIds.First(), _workshopId); + if (command.GroupId != 0) + { + var employeeSettingsResult = _customizeWorkshopSettingsApplication.CreateEmployeeSettings(command); + if (employeeSettingsResult.IsSuccedded == false) + { + return new JsonResult(new + { + isSuccedded = employeeSettingsResult.IsSuccedded, + message = employeeSettingsResult.Message, + }); + } + } + var rollCallEmployee = + _rollCallEmployeeApplication.GetByEmployeeIdAndWorkshopId(command.EmployeeIds.First(), _workshopId); - if (rollCallEmployee == null) - { - return new JsonResult(new - { - isSuccedded = false, - message = "لطفا بخش آپلود عکس خودرا تکمیل نمایید", - }); - } + if (rollCallEmployee == null) + { + return new JsonResult(new + { + isSuccedded = false, + message = "لطفا بخش آپلود عکس خودرا تکمیل نمایید", + }); + } - var createRollCallEmployeeStatus = _rollCallEmployeeStatusApplication.Create(new CreateRollCallEmployeeStatus() - { - RollCallEmployeeId = rollCallEmployee.Id - }); + var createRollCallEmployeeStatus = _rollCallEmployeeStatusApplication.Create(new CreateRollCallEmployeeStatus() + { + RollCallEmployeeId = rollCallEmployee.Id + }); - if (createRollCallEmployeeStatus.IsSuccedded == false) - { - return new JsonResult(new - { - isSuccedded = createRollCallEmployeeStatus.IsSuccedded, - message = createRollCallEmployeeStatus.Message, - }); - } + if (createRollCallEmployeeStatus.IsSuccedded == false) + { + return new JsonResult(new + { + isSuccedded = createRollCallEmployeeStatus.IsSuccedded, + message = createRollCallEmployeeStatus.Message, + }); + } - var changeNameResult = _rollCallEmployeeApplication.ChangeEmployeeRollCallName(rollCallEmployee.Id, fName, lName); - if (changeNameResult.IsSuccedded == false) - { - return new JsonResult(new - { - isSuccedded = changeNameResult.IsSuccedded, - message = changeNameResult.Message, - }); - } + var changeNameResult = _rollCallEmployeeApplication.ChangeEmployeeRollCallName(rollCallEmployee.Id, fName, lName); + if (changeNameResult.IsSuccedded == false) + { + return new JsonResult(new + { + isSuccedded = changeNameResult.IsSuccedded, + message = changeNameResult.Message, + }); + } - transactionScope.Complete(); + transactionScope.Complete(); - return new JsonResult(new - { - isSuccedded = true, - message = createRollCallEmployeeStatus.Message, - }); - } + return new JsonResult(new + { + isSuccedded = true, + message = createRollCallEmployeeStatus.Message, + }); + } - public IActionResult OnPostCreateEmployeeSettingsAndChangeNameAndCreateRollCallEmployeeStatusModalStatus(long employeeId) - { - var employeeWorkshopInfo = - _rollCallEmployeeApplication.GetByEmployeeIdAndWorkshopId(employeeId, _workshopId); + public IActionResult OnPostCreateEmployeeSettingsAndChangeNameAndCreateRollCallEmployeeStatusModalStatus(long employeeId) + { + var employeeWorkshopInfo = + _rollCallEmployeeApplication.GetByEmployeeIdAndWorkshopId(employeeId, _workshopId); - bool hasPicture = false; - if (employeeWorkshopInfo != null && !string.IsNullOrEmpty(employeeWorkshopInfo.HasUploadedImage)) - hasPicture = bool.Parse(employeeWorkshopInfo.HasUploadedImage); + bool hasPicture = false; + if (employeeWorkshopInfo != null && !string.IsNullOrEmpty(employeeWorkshopInfo.HasUploadedImage)) + hasPicture = bool.Parse(employeeWorkshopInfo.HasUploadedImage); - var employeeDetails = _employeeApplication.GetDetails(employeeId); + var employeeDetails = _employeeApplication.GetDetails(employeeId); - string employeeName = string.Empty; - if (employeeDetails != null) - employeeName = employeeDetails.EmployeeFullName; + string employeeName = string.Empty; + if (employeeDetails != null) + employeeName = employeeDetails.EmployeeFullName; - var workshopGroupSettings = - _customizeWorkshopSettingsApplication.GetWorkshopIncludeGroupsByWorkshopId(_workshopId); + var workshopGroupSettings = + _customizeWorkshopSettingsApplication.GetWorkshopIncludeGroupsByWorkshopId(_workshopId); - var employeeSettings = - _customizeWorkshopSettingsApplication.GetByEmployeeIdAndWorkshopIdIncludeGroupSettings(_workshopId, - employeeId); + var employeeSettings = + _customizeWorkshopSettingsApplication.GetByEmployeeIdAndWorkshopIdIncludeGroupSettings(_workshopId, + employeeId); - var rollCallEmployee = _rollCallEmployeeApplication.GetByEmployeeIdAndWorkshopId(employeeId, _workshopId); + var rollCallEmployee = _rollCallEmployeeApplication.GetByEmployeeIdAndWorkshopId(employeeId, _workshopId); - var res = new TakePictureModel() - { - EmployeeId = employeeId, - WorkshopId = _workshopId, - Name = employeeName, - FirstNickName = rollCallEmployee?.EmployeeFName ?? employeeDetails.FName, - LastNickName = rollCallEmployee?.EmployeeLName ?? employeeDetails.LName, + var res = new TakePictureModel() + { + EmployeeId = employeeId, + WorkshopId = _workshopId, + Name = employeeName, + FirstNickName = rollCallEmployee?.EmployeeFName ?? employeeDetails.FName, + LastNickName = rollCallEmployee?.EmployeeLName ?? employeeDetails.LName, - GroupSettings = workshopGroupSettings, - EmployeeSettings = employeeSettings, - HasUploadedImage = rollCallEmployee?.HasUploadedImage == "true" - }; + GroupSettings = workshopGroupSettings, + EmployeeSettings = employeeSettings, + HasUploadedImage = rollCallEmployee?.HasUploadedImage == "true" + }; - return Partial("ModalTakeImages", res); - } + return Partial("ModalTakeImages", res); + } - public IActionResult OnPostCreateEmployeeSettingsAndChangeNameAndCreateRollCallEmployeeStatusModalStatusSave(EditCustomizeEmployeeSettings command, string fName, string lName) - { - using var transactionScope = new TransactionScope(); + public IActionResult OnPostCreateEmployeeSettingsAndChangeNameAndCreateRollCallEmployeeStatusModalStatusSave(EditCustomizeEmployeeSettings command, string fName, string lName) + { + using var transactionScope = new TransactionScope(); - if (command.GroupId != 0) - { - var employeeSettingsResult = _customizeWorkshopSettingsApplication.CreateEmployeeSettings(command); - if (employeeSettingsResult.IsSuccedded == false) - { - return new JsonResult(new - { - isSuccedded = employeeSettingsResult.IsSuccedded, - message = employeeSettingsResult.Message, - }); - } - } - var rollCallEmployee = - _rollCallEmployeeApplication.GetByEmployeeIdAndWorkshopId(command.EmployeeIds.First(), _workshopId); + if (command.GroupId != 0) + { + var employeeSettingsResult = _customizeWorkshopSettingsApplication.CreateEmployeeSettings(command); + if (employeeSettingsResult.IsSuccedded == false) + { + return new JsonResult(new + { + isSuccedded = employeeSettingsResult.IsSuccedded, + message = employeeSettingsResult.Message, + }); + } + } + var rollCallEmployee = + _rollCallEmployeeApplication.GetByEmployeeIdAndWorkshopId(command.EmployeeIds.First(), _workshopId); - if (rollCallEmployee == null) - { - return new JsonResult(new - { - isSuccedded = false, - message = "لطفا بخش آپلود عکس خودرا تکمیل نمایید", - }); - } + if (rollCallEmployee == null) + { + return new JsonResult(new + { + isSuccedded = false, + message = "لطفا بخش آپلود عکس خودرا تکمیل نمایید", + }); + } - var createRollCallEmployeeStatus = _rollCallEmployeeStatusApplication.Create(new CreateRollCallEmployeeStatus() - { - RollCallEmployeeId = rollCallEmployee.Id - }); + var createRollCallEmployeeStatus = _rollCallEmployeeStatusApplication.Create(new CreateRollCallEmployeeStatus() + { + RollCallEmployeeId = rollCallEmployee.Id + }); - if (createRollCallEmployeeStatus.IsSuccedded == false) - { - return new JsonResult(new - { - isSuccedded = createRollCallEmployeeStatus.IsSuccedded, - message = createRollCallEmployeeStatus.Message, - }); - } + if (createRollCallEmployeeStatus.IsSuccedded == false) + { + return new JsonResult(new + { + isSuccedded = createRollCallEmployeeStatus.IsSuccedded, + message = createRollCallEmployeeStatus.Message, + }); + } - var changeNameResult = _rollCallEmployeeApplication.ChangeEmployeeRollCallName(rollCallEmployee.Id, fName, lName); - if (changeNameResult.IsSuccedded == false) - { - return new JsonResult(new - { - isSuccedded = changeNameResult.IsSuccedded, - message = changeNameResult.Message, - }); - } + var changeNameResult = _rollCallEmployeeApplication.ChangeEmployeeRollCallName(rollCallEmployee.Id, fName, lName); + if (changeNameResult.IsSuccedded == false) + { + return new JsonResult(new + { + isSuccedded = changeNameResult.IsSuccedded, + message = changeNameResult.Message, + }); + } - transactionScope.Complete(); + transactionScope.Complete(); - return new JsonResult(new - { - isSuccedded = true, - message = createRollCallEmployeeStatus.Message, - }); - } + return new JsonResult(new + { + isSuccedded = true, + message = createRollCallEmployeeStatus.Message, + }); + } - public IActionResult OnGetCreateEmployee() - { - var command = new CreateEmployeeByClient(); - command.PersonnelCode = (_personnelCodeApplication.GetLastPersonnelCodeByWorkshop(_workshopId) + 1).ToString(); - command.HasRollCallService = _rollCallServiceApplication.IsExistActiveServiceByWorkshopId(_workshopId); - return Partial("../Employees/CreateEmployeeModal", command); - } + public IActionResult OnGetCreateEmployee() + { + var command = new CreateEmployeeByClient(); + command.PersonnelCode = (_personnelCodeApplication.GetLastPersonnelCodeByWorkshop(_workshopId) + 1).ToString(); + command.HasRollCallService = _rollCallServiceApplication.IsExistActiveServiceByWorkshopId(_workshopId); + return Partial("../Employees/CreateEmployeeModal", command); + } - public IActionResult OnPostSaveSubmit(SubmitEmployeeDocuments cmd) - { - var result = _employeeDocumentsApplication.SubmitDocumentItemsByClient(cmd); + public IActionResult OnPostSaveSubmit(SubmitEmployeeDocuments cmd) + { + var result = _employeeDocumentsApplication.SubmitDocumentItemsByClient(cmd); - return new JsonResult(new - { - isSuccedded = result.IsSuccedded, - message = result.Message, - }); - } + return new JsonResult(new + { + isSuccedded = result.IsSuccedded, + message = result.Message, + }); + } - public IActionResult OnPostCreateEmployee(CreateEmployeeByClient command) - { - command.WorkshopId = _workshopId; - var result = _employeeApplication.CreateEmployeeByClient(command); - return new JsonResult(new - { - success = result.IsSuccedded, - message = result.Message, - }); - } + public IActionResult OnPostCreateEmployee(CreateEmployeeByClient command) + { + command.WorkshopId = _workshopId; + var result = _employeeApplication.CreateEmployeeByClient(command); + return new JsonResult(new + { + success = result.IsSuccedded, + message = result.Message, + }); + } - public async Task OnGetEmployeeDetailsWithNationalCode(string nationalCode,string birthDate) - { - var result = await _employeeApplication.ValidateCreateEmployeeClientByNationalCodeAndWorkshopId(nationalCode, birthDate, _workshopId); - return new JsonResult(result); - } + public async Task OnGetEmployeeDetailsWithNationalCode(string nationalCode, string birthDate) + { + var result = await _employeeApplication.ValidateCreateEmployeeClientByNationalCodeAndWorkshopId(nationalCode, birthDate, _workshopId); + return new JsonResult(result); + } - public IActionResult OnGetJobSearch(string jobName) - { - var jobViewModels = _jobApplication.GetJobListByText(jobName); + public IActionResult OnGetJobSearch(string jobName) + { + var jobViewModels = _jobApplication.GetJobListByText(jobName); - return new JsonResult(jobViewModels); - } - } + return new JsonResult(jobViewModels); + } + + private async Task SendEmbeddingsToApi(long employeeId, long workshopId, string picture1Path, string picture2Path) + { + try + { + var httpClient = _httpClientFactory.CreateClient(); + httpClient.Timeout = TimeSpan.FromSeconds(30); + + var employee = _employeeApplication.GetDetailsForClient(employeeId, workshopId); + string employeeFullName = employee?.EmployeeFullName ?? ""; + + 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 + var picture1Bytes = await System.IO.File.ReadAllBytesAsync(picture1Path); + var picture1Content = new ByteArrayContent(picture1Bytes); + picture1Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg"); + content.Add(picture1Content, "picture1", "1.jpg"); + + var picture2Bytes = await System.IO.File.ReadAllBytesAsync(picture2Path); + var picture2Content = new ByteArrayContent(picture2Bytes); + picture2Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg"); + content.Add(picture2Content, "picture2", "2.jpg"); + + // Send request to Python API + var response = await httpClient.PostAsync("http://localhost:8000/embeddings", content); + + if (!response.IsSuccessStatusCode) + { + var errorContent = await response.Content.ReadAsStringAsync(); + Console.WriteLine($"Failed to generate embeddings. Status: {response.StatusCode}, Error: {errorContent}"); + } + else + { + var responseContent = await response.Content.ReadAsStringAsync(); + Console.WriteLine($"Embeddings generated successfully: {responseContent}"); + } + } + catch (HttpRequestException ex) + { + Console.WriteLine($"HTTP error while calling embeddings API: {ex.Message}"); + // Don't throw - we don't want to break the upload process if the API is unavailable + } + catch (Exception ex) + { + Console.WriteLine($"Error while calling embeddings API: {ex.Message}"); + // Don't throw - we don't want to break the upload process + } + } + } } diff --git a/ServiceHost/Areas/Client/Pages/Index.cshtml.cs b/ServiceHost/Areas/Client/Pages/Index.cshtml.cs index 18436670..12560871 100644 --- a/ServiceHost/Areas/Client/Pages/Index.cshtml.cs +++ b/ServiceHost/Areas/Client/Pages/Index.cshtml.cs @@ -65,6 +65,7 @@ namespace ServiceHost.Areas.Client.Pages #region Mahan public bool HasInsuranceToConfirm { get; set; } public bool HasApkToDownload { get; set; } + public bool HasFaceDetectionApkToDownload { get; set; } #endregion public IndexModel(IAuthHelper authHelper, IPasswordHasher passwordHasher, IWorkshopApplication workshopApplication, ILeaveApplication leaveApplication, IEmployeeApplication employeeApplication, IPaymentToEmployeeItemApplication paymentToEmployeeItemApplication, IPaymentToEmployeeApplication paymentToEmployeeApplication, IHolidayItemApplication holidayItemApplication, IInsuranceListApplication insuranceListApplication, IAndroidApkVersionApplication androidApkVersionApplication, IRollCallServiceApplication rollCallServiceApplication, IPersonnelCodeApplication personnelCodeApplication, ICustomizeWorkshopSettingsApplication customizeWorkshopSettingsApplication, IBankApplication bankApplication, ILeftWorkTempApplication leftWorkTempApplication, IJobApplication jobApplication, ICustomizeWorkshopSettingsApplication customizeWorkshopEmployee, IRollCallEmployeeStatusApplication rollCallEmployeeStatusApplication, ICameraAccountApplication cameraAccountApplication) @@ -100,7 +101,8 @@ namespace ServiceHost.Areas.Client.Pages profilePicture = account.ProfilePhoto; AccountFullName = account.Fullname; var todayGr = DateTime.Now; - HasApkToDownload = _androidApkVersionApplication.HasAndroidApkToDownload(); + HasApkToDownload = _androidApkVersionApplication.HasAndroidApkToDownload(ApkType.WebView); + HasFaceDetectionApkToDownload = _androidApkVersionApplication.HasAndroidApkToDownload(ApkType.FaceDetection); #region Mahan diff --git a/ServiceHost/Controllers/GeneralController.cs b/ServiceHost/Controllers/GeneralController.cs index eba3bf55..1209c2c4 100644 --- a/ServiceHost/Controllers/GeneralController.cs +++ b/ServiceHost/Controllers/GeneralController.cs @@ -1,14 +1,24 @@ using _0_Framework.Application; +using _0_Framework.Application.PaymentGateway; +using Company.Domain.BankAgg; +using Company.Domain.PaymentTransactionAgg; +using CompanyManagment.App.Contracts.FinancialStatment; +using CompanyManagment.App.Contracts.FinancilTransaction; using CompanyManagment.App.Contracts.PaymentTransaction; +using CompanyManagment.App.Contracts.Workshop; using CompanyManagment.EFCore.Migrations; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using ServiceHost.BaseControllers; -using System.Globalization; -using _0_Framework.Application.PaymentGateway; using Microsoft.Extensions.Options; -using CompanyManagment.App.Contracts.FinancialStatment; -using CompanyManagment.App.Contracts.FinancilTransaction; +using Newtonsoft.Json; +using NuGet.Protocol; +using Parbad; +using ServiceHost.BaseControllers; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Net.Http; +using System.Security.Cryptography; +using System.Threading; namespace ServiceHost.Controllers; @@ -18,12 +28,14 @@ public class GeneralController : GeneralBaseController private readonly IPaymentTransactionApplication _paymentTransactionApplication; private readonly IPaymentGateway _paymentGateway; private readonly IFinancialStatmentApplication _financialStatmentApplication; + private readonly IOnlinePayment _onlinePayment; - public GeneralController(IPaymentTransactionApplication paymentTransactionApplication,IHttpClientFactory clientFactory, IFinancialStatmentApplication financialStatmentApplication, IOptions appSetting) + public GeneralController(IPaymentTransactionApplication paymentTransactionApplication,IHttpClientFactory clientFactory, IFinancialStatmentApplication financialStatmentApplication, IOptions appSetting, IOnlinePayment onlinePayment) { _paymentTransactionApplication = paymentTransactionApplication; - _paymentGateway = new AqayePardakhtPaymentGateway(clientFactory, appSetting); + _paymentGateway = new SepehrPaymentGateway(clientFactory); _financialStatmentApplication = financialStatmentApplication; + _onlinePayment = onlinePayment; } /// @@ -47,7 +59,103 @@ public class GeneralController : GeneralBaseController }); } - [HttpPost("/api/callback")] + + [HttpGet("/api/callback"), HttpPost("/api/callback")] + public async Task Verify(SepehrGatewayPayResponse payResponse) + { + if (!long.TryParse(payResponse.invoiceid, out var paymentTransactionId)) + { + return BadRequest("Invalid invoice_id"); + } + + var transaction = await _paymentTransactionApplication.GetDetails(paymentTransactionId); + + if (transaction == null) + { + return NotFound("Transaction not found"); + } + + if (transaction.Status != PaymentTransactionStatus.Pending) + { + return BadRequest("این تراکنش قبلا پرداخت شده است"); + } + + + if (payResponse.respcode != 0) + { + return await HandleFailedTransaction(transaction); + } + + var verifyCommand = new VerifyPaymentGateWayRequest() + { + Amount = transaction.Amount, + TransactionId = payResponse.invoiceid, + DigitalReceipt = payResponse.digitalreceipt + }; + var verifyRes = await _paymentGateway.Verify(verifyCommand, CancellationToken.None); + + + + + if (verifyRes.IsSuccess) + { + var command = new CreateFinancialStatment() + { + ContractingPartyId = transaction.ContractingPartyId, + Deptor = 0, + Creditor = transaction.Amount, + DeptorString = "0", + TypeOfTransaction = "credit", + DescriptionOption = "بابت قرارداد مابین (روابط کار)", + Description = "درگاه بانکی", + + }; + var statementResult = _financialStatmentApplication.CreateFromBankGateway(command); + if (!statementResult.IsSuccedded) + { + return new JsonResult(statementResult); + } + + var setSuccessResult = _paymentTransactionApplication.SetSuccess(paymentTransactionId, payResponse.cardnumber, payResponse.issuerbank, payResponse.rrn.ToString(), payResponse.digitalreceipt); + + if (!setSuccessResult.IsSuccedded) + { + return await HandleFailedTransaction(transaction); + } + return Redirect(BuildCallbackUrl(transaction.CallBackUrl, true, transaction.Id)); + } + + // در غیر این صورت تراکنش ناموفق است + return await HandleFailedTransaction(transaction); + + + //var data = JsonConvert.SerializeObject(invoice.AdditionalData); + //var statics = + //JsonConvert.SerializeObject(res); + + //await _onlinePayment.CancelAsync(invoice); + //return new JsonResult(new + //{ + // data, + // statics + //}); + + //// Check if the invoice is new, or it's already processed before. + //if (invoice.Status != PaymentFetchResultStatus.ReadyForVerifying) + //{ + // // You can also see if the invoice is already verified before. + // var isAlreadyVerified = invoice.IsAlreadyVerified; + + // return Content("The payment was not successful."); + //} + + //// Note: Save the verifyResult.TransactionCode in your database. + + + + } + + [HttpPost("/api/callback3232")] public async Task OnGetCallBack(string? transid, string? cardnumber, string? tracking_number, string bank, string invoice_id, string? status,CancellationToken cancellationToken) { @@ -70,7 +178,7 @@ public class GeneralController : GeneralBaseController // اگر شماره کارت یا شماره پیگیری خالی باشد، تراکنش ناموفق است if (string.IsNullOrWhiteSpace(cardnumber) || string.IsNullOrWhiteSpace(tracking_number)) { - return await HandleFailedTransaction(transaction, paymentTransactionId); + return await HandleFailedTransaction(transaction); } var verifyCommand = new VerifyPaymentGateWayRequest() @@ -97,10 +205,10 @@ public class GeneralController : GeneralBaseController var statementResult = _financialStatmentApplication.CreateFromBankGateway(command); if (!statementResult.IsSuccedded) { - return await HandleFailedTransaction(transaction, paymentTransactionId); + return await HandleFailedTransaction(transaction); } - var setSuccessResult = _paymentTransactionApplication.SetSuccess(paymentTransactionId, cardnumber, bank); + var setSuccessResult = _paymentTransactionApplication.SetSuccess(paymentTransactionId, cardnumber, bank,null,null); if (!setSuccessResult.IsSuccedded) { @@ -110,12 +218,12 @@ public class GeneralController : GeneralBaseController } // در غیر این صورت تراکنش ناموفق است - return await HandleFailedTransaction(transaction, paymentTransactionId); + return await HandleFailedTransaction(transaction); } - private async Task HandleFailedTransaction(PaymentTransactionDetailsViewModel transaction, long transactionId) + private async Task HandleFailedTransaction(PaymentTransactionDetailsViewModel transaction) { - var result = _paymentTransactionApplication.SetFailed(transactionId); + var result = _paymentTransactionApplication.SetFailed(transaction.Id); if (!result.IsSuccedded) { return new JsonResult(result); @@ -131,4 +239,81 @@ public class GeneralController : GeneralBaseController } +} + +public class TokenReq +{ + + public long Amount { get; set; } + + public string CallbackUrl { get; set; } + [Display(Name = "شماره فاکتور")] + [MaxLength(100)] + [Required] + [Key] + // be ezaye har pazirande bayad yekta bashad + public string invoiceID { get; set; } + [Required] + public long terminalID { get; set; } + /* + * JSON Bashad + * etelaate takmili site harchi + * nabayad char khas dashte bashe (*'"xp_%!+- ...) + */ + public string Payload { get; set; } = ""; + public string email { get; set; } +} +public class TokenResp +{ + // if 0 = success + public int Status { get; set; } + public string AccessToken { get; set; } +} +public class PayRequest +{ + [Required] + [MaxLength(3000)] + public string token { get; set; } + [Required] + public long terminalID { get; set; } + public string nationalCode { get; set; } + +} +public class SepehrGatewayPayResponse +{ + /* 0 yni movafaq + * -1 yni enseraf + * -2 yni etmam zaman + */ + public int respcode { get; set; } + [Display(Name = "متن نتیجه تراکنش")] + public string respmsg { get; set; } + [Display(Name = "مبلغ کسر شده از مشتری")] + public long amount { get; set; } + [Display(Name = " شماره فاکتور ")] + public string invoiceid { get; set; } + [Display(Name = " اطلاعاتی که پذیرنده ارسال کرد ")] + public string payload { get; set; } + [Display(Name = " شماره ترمینال ")] + public long terminalid { get; set; } + [Display(Name = " شماره پیگیری ")] + public long tracenumber { get; set; } + // bayad negah dari she hatman to db + [Display(Name = " شماره سند بانکی ")] + public long rrn { get; set; } + [Display(Name = " زمان و تاریخ پرداخت ")] + public string datepaid { get; set; } + [Display(Name = " رسید دیجیتال ")] + public string digitalreceipt { get; set; } + [Display(Name = " نام بانک صادر کننده کارت ")] + public string issuerbank { get; set; } + [Display(Name = " شماره کارت ")] + public string cardnumber { get; set; } +} + +public class AdviceReq +{ + [Display(Name = " رسید دیجیتال ")] + public string digitalreceipt { get; set; } + public long Tid { get; set; } } \ No newline at end of file diff --git a/ServiceHost/FaceEmbeddingNotificationService.cs b/ServiceHost/FaceEmbeddingNotificationService.cs new file mode 100644 index 00000000..0f9291f9 --- /dev/null +++ b/ServiceHost/FaceEmbeddingNotificationService.cs @@ -0,0 +1,58 @@ +using _0_Framework.Application.FaceEmbedding; +using Microsoft.AspNetCore.SignalR; +using ServiceHost.Hubs; +using System.Threading.Tasks; + +namespace ServiceHost +{ + /// + /// پیاده‌سازی سرویس اطلاع‌رسانی Face Embedding با استفاده از SignalR + /// + public class FaceEmbeddingNotificationService : IFaceEmbeddingNotificationService + { + private readonly IHubContext _hubContext; + + public FaceEmbeddingNotificationService(IHubContext hubContext) + { + _hubContext = hubContext; + } + + public async Task NotifyEmbeddingCreatedAsync(long workshopId, long employeeId, string employeeFullName) + { + var groupName = FaceEmbeddingHub.GetGroupName(workshopId); + + await _hubContext.Clients.Group(groupName).SendAsync("EmbeddingCreated", new + { + workshopId, + employeeId, + employeeFullName, + timestamp = System.DateTime.UtcNow + }); + } + + public async Task NotifyEmbeddingDeletedAsync(long workshopId, long employeeId) + { + var groupName = FaceEmbeddingHub.GetGroupName(workshopId); + + await _hubContext.Clients.Group(groupName).SendAsync("EmbeddingDeleted", new + { + workshopId, + employeeId, + timestamp = System.DateTime.UtcNow + }); + } + + public async Task NotifyEmbeddingRefinedAsync(long workshopId, long employeeId) + { + var groupName = FaceEmbeddingHub.GetGroupName(workshopId); + + await _hubContext.Clients.Group(groupName).SendAsync("EmbeddingRefined", new + { + workshopId, + employeeId, + timestamp = System.DateTime.UtcNow + }); + } + } +} + diff --git a/ServiceHost/Hubs/FaceEmbeddingHub.cs b/ServiceHost/Hubs/FaceEmbeddingHub.cs new file mode 100644 index 00000000..5ea969dc --- /dev/null +++ b/ServiceHost/Hubs/FaceEmbeddingHub.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.SignalR; + +namespace ServiceHost.Hubs +{ + public class FaceEmbeddingHub : Hub + { + public async Task JoinWorkshopGroup(long workshopId) + { + await Groups.AddToGroupAsync(Context.ConnectionId, GetGroupName(workshopId)); + } + + public async Task LeaveWorkshopGroup(long workshopId) + { + await Groups.RemoveFromGroupAsync(Context.ConnectionId, GetGroupName(workshopId)); + } + + public static string GetGroupName(long workshopId) + { + return $"group-faceembedding-{workshopId}"; + } + } +} + diff --git a/ServiceHost/Pages/Apk/AndroidApk.cs b/ServiceHost/Pages/Apk/AndroidApk.cs index 19fa8f23..ac679fb7 100644 --- a/ServiceHost/Pages/Apk/AndroidApk.cs +++ b/ServiceHost/Pages/Apk/AndroidApk.cs @@ -13,9 +13,11 @@ public class AndroidApk : Controller } [Route("Apk/Android")] - public async Task Index() + public async Task Index([FromQuery] string type = "WebView") { - var path = await _androidApkVersionApplication.GetLatestActiveVersionPath(); - return PhysicalFile(path,"application/vnd.android.package-archive","Gozareshgir.apk"); + ApkType apkType = type.ToLower() == "facedetection" ? ApkType.FaceDetection : ApkType.WebView; + var path = await _androidApkVersionApplication.GetLatestActiveVersionPath(apkType); + var fileName = apkType == ApkType.WebView ? "Gozareshgir.apk" : "Gozareshgir-FaceDetection.apk"; + return PhysicalFile(path,"application/vnd.android.package-archive", fileName); } } \ No newline at end of file diff --git a/ServiceHost/Pages/login/Index.cshtml b/ServiceHost/Pages/login/Index.cshtml index 444c0aa7..5da87825 100644 --- a/ServiceHost/Pages/login/Index.cshtml +++ b/ServiceHost/Pages/login/Index.cshtml @@ -185,10 +185,10 @@ - @if (Model.HasApkToDownload) + @if (Model.HasFaceDetectionApkToDownload) { } diff --git a/ServiceHost/Pages/login/Index.cshtml.cs b/ServiceHost/Pages/login/Index.cshtml.cs index 61281f79..c882e923 100644 --- a/ServiceHost/Pages/login/Index.cshtml.cs +++ b/ServiceHost/Pages/login/Index.cshtml.cs @@ -43,6 +43,7 @@ public class IndexModel : PageModel [BindProperty] public string Password { get; set; } [BindProperty] public string CaptchaResponse { get; set; } public bool HasApkToDownload { get; set; } + public bool HasFaceDetectionApkToDownload { get; set; } private static Timer aTimer; public Login login; public AccountViewModel Search; @@ -76,7 +77,8 @@ public class IndexModel : PageModel //_context.SaveChanges(); - HasApkToDownload = _androidApkVersionApplication.HasAndroidApkToDownload(); + HasApkToDownload = _androidApkVersionApplication.HasAndroidApkToDownload(ApkType.WebView); + HasFaceDetectionApkToDownload = _androidApkVersionApplication.HasAndroidApkToDownload(ApkType.FaceDetection); if (User.Identity is { IsAuthenticated: true }) { if (User.FindFirstValue("IsCamera") == "true") diff --git a/ServiceHost/Program.cs b/ServiceHost/Program.cs index 7e3431e0..d392bad1 100644 --- a/ServiceHost/Program.cs +++ b/ServiceHost/Program.cs @@ -19,17 +19,21 @@ using ServiceHost.MiddleWare; using WorkFlow.Infrastructure.Config; using _0_Framework.Application.UID; using _0_Framework.Exceptions.Handler; +using _0_Framework.Application.FaceEmbedding; using Microsoft.OpenApi.Models; using ServiceHost.Test; using System.Text.Json.Serialization; using System.Text.Json; using _0_Framework.InfraStructure.Mongo; +using Bogus; using CompanyManagment.EFCore.Services; using Microsoft.AspNetCore.CookiePolicy; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Diagnostics; using MongoDB.Driver; +using Parbad.Builder; +using Parbad.Gateway.Sepehr; using Swashbuckle.AspNetCore.SwaggerUI; @@ -73,6 +77,7 @@ builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); +builder.Services.AddTransient(); //services.AddSingleton(); //services.AddHostedService(); @@ -306,11 +311,29 @@ builder.Services.AddCors(options => builder.Services.AddExceptionHandler(); +var sepehrTerminalId = builder.Configuration.GetValue("SepehrGateWayTerminalId"); + +builder.Services.AddParbad().ConfigureGateways(gateways => +{ + gateways.AddSepehr().WithAccounts(accounts => + { + accounts.AddInMemory(account => + { + account.TerminalId = sepehrTerminalId; + account.Name="Sepehr Account"; + }); + }); +}).ConfigureHttpContext(httpContext=>httpContext.UseDefaultAspNetCore()) +.ConfigureStorage(storage => +{ + storage.UseMemoryCache(); +}); var app = builder.Build(); app.UseCors("AllowSpecificOrigins"); + #region Mahan //app.UseStatusCodePagesWithRedirects("/error/{0}"); @@ -324,6 +347,7 @@ if (builder.Environment.IsDevelopment()) await tester.Test(); } + if (app.Environment.IsDevelopment()) { app.UseSwagger(); @@ -384,6 +408,7 @@ app.MapHub("/trackingHub"); app.MapHub("/trackingSmsHub"); app.MapHub("/trackingHolidayHub"); app.MapHub("/trackingCheckoutHub"); +// app.MapHub("/trackingFaceEmbeddingHub"); app.MapRazorPages(); app.MapControllers(); diff --git a/ServiceHost/Properties/launchSettings.json b/ServiceHost/Properties/launchSettings.json index 89af9011..788962e4 100644 --- a/ServiceHost/Properties/launchSettings.json +++ b/ServiceHost/Properties/launchSettings.json @@ -19,7 +19,7 @@ "sqlDebugging": true, "dotnetRunMessages": "true", "nativeDebugging": true, - "applicationUrl": "https://localhost:5004;http://localhost:5003;https://192.168.0.117:8080;http://192.168.0.117:8081", + "applicationUrl": "https://localhost:5004;http://localhost:5003;", "jsWebView2Debugging": false, "hotReloadEnabled": true }, diff --git a/ServiceHost/ServiceHost.csproj b/ServiceHost/ServiceHost.csproj index fe979c5d..57d558c9 100644 --- a/ServiceHost/ServiceHost.csproj +++ b/ServiceHost/ServiceHost.csproj @@ -90,6 +90,8 @@ + + diff --git a/ServiceHost/appsettings.Development.json b/ServiceHost/appsettings.Development.json index 5efba4b6..a749e19d 100644 --- a/ServiceHost/appsettings.Development.json +++ b/ServiceHost/appsettings.Development.json @@ -42,7 +42,8 @@ "SmsSettings": { "IsTestMode": true, "TestNumbers": [ "09116967898", "09116067106", "09114221321" ] - } + }, + "SepehrGateWayTerminalId": 99213700 } diff --git a/ServiceHost/appsettings.json b/ServiceHost/appsettings.json index 59b613a6..0b602bb1 100644 --- a/ServiceHost/appsettings.json +++ b/ServiceHost/appsettings.json @@ -35,5 +35,7 @@ "SmsSettings": { "IsTestMode": false, "TestNumbers": [] - } + }, + "SepehrGateWayTerminalId": 99213700 + } diff --git a/plan-addApkTypeToAndroidApkVersion.prompt.md b/plan-addApkTypeToAndroidApkVersion.prompt.md new file mode 100644 index 00000000..2027a490 --- /dev/null +++ b/plan-addApkTypeToAndroidApkVersion.prompt.md @@ -0,0 +1,184 @@ +# Plan: Add ApkType to AndroidApkVersion for WebView and FaceDetection separation + +## Overview + +The user wants to integrate the `ApkType` enum (WebView/FaceDetection) into the `AndroidApkVersion` entity to distinguish between the two different APK types. Currently, all APKs are treated as WebView. The system needs to be updated to support both types with separate handling. + +## Steps + +### 1. Update AndroidApkVersion.cs domain entity +**File:** `Company.Domain\AndroidApkVersionAgg\AndroidApkVersion.cs` + +- Add `ApkType` property to the class +- Update constructor to accept `ApkType` parameter +- Modify `Title` property generation to include APK type information +- Update the constructor logic to handle both WebView and FaceDetection types + +### 2. Update AndroidApkVersionMapping.cs EF configuration +**File:** `CompanyManagment.EFCore\Mapping\AndroidApkVersionMapping.cs` + +- Add mapping configuration for `ApkType` property +- Use enum-to-string conversion (similar to existing `IsActive` mapping) +- Set appropriate column max length for the enum value + +### 3. Create database migration +**Files:** Generated migration files in `CompanyManagment.EFCore\Migrations\` + +- Generate new migration to add `ApkType` column to `AndroidApkVersions` table +- Set default value to `ApkType.WebView` for existing records +- Apply migration to update database schema + +### 4. Update IAndroidApkVersionRepository.cs interface +**File:** `Company.Domain\AndroidApkVersionAgg\IAndroidApkVersionRepository.cs` + +- Modify `GetActives()` to accept `ApkType` parameter for filtering +- Modify `GetLatestActiveVersionPath()` to accept `ApkType` parameter +- Add methods to handle type-specific queries + +### 5. Update AndroidApkVersionRepository.cs implementation +**File:** `CompanyManagment.EFCore\Repository\AndroidApkVersionRepository.cs` + +- Implement type-based filtering in `GetActives()` method +- Implement type-based filtering in `GetLatestActiveVersionPath()` method +- Add appropriate WHERE clauses to filter by `ApkType` + +### 6. Update IAndroidApkVersionApplication.cs interface +**File:** `CompanyManagment.App.Contracts\AndroidApkVersion\IAndroidApkVersionApplication.cs` + +- Add `ApkType` parameter to `CreateAndActive()` method +- Add `ApkType` parameter to `CreateAndDeActive()` method +- Add `ApkType` parameter to `GetLatestActiveVersionPath()` method +- Add `ApkType` parameter to `HasAndroidApkToDownload()` method + +### 7. Update AndroidApkVersionApplication.cs implementation +**File:** `CompanyManagment.Application\AndroidApkVersionApplication.cs` + +- Update `CreateAndActive()` method: + - Accept `ApkType` parameter + - Change storage path from hardcoded "GozreshgirWebView" to dynamic based on type + - Use "GozreshgirWebView" for `ApkType.WebView` + - Use "GozreshgirFaceDetection" for `ApkType.FaceDetection` + - Pass `ApkType` to repository methods when getting/deactivating existing APKs + - Pass `ApkType` to entity constructor + +- Update `CreateAndDeActive()` method: + - Accept `ApkType` parameter + - Update storage path logic similar to `CreateAndActive()` + - Pass `ApkType` to entity constructor + +- Update `GetLatestActiveVersionPath()` method: + - Accept `ApkType` parameter + - Pass type to repository method + +- Update `HasAndroidApkToDownload()` method: + - Accept `ApkType` parameter + - Filter by type when checking for active APKs + +### 8. Update AndroidApk.cs controller +**File:** `ServiceHost\Pages\Apk\AndroidApk.cs` + +- Modify the download endpoint to accept `ApkType` parameter +- Options: + - Add query string parameter: `/Apk/Android?type=WebView` or `/Apk/Android?type=FaceDetection` + - Create separate routes: `/Apk/Android/WebView` and `/Apk/Android/FaceDetection` +- Pass the type parameter to `GetLatestActiveVersionPath()` method +- Maintain backward compatibility by defaulting to `ApkType.WebView` if no type specified + +### 9. Update admin UI Index.cshtml.cs +**File:** `ServiceHost\Areas\AdminNew\Pages\Company\AndroidApk\Index.cshtml.cs` + +- Add property to store selected `ApkType` +- Add `[BindProperty]` for ApkType selection +- Modify `OnPostUpload()` to pass selected `ApkType` to application method +- Create corresponding UI changes in Index.cshtml (if exists) to allow type selection + +### 10. Update client-facing pages +**Files:** +- `ServiceHost\Pages\login\Index.cshtml.cs` +- `ServiceHost\Areas\Client\Pages\Index.cshtml.cs` + +- Update calls to `HasAndroidApkToDownload()` to specify which APK type to check +- Consider showing different download buttons/links for WebView vs FaceDetection apps +- Update download links to include APK type parameter + +## Migration Strategy + +### Handling Existing Data +- All existing `AndroidApkVersion` records should be marked as `ApkType.WebView` by default +- Use migration to set default value +- No manual data update required if migration includes default value + +### Database Schema Change +```sql +ALTER TABLE AndroidApkVersions +ADD ApkType NVARCHAR(20) NOT NULL DEFAULT 'WebView'; +``` + +## UI Design Considerations + +### Admin Upload Page +**Recommended approach:** Single form with radio buttons or dropdown + +- Add radio buttons or dropdown to select APK type before upload +- Labels: "WebView Application" and "Face Detection Application" +- Group uploads by type in the list/table view +- Show type column in the APK list + +### Client Download Pages +**Recommended approach:** Separate download buttons + +- Show "Download Gozareshgir WebView" button (existing functionality) +- Show "Download Gozareshgir FaceDetection" button (new functionality) +- Only show buttons if corresponding APK type is available +- Use different icons or colors to distinguish between types + +## Download URL Structure + +**Recommended approach:** Single endpoint with query parameter + +- Current: `/Apk/Android` (defaults to WebView for backward compatibility) +- New WebView: `/Apk/Android?type=WebView` +- New FaceDetection: `/Apk/Android?type=FaceDetection` + +**Alternative approach:** Separate endpoints +- `/Apk/Android/WebView` +- `/Apk/Android/FaceDetection` + +## Testing Checklist + +1. ✅ Upload WebView APK successfully +2. ✅ Upload FaceDetection APK successfully +3. ✅ Both types can coexist in database +4. ✅ Activating WebView APK doesn't affect FaceDetection APK +5. ✅ Activating FaceDetection APK doesn't affect WebView APK +6. ✅ Download correct APK based on type parameter +7. ✅ Admin UI shows type information correctly +8. ✅ Client pages show correct download availability +9. ✅ Backward compatibility maintained (existing links still work) +10. ✅ Migration applies successfully to existing database + +## File Summary + +**Files to modify:** +1. `Company.Domain\AndroidApkVersionAgg\AndroidApkVersion.cs` +2. `CompanyManagment.EFCore\Mapping\AndroidApkVersionMapping.cs` +3. `Company.Domain\AndroidApkVersionAgg\IAndroidApkVersionRepository.cs` +4. `CompanyManagment.EFCore\Repository\AndroidApkVersionRepository.cs` +5. `CompanyManagment.App.Contracts\AndroidApkVersion\IAndroidApkVersionApplication.cs` +6. `CompanyManagment.Application\AndroidApkVersionApplication.cs` +7. `ServiceHost\Pages\Apk\AndroidApk.cs` +8. `ServiceHost\Areas\AdminNew\Pages\Company\AndroidApk\Index.cshtml.cs` +9. `ServiceHost\Pages\login\Index.cshtml.cs` +10. `ServiceHost\Areas\Client\Pages\Index.cshtml.cs` + +**Files to create:** +1. New migration file (auto-generated) +2. Possibly `ServiceHost\Areas\AdminNew\Pages\Company\AndroidApk\Index.cshtml` (if doesn't exist) + +## Notes + +- The `ApkType` enum is already defined in `AndroidApkVersion.cs` +- Storage folders will be separate: `Storage/Apk/Android/GozreshgirWebView` and `Storage/Apk/Android/GozreshgirFaceDetection` +- Each APK type maintains its own active/inactive state independently +- Consider adding validation to ensure APK file matches selected type (optional enhancement) + diff --git a/plan-addIsForceToAndroidApkVersion.prompt.md b/plan-addIsForceToAndroidApkVersion.prompt.md new file mode 100644 index 00000000..152f8243 --- /dev/null +++ b/plan-addIsForceToAndroidApkVersion.prompt.md @@ -0,0 +1,151 @@ +# Plan: Add IsForce Field to Android APK Version Management + +## Overview +Add support for force update functionality to the Android APK version management system. This allows administrators to specify whether an APK update is mandatory (force update) or optional when uploading new versions. The system now supports two separate APK types: WebView and FaceDetection. + +## Context +- The system manages Android APK versions for two different application types +- Previously, all updates were treated as optional +- Need to add ability to mark certain updates as mandatory +- Force update flag should be stored in database and returned via API + +## Requirements +1. Add `IsForce` boolean field to the `AndroidApkVersion` entity +2. Allow administrators to specify force update status when uploading APK +3. Store force update status in database +4. Return force update status via API endpoint +5. Separate handling for WebView and FaceDetection APK types + +## Implementation Steps + +### 1. Domain Layer Updates +- ✅ Add `IsForce` property to `AndroidApkVersion` entity +- ✅ Update constructor to accept `isForce` parameter with default value of `false` +- ✅ File: `Company.Domain/AndroidApkVersionAgg/AndroidApkVersion.cs` + +### 2. Database Mapping +- ✅ Add `IsForce` property mapping in `AndroidApkVersionMapping` +- ✅ File: `CompanyManagment.EFCore/Mapping/AndroidApkVersionMapping.cs` + +### 3. Application Layer Updates +- ✅ Update `IAndroidApkVersionApplication` interface: + - Add `isForce` parameter to `CreateAndActive` method + - Add `isForce` parameter to `CreateAndDeActive` method + - Remove `isForceUpdate` parameter from `GetLatestActiveInfo` method +- ✅ File: `CompanyManagment.App.Contracts/AndroidApkVersion/IAndroidApkVersionApplication.cs` + +### 4. Application Implementation +- ✅ Update `AndroidApkVersionApplication`: + - Pass `isForce` to `AndroidApkVersion` constructor in `CreateAndActive` + - Pass `isForce` to `AndroidApkVersion` constructor in `CreateAndDeActive` + - Update `GetLatestActiveInfo` to return `IsForce` from database entity instead of parameter +- ✅ File: `CompanyManagment.Application/AndroidApkVersionApplication.cs` + +### 5. API Controller Updates +- ✅ Update `AndroidApkController`: + - Remove `force` parameter from `CheckUpdate` endpoint + - API now returns `IsForce` from database +- ✅ File: `ServiceHost/Areas/Admin/Controllers/AndroidApkController.cs` + +### 6. Admin UI Updates +- ✅ Add `IsForce` property to `IndexModel` +- ✅ Add checkbox for force update in upload form +- ✅ Pass `IsForce` value to `CreateAndActive` method +- ✅ Files: + - `ServiceHost/Areas/AdminNew/Pages/Company/AndroidApk/Index.cshtml.cs` + - `ServiceHost/Areas/AdminNew/Pages/Company/AndroidApk/Index.cshtml` + +### 7. Database Migration (To Be Done) +- ⚠️ **REQUIRED**: Create and run migration to add `IsForce` column to `AndroidApkVersions` table +- Command: `Add-Migration AddIsForceToAndroidApkVersion` +- Then: `Update-Database` + +## API Endpoints + +### Check for Updates +```http +GET /api/android-apk/check-update?type={ApkType}¤tVersionCode={int} +``` + +**Parameters:** +- `type`: Enum value - `WebView` or `FaceDetection` +- `currentVersionCode`: Current version code of installed app (integer) + +**Response:** +```json +{ + "latestVersionCode": 120, + "latestVersionName": "1.2.0", + "shouldUpdate": true, + "isForceUpdate": false, + "downloadUrl": "/Apk/Android?type=WebView", + "releaseNotes": "Bug fixes and improvements" +} +``` + +## APK Type Separation + +The system now fully supports two separate APK types: +1. **WebView**: Original web-view based application + - Stored in: `Storage/Apk/Android/GozreshgirWebView/` + - Title format: `Gozareshgir-WebView-{version}-{date}` + +2. **FaceDetection**: New face detection application + - Stored in: `Storage/Apk/Android/GozreshgirFaceDetection/` + - Title format: `Gozareshgir-FaceDetection-{version}-{date}` + +Each APK type maintains its own: +- Version history +- Active version +- Force update settings +- Download endpoint + +## Usage Examples + +### Admin Upload with Force Update +1. Navigate to admin APK upload page +2. Select APK file +3. Choose APK type (WebView or FaceDetection) +4. Check "آپدیت اجباری (Force Update)" if update should be mandatory +5. Click Upload + +### Client Check for Update (WebView) +```http +GET /api/android-apk/check-update?type=WebView¤tVersionCode=100 +``` + +### Client Check for Update (FaceDetection) +```http +GET /api/android-apk/check-update?type=FaceDetection¤tVersionCode=50 +``` + +## Testing Checklist +- [ ] Test uploading APK with force update enabled for WebView +- [ ] Test uploading APK with force update disabled for WebView +- [ ] Test uploading APK with force update enabled for FaceDetection +- [ ] Test uploading APK with force update disabled for FaceDetection +- [ ] Verify API returns correct `isForceUpdate` value for WebView +- [ ] Verify API returns correct `isForceUpdate` value for FaceDetection +- [ ] Verify only one active version exists per APK type +- [ ] Test migration creates `IsForce` column correctly +- [ ] Verify existing records default to `false` for `IsForce` + +## Notes +- Default value for `IsForce` is `false` (optional update) +- When uploading new active APK, all previous active versions of same type are deactivated +- Each APK type is managed independently +- Force update flag is stored per version, not globally +- API returns force update status from the latest active version in database + +## Files Modified +1. `Company.Domain/AndroidApkVersionAgg/AndroidApkVersion.cs` +2. `CompanyManagment.EFCore/Mapping/AndroidApkVersionMapping.cs` +3. `CompanyManagment.App.Contracts/AndroidApkVersion/IAndroidApkVersionApplication.cs` +4. `CompanyManagment.Application/AndroidApkVersionApplication.cs` +5. `ServiceHost/Areas/Admin/Controllers/AndroidApkController.cs` +6. `ServiceHost/Areas/AdminNew/Pages/Company/AndroidApk/Index.cshtml.cs` +7. `ServiceHost/Areas/AdminNew/Pages/Company/AndroidApk/Index.cshtml` + +## Migration Required +⚠️ **Important**: Don't forget to create and run the database migration to add the `IsForce` column. +