Compare commits

...

12 Commits

Author SHA1 Message Date
f112bab021 Add endpoint for workshop details: implement GetWorkshopsDetails method in institutionContractController 2025-11-18 15:07:38 +03:30
8b39eed7bb Add workshop total amount details: include price properties in InstitutionContract and implement GetContractWorkshopsDetails method 2025-11-18 14:53:08 +03:30
07851ff7c8 Add total amount to workshop list: include Price property in InstitutionContractListWorkshop 2025-11-17 17:11:49 +03:30
07b2596a6a Update APK handling and UI: add title length limit, modify download link, and include force update flag 2025-11-17 00:26:19 +03:30
7dce7f5bc8 Merge branch 'Feature/roll-call/camera-api' 2025-11-16 23:32:15 +03:30
7ac078c631 Refactor APK creation methods to reorder parameters and enhance title length limit in the database 2025-11-16 23:31:43 +03:30
265d5f8b22 Enhance Excel download by grouping institution contracts by ContractingPartyId 2025-11-16 22:29:18 +03:30
cf5c9f29cf Enhance APK upload functionality by adding version name and code parameters, and update UI for better user input validation 2025-11-16 22:09:19 +03:30
7448ddc79c Merge branch 'refs/heads/Feature/roll-call/apk-versioning' into Feature/roll-call/camera-api 2025-11-16 22:00:19 +03:30
3f1a6f3387 Merge branch 'refs/heads/master' into Feature/roll-call/camera-api
# Conflicts:
#	ServiceHost/Properties/launchSettings.json
2025-11-16 21:40:33 +03:30
95d4dfe568 feat: add SignalR integration for Face Embedding with client implementation and notification service 2025-11-16 20:30:15 +03:30
0b439d0268 Refactor and integrate face embedding API support
Refactored `EmployeeUploadPicture.cshtml.cs` to improve readability, maintainability, and modularity. Introduced `_httpClientFactory` for HTTP requests and added `SendEmbeddingsToApi` for Python API integration. Enhanced employee-related operations, including activation, image handling, and settings management.

Added `IFaceEmbeddingService` interface and implemented it in `FaceEmbeddingService` to manage face embeddings. Integrated with a Python API for generating, refining, deleting, and retrieving embeddings. Included robust error handling and detailed logging.

Improved code structure, reduced duplication, and added comments for better debugging and future development.

Add face embedding integration for employee management

Introduced `IFaceEmbeddingService` and its implementation to manage
face embeddings via a Python API. Integrated embedding generation
into `EmployeeApplication` and `EmployeeUploadPictureModel`,
enabling image uploads, embedding creation, and validation.

Refactored `EmployeeUploadPictureModel` for clarity, adding methods
to handle image processing, API interactions, and employee
activation/deactivation with embedding checks. Enhanced error
handling, logging, and user feedback.

Removed legacy code and updated dependencies to include
`IHttpClientFactory` and `IFaceEmbeddingService`. Added localized
error messages and improved maintainability by streamlining code.
2025-11-11 18:52:35 +03:30
29 changed files with 13244 additions and 748 deletions

View File

@@ -0,0 +1,25 @@
using System.Threading.Tasks;
namespace _0_Framework.Application.FaceEmbedding;
/// <summary>
/// سرویس اطلاع‌رسانی تغییرات Face Embedding
/// </summary>
public interface IFaceEmbeddingNotificationService
{
/// <summary>
/// اطلاع‌رسانی ایجاد یا به‌روزرسانی Embedding
/// </summary>
Task NotifyEmbeddingCreatedAsync(long workshopId, long employeeId, string employeeFullName);
/// <summary>
/// اطلاع‌رسانی حذف Embedding
/// </summary>
Task NotifyEmbeddingDeletedAsync(long workshopId, long employeeId);
/// <summary>
/// اطلاع‌رسانی بهبود Embedding
/// </summary>
Task NotifyEmbeddingRefinedAsync(long workshopId, long employeeId);
}

View File

@@ -0,0 +1,24 @@
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
namespace _0_Framework.Application.FaceEmbedding;
public interface IFaceEmbeddingService
{
Task<OperationResult> GenerateEmbeddingsAsync(long employeeId, long workshopId, string employeeFullName, string picture1Path, string picture2Path);
Task<OperationResult> GenerateEmbeddingsFromStreamAsync(long employeeId, long workshopId, string employeeFullName, Stream picture1Stream, Stream picture2Stream);
Task<OperationResult> RefineEmbeddingAsync(long employeeId, long workshopId, float[] embedding, float confidence, Dictionary<string, object> metadata = null);
Task<OperationResult> DeleteEmbeddingAsync(long employeeId, long workshopId);
Task<OperationResult<FaceEmbeddingResponse>> 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<string, object> Metadata { get; set; }
}

View File

@@ -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;
/// <summary>
/// پیاده‌سازی سرویس ارتباط با API پایتون برای مدیریت Embeddings چهره
/// </summary>
public class FaceEmbeddingService : IFaceEmbeddingService
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<FaceEmbeddingService> _logger;
private readonly IFaceEmbeddingNotificationService _notificationService;
private readonly string _apiBaseUrl;
public FaceEmbeddingService(IHttpClientFactory httpClientFactory, ILogger<FaceEmbeddingService> logger,
IFaceEmbeddingNotificationService notificationService = null)
{
_httpClientFactory = httpClientFactory;
_logger = logger;
_notificationService = notificationService;
_apiBaseUrl = "http://localhost:8000";
}
public async Task<OperationResult> 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<OperationResult> 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<OperationResult> RefineEmbeddingAsync(long employeeId, long workshopId, float[] embedding,
float confidence, Dictionary<string, object> 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<string, object>()
};
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<OperationResult> 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<OperationResult<FaceEmbeddingResponse>> 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<FaceEmbeddingResponse>(content,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
_logger.LogInformation("Embedding retrieved successfully for Employee {EmployeeId}", employeeId);
return new OperationResult<FaceEmbeddingResponse>
{
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<FaceEmbeddingResponse>
{
IsSuccedded = false,
Message = $"خطا در دریافت Embedding: {response.StatusCode}"
};
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while getting embedding for Employee {EmployeeId}", employeeId);
return new OperationResult<FaceEmbeddingResponse>
{
IsSuccedded = false,
Message = "خطا در دریافت Embedding"
};
}
}
}

View File

@@ -0,0 +1,30 @@
using System.Threading.Tasks;
using _0_Framework.Application.FaceEmbedding;
namespace _0_Framework.InfraStructure;
/// <summary>
/// پیاده‌سازی پیش‌فرض (بدون عملیات) برای IFaceEmbeddingNotificationService
/// این کلاس زمانی استفاده می‌شود که SignalR در دسترس نباشد
/// </summary>
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;
}
}

624
ANDROID_SIGNALR_GUIDE.md Normal file
View File

@@ -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
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
```
## 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<Button>(R.id.btnLogin)
btnLogin.setOnClickListener { performLogin() }
}
private fun performLogin() {
// فراخوانی API لاگین
// فرض کنید response شامل workshopId است
// مثال ساده (باید از Retrofit یا کتابخانه مشابه استفاده کنید):
// val response = apiService.login(username, password)
// val workshopId = response.workshopId
val workshopId = 123L // این را از response دریافت کنید
// ذخیره workshopId
val prefs = getSharedPreferences("AppPrefs", Context.MODE_PRIVATE)
prefs.edit().putLong("workshopId", workshopId).apply()
// رفتن به صفحه اصلی
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
}
class MainActivity : AppCompatActivity() {
private lateinit var signalRClient: FaceEmbeddingSignalRClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// دریافت workshopId از SharedPreferences
val prefs = getSharedPreferences("AppPrefs", Context.MODE_PRIVATE)
val workshopId = prefs.getLong("workshopId", 0L)
if (workshopId == 0L) {
// اگر workshopId وجود نداره، برگرد به صفحه لاگین
val intent = Intent(this, LoginActivity::class.java)
startActivity(intent)
finish()
return
}
// ایجاد و اتصال SignalR
signalRClient = object : FaceEmbeddingSignalRClient(workshopId) {
override fun onEmbeddingCreated(employeeId: Long, employeeFullName: String, timestamp: String) {
runOnUiThread {
// بروزرسانی UI
Toast.makeText(this@MainActivity,
"Embedding ایجاد شد برای: $employeeFullName",
Toast.LENGTH_SHORT).show()
// دریافت داده‌های جدید از API
refreshEmployeeList()
}
}
override fun onEmbeddingDeleted(employeeId: Long, timestamp: String) {
runOnUiThread {
// بروزرسانی UI
refreshEmployeeList()
}
}
override fun onEmbeddingRefined(employeeId: Long, timestamp: String) {
runOnUiThread {
// بروزرسانی UI
refreshEmployeeList()
}
}
}
signalRClient.connect()
}
override fun onDestroy() {
super.onDestroy()
signalRClient.disconnect()
}
private fun refreshEmployeeList() {
// دریافت لیست جدید کارمندان از API
}
}
```
### مثال ساده بدون Login:
اگر workshopId را از قبل می‌دانید:
#### Java:
```java
public class MainActivity extends AppCompatActivity {
private FaceEmbeddingSignalRClient signalRClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
long workshopId = 123; // شناسه کارگاه خود را وارد کنید
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();
});
}
};
signalRClient.connect();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (signalRClient != null) {
signalRClient.disconnect();
}
}
private void refreshEmployeeList() {
// دریافت لیست جدید کارمندان از API
}
}
```
#### Kotlin:
```kotlin
class MainActivity : AppCompatActivity() {
private lateinit var signalRClient: FaceEmbeddingSignalRClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val workshopId = 123L // شناسه کارگاه خود را وارد کنید
signalRClient = object : FaceEmbeddingSignalRClient(workshopId) {
override fun onEmbeddingCreated(employeeId: Long, employeeFullName: String, timestamp: String) {
runOnUiThread {
// بروزرسانی UI
Toast.makeText(this@MainActivity,
"Embedding ایجاد شد برای: $employeeFullName",
Toast.LENGTH_SHORT).show()
// دریافت داده‌های جدید از API
refreshEmployeeList()
}
}
override fun onEmbeddingDeleted(employeeId: Long, timestamp: String) {
runOnUiThread {
// بروزرسانی UI
refreshEmployeeList()
}
}
}
signalRClient.connect()
}
override fun onDestroy() {
super.onDestroy()
signalRClient.disconnect()
}
private fun refreshEmployeeList() {
// دریافت لیست جدید کارمندان از API
}
}
```
## 5. نکات مهم
### آدرس سرور
- اگر روی شبیه‌ساز اندروید تست می‌کنید و سرور روی localhost اجرا می‌شود، از آدرس `http://10.0.2.2:PORT` استفاده کنید
- اگر روی دستگاه فیزیکی تست می‌کنید، از آدرس IP شبکه محلی سرور استفاده کنید (مثل `http://192.168.1.100:PORT`)
- PORT پیش‌فرض معمولاً 5000 یا 5001 است (بسته به کانفیگ پروژه شما)
### دریافت WorkshopId از Login
بعد از login موفق، workshopId را از سرور دریافت کنید و در SharedPreferences یا یک Singleton ذخیره کنید:
```java
// بعد از login موفق
SharedPreferences prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE);
prefs.edit().putLong("workshopId", workshopId).apply();
// استفاده در Activity
long workshopId = prefs.getLong("workshopId", 0);
```
یا در Kotlin:
```kotlin
// بعد از login موفق
val prefs = getSharedPreferences("AppPrefs", Context.MODE_PRIVATE)
prefs.edit().putLong("workshopId", workshopId).apply()
// استفاده در Activity
val workshopId = prefs.getLong("workshopId", 0L)
```
### مدیریت اتصال
برای reconnection خودکار:
```java
hubConnection.onClosed(exception -> {
Log.e(TAG, "Connection closed. Attempting to reconnect...");
new Handler().postDelayed(() -> connect(), 5000); // تلاش مجدد بعد از 5 ثانیه
});
```
### Thread Safety
همیشه UI updates را در main thread انجام دهید:
```java
runOnUiThread(() -> {
// UI updates here
});
```
## 6. تست اتصال
برای تست می‌توانید:
1. اپلیکیشن را اجرا کنید
2. از طریق Postman یا Swagger یک Embedding ایجاد کنید
3. باید در Logcat پیام "Embedding Created" را ببینید
## 7. خطایابی (Debugging)
برای دیدن جزئیات بیشتر:
```java
hubConnection = HubConnectionBuilder
.create(serverUrl)
.withHttpConnectionOptions(options -> {
options.setLogging(LogLevel.TRACE);
})
.build();
```
---
## خلاصه Endpoints
| نوع رویداد | متد SignalR | پارامترهای دریافتی |
|-----------|-------------|---------------------|
| ایجاد Embedding | `EmbeddingCreated` | workshopId, employeeId, employeeFullName, timestamp |
| حذف Embedding | `EmbeddingDeleted` | workshopId, employeeId, timestamp |
| بهبود Embedding | `EmbeddingRefined` | workshopId, employeeId, timestamp |
| متد ارسالی | پارامتر | توضیحات |
|-----------|---------|---------|
| `JoinWorkshopGroup` | workshopId | عضویت در گروه کارگاه |
| `LeaveWorkshopGroup` | workshopId | خروج از گروه کارگاه |

View File

@@ -77,4 +77,5 @@ public interface IInstitutionContractRepository : IRepository<long, InstitutionC
Task<List<InstitutionContractSelectListViewModel>> GetInstitutionContractSelectList(string search, string selected);
Task<List<InstitutionContractPrintViewModel>> PrintAllAsync(List<long> ids);
Task<GetInstitutionContractWorkshopsDetails> GetContractWorkshopsDetails(long id);
}

View File

@@ -8,8 +8,8 @@ public record AndroidApkVersionInfo(int LatestVersionCode, string LatestVersionN
public interface IAndroidApkVersionApplication
{
Task<OperationResult> CreateAndActive(IFormFile file, ApkType apkType, bool isForce = false);
Task<OperationResult> CreateAndDeActive(IFormFile file, ApkType apkType, bool isForce = false);
Task<OperationResult> CreateAndActive(IFormFile file, ApkType apkType, string versionName, string versionCode, bool isForce = false);
Task<OperationResult> CreateAndDeActive(IFormFile file, ApkType apkType, string versionName, string versionCode, bool isForce = false);
Task<string> GetLatestActiveVersionPath(ApkType apkType);
Task<AndroidApkVersionInfo> GetLatestActiveInfo(ApkType apkType, int currentVersionCode);
OperationResult Remove(long id);

View File

@@ -1,4 +1,7 @@
using System.Collections.Generic;
using System.Security.AccessControl;
using CompanyManagment.App.Contracts.Workshop;
using Microsoft.AspNetCore.Server.HttpSys;
namespace CompanyManagment.App.Contracts.InstitutionContract;
@@ -97,6 +100,7 @@ public class InstitutionContractListWorkshop
{
public string WorkshopName { get; set; }
public int EmployeeCount { get; set; }
public string Price { get; set; }
public WorkshopServicesViewModel WorkshopServices { get; set; }
}
@@ -104,22 +108,31 @@ public class WorkshopServicesViewModel
{
public bool Insurance { get; set; }
public string InsuranceLabel => "ارسال لیست بیمه";
public string InsurancePrice { get; set; }
public bool InsuranceInPerson { get; set; }
public string InsuranceInPersonLabel => "خدمات مستقیم";
public string InsuranceInPersonPrice { get; set; }
public bool Contract { get; set; }
public string ContractLabel => "قرارداد و تصفیه حساب";
public string ContractPrice { get; set; }
public bool ContractInPerson { get; set; }
public string ContractInPersonLabel => "خدمات مستقیم";
public string ContractInPersonPrice { get; set; }
public bool RollCall { get; set; }
public string RollCallLabel => "ساعت حضور و غیاب";
public string RollCallPrice { get; set; }
public bool RollCallInPerson { get; set; }
public string RollCallInPersonLabel => "خدمات مستقیم";
public string RollCallInPersonPrice { get; set; }
public bool CustomizeCheckout { get; set; }
public string CustomizeCheckoutLabel => "فیش غیر رسمی";
public string CustomizeCheckoutPrice { get; set; }
}

View File

@@ -251,8 +251,17 @@ public interface IInstitutionContractApplication
/// <param name="id"></param>
/// <returns></returns>
Task<InstitutionContractPrintViewModel> PrintOneAsync(long id);
Task<GetInstitutionContractWorkshopsDetails> GetContractWorkshopsDetails(long id);
}
public class GetInstitutionContractWorkshopsDetails
{
public List<InstitutionContractListWorkshop> Workshops { get; set; }
}
public class InstitutionContractPrintViewModel
{
public InstitutionContratVerificationParty FirstParty { get; set; }

View File

@@ -22,7 +22,7 @@ public class AndroidApkVersionApplication : IAndroidApkVersionApplication
_taskRepository = taskRepository;
}
public async Task<OperationResult> CreateAndActive(IFormFile file, ApkType apkType, bool isForce = false)
public async Task<OperationResult> CreateAndActive(IFormFile file, ApkType apkType, string versionName, string versionCode, bool isForce = false)
{
OperationResult op = new OperationResult();
@@ -34,6 +34,12 @@ public class AndroidApkVersionApplication : IAndroidApkVersionApplication
if (Path.GetExtension(file.FileName).ToLower() != ".apk")
return op.Failed("لطفا فایلی با پسوند .apk وارد کنید");
if (string.IsNullOrWhiteSpace(versionName))
return op.Failed("لطفا نام ورژن را وارد کنید");
if (string.IsNullOrWhiteSpace(versionCode))
return op.Failed("لطفا کد ورژن را وارد کنید");
#endregion
var activeApks = _androidApkVersionRepository.GetActives(apkType);
@@ -47,30 +53,8 @@ public class AndroidApkVersionApplication : IAndroidApkVersionApplication
Directory.CreateDirectory(path);
// Read APK metadata with error handling
ApkInfo apk;
try
{
apk = ApkHelper.ReadApkInfo(file.OpenReadStream());
// Validate APK info
if (string.IsNullOrEmpty(apk.VersionName) || string.IsNullOrEmpty(apk.VersionCode))
{
return op.Failed("فایل APK معتبر نیست - اطلاعات نسخه یافت نشد");
}
// Validate version code is numeric
if (!int.TryParse(apk.VersionCode, out _))
{
return op.Failed("فایل APK معتبر نیست - کد نسخه باید عددی باشد");
}
}
catch (Exception ex)
{
return op.Failed("خطا در خواندن فایل APK: " + ex.Message);
}
string uniqueFileName = $"{Path.GetFileNameWithoutExtension(file.FileName)}.v{versionName}{Path.GetExtension(file.FileName)}";
string uniqueFileName = $"{Path.GetFileNameWithoutExtension(file.FileName)}.v{apk.VersionName}{Path.GetExtension(file.FileName)}";
string filepath = Path.Combine(path, uniqueFileName);
await using (var stream = new FileStream(filepath, FileMode.Create))
@@ -78,13 +62,13 @@ public class AndroidApkVersionApplication : IAndroidApkVersionApplication
await file.CopyToAsync(stream);
}
var entity = new AndroidApkVersion(apk.VersionName, apk.VersionCode, IsActive.True, filepath, apkType, isForce);
var entity = new AndroidApkVersion(versionName, versionCode, IsActive.True, filepath,apkType,isForce);
_androidApkVersionRepository.Create(entity);
_androidApkVersionRepository.SaveChanges();
return op.Succcedded();
}
public async Task<OperationResult> CreateAndDeActive(IFormFile file, ApkType apkType, bool isForce = false)
public async Task<OperationResult> CreateAndDeActive(IFormFile file, ApkType apkType, string versionName, string versionCode, bool isForce = false)
{
OperationResult op = new OperationResult();
@@ -96,6 +80,12 @@ public class AndroidApkVersionApplication : IAndroidApkVersionApplication
if (Path.GetExtension(file.FileName).ToLower() != ".apk")
return op.Failed("لطفا فایلی با پسوند .apk وارد کنید");
if (string.IsNullOrWhiteSpace(versionName))
return op.Failed("لطفا نام ورژن را وارد کنید");
if (string.IsNullOrWhiteSpace(versionCode))
return op.Failed("لطفا کد ورژن را وارد کنید");
#endregion
@@ -105,31 +95,7 @@ public class AndroidApkVersionApplication : IAndroidApkVersionApplication
Directory.CreateDirectory(path);
// Read APK metadata with error handling
ApkInfo apk;
try
{
apk = ApkHelper.ReadApkInfo(file.OpenReadStream());
// Validate APK info
if (string.IsNullOrEmpty(apk.VersionName) || string.IsNullOrEmpty(apk.VersionCode))
{
return op.Failed("فایل APK معتبر نیست - اطلاعات نسخه یافت نشد");
}
// Validate version code is numeric
if (!int.TryParse(apk.VersionCode, out _))
{
return op.Failed("فایل APK معتبر نیست - کد نسخه باید عددی باشد");
}
}
catch (Exception ex)
{
return op.Failed("خطا در خواندن فایل APK: " + ex.Message);
}
string uniqueFileName = $"{Path.GetFileNameWithoutExtension(file.FileName)}.v{apk.VersionName}{Path.GetExtension(file.FileName)}";
string uniqueFileName = $"{Path.GetFileNameWithoutExtension(file.FileName)}.v{versionName}{Path.GetExtension(file.FileName)}";
string filepath = Path.Combine(path, uniqueFileName);
@@ -138,7 +104,7 @@ public class AndroidApkVersionApplication : IAndroidApkVersionApplication
await file.CopyToAsync(stream);
}
var entity = new AndroidApkVersion(apk.VersionName, apk.VersionCode, IsActive.False, filepath, apkType, isForce);
var entity = new AndroidApkVersion(versionName, versionCode, IsActive.False, filepath,apkType);
_androidApkVersionRepository.Create(entity);
_androidApkVersionRepository.SaveChanges();
return op.Succcedded();

View File

@@ -31,6 +31,7 @@ using Company.Domain.LeftWorkAgg;
using CompanyManagment.App.Contracts.Employee.DTO;
using Company.Domain.EmployeeAuthorizeTempAgg;
using Company.Domain.LeftWorkInsuranceAgg;
using _0_Framework.Application.FaceEmbedding;
namespace CompanyManagment.Application;
@@ -61,29 +62,31 @@ public class EmployeeAplication : RepositoryBase<long, Employee>, IEmployeeAppli
private readonly ICustomizeWorkshopGroupSettingsRepository _customizeWorkshopGroupSettingsRepository;
private readonly IEmployeeAuthorizeTempRepository _employeeAuthorizeTempRepository;
private readonly ILeftWorkInsuranceRepository _leftWorkInsuranceRepository;
private readonly IFaceEmbeddingService _faceEmbeddingService;
public EmployeeAplication(IEmployeeRepository employeeRepository, CompanyContext context, IWorkshopRepository workShopRepository, IWebHostEnvironment webHostEnvironment, IRollCallEmployeeStatusApplication rollCallEmployeeStatusApplication, IRollCallEmployeeRepository rollCallEmployeeRepository, ICustomizeWorkshopSettingsApplication customizeWorkshopSettingsApplication, IEmployeeDocumentsApplication employeeDocumentsApplication, IEmployeeDocumentsRepository employeeDocumentsRepository, IEmployeeBankInformationApplication employeeBankInformationApplication, ILeftWorkTempRepository leftWorkTempRepository, IUidService uidService, ICustomizeWorkshopEmployeeSettingsRepository customizeWorkshopEmployeeSettingsRepository, IPersonnelCodeRepository personnelCodeRepository, IEmployeeClientTempRepository employeeClientTempRepository, ICustomizeWorkshopGroupSettingsRepository customizeWorkshopGroupSettingsRepository, ILeftWorkRepository leftWorkRepository, IEmployeeAuthorizeTempRepository employeeAuthorizeTempRepository, ILeftWorkInsuranceRepository leftWorkInsuranceRepository) : base(context)
{
_context = context;
_WorkShopRepository = workShopRepository;
_webHostEnvironment = webHostEnvironment;
_rollCallEmployeeStatusApplication = rollCallEmployeeStatusApplication;
_rollCallEmployeeRepository = rollCallEmployeeRepository;
_customizeWorkshopSettingsApplication = customizeWorkshopSettingsApplication;
_employeeDocumentsApplication = employeeDocumentsApplication;
_employeeBankInformationApplication = employeeBankInformationApplication;
_leftWorkTempRepository = leftWorkTempRepository;
_uidService = uidService;
_customizeWorkshopEmployeeSettingsRepository = customizeWorkshopEmployeeSettingsRepository;
_personnelCodeRepository = personnelCodeRepository;
_employeeClientTempRepository = employeeClientTempRepository;
_leftWorkRepository = leftWorkRepository;
_employeeAuthorizeTempRepository = employeeAuthorizeTempRepository;
_leftWorkInsuranceRepository = leftWorkInsuranceRepository;
_EmployeeRepository = employeeRepository;
}
public EmployeeAplication(IEmployeeRepository employeeRepository, CompanyContext context, IWorkshopRepository workShopRepository, IWebHostEnvironment webHostEnvironment, IRollCallEmployeeStatusApplication rollCallEmployeeStatusApplication, IRollCallEmployeeRepository rollCallEmployeeRepository, ICustomizeWorkshopSettingsApplication customizeWorkshopSettingsApplication, IEmployeeDocumentsApplication employeeDocumentsApplication, IEmployeeDocumentsRepository employeeDocumentsRepository, IEmployeeBankInformationApplication employeeBankInformationApplication, ILeftWorkTempRepository leftWorkTempRepository, IUidService uidService, ICustomizeWorkshopEmployeeSettingsRepository customizeWorkshopEmployeeSettingsRepository, IPersonnelCodeRepository personnelCodeRepository, IEmployeeClientTempRepository employeeClientTempRepository, ICustomizeWorkshopGroupSettingsRepository customizeWorkshopGroupSettingsRepository, ILeftWorkRepository leftWorkRepository, IEmployeeAuthorizeTempRepository employeeAuthorizeTempRepository, ILeftWorkInsuranceRepository leftWorkInsuranceRepository, IFaceEmbeddingService faceEmbeddingService) : base(context)
{
_context = context;
_WorkShopRepository = workShopRepository;
_webHostEnvironment = webHostEnvironment;
_rollCallEmployeeStatusApplication = rollCallEmployeeStatusApplication;
_rollCallEmployeeRepository = rollCallEmployeeRepository;
_customizeWorkshopSettingsApplication = customizeWorkshopSettingsApplication;
_employeeDocumentsApplication = employeeDocumentsApplication;
_employeeBankInformationApplication = employeeBankInformationApplication;
_leftWorkTempRepository = leftWorkTempRepository;
_uidService = uidService;
_customizeWorkshopEmployeeSettingsRepository = customizeWorkshopEmployeeSettingsRepository;
_personnelCodeRepository = personnelCodeRepository;
_employeeClientTempRepository = employeeClientTempRepository;
_leftWorkRepository = leftWorkRepository;
_employeeAuthorizeTempRepository = employeeAuthorizeTempRepository;
_leftWorkInsuranceRepository = leftWorkInsuranceRepository;
_EmployeeRepository = employeeRepository;
_faceEmbeddingService = faceEmbeddingService;
}
public OperationResult Create(CreateEmployee command)
public OperationResult Create(CreateEmployee command)
{
var opration = new OperationResult();
if (_EmployeeRepository.Exists(x =>
@@ -1122,6 +1125,12 @@ public class EmployeeAplication : RepositoryBase<long, Employee>, IEmployeeAppli
rollCallEmployee.HasImage();
_rollCallEmployeeRepository.Create(rollCallEmployee);
_rollCallEmployeeRepository.SaveChanges();
string employeeFullName = employee.FName + " " + employee.LName;
var res = _faceEmbeddingService.GenerateEmbeddingsAsync(employee.id,command.WorkshopId,employeeFullName, filePath1,filePath2).GetAwaiter().GetResult();
if (!res.IsSuccedded)
{
return op.Failed(res.Message);
}
}

View File

@@ -1387,6 +1387,11 @@ public class InstitutionContractApplication : IInstitutionContractApplication
return (await _institutionContractRepository.PrintAllAsync([id])).FirstOrDefault();
}
public Task<GetInstitutionContractWorkshopsDetails> GetContractWorkshopsDetails(long id)
{
return _institutionContractRepository.GetContractWorkshopsDetails(id);
}
private async Task<OperationResult<PersonalContractingParty>> CreateLegalContractingPartyEntity(
CreateInstitutionContractLegalPartyRequest request, long representativeId, string address, string city,

View File

@@ -22,7 +22,7 @@ public class AndroidApkVersionMapping:IEntityTypeConfiguration<AndroidApkVersion
v => v.ToString(),
v => (ApkType)Enum.Parse(typeof(ApkType), v)).HasMaxLength(20);
builder.Property(x => x.Title).HasMaxLength(50);
builder.Property(x => x.Title).HasMaxLength(200);
builder.Property(x => x.VersionCode).HasMaxLength(20);
builder.Property(x => x.VersionName).HasMaxLength(35);
builder.Property(x => x.Path).HasMaxLength(255);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CompanyManagment.EFCore.Migrations
{
/// <inheritdoc />
public partial class addtitlemaxlengthinandroidapk : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Title",
table: "AndroidApkVersions",
type: "nvarchar(200)",
maxLength: 200,
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(50)",
oldMaxLength: 50,
oldNullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Title",
table: "AndroidApkVersions",
type: "nvarchar(50)",
maxLength: 50,
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(200)",
oldMaxLength: 200,
oldNullable: true);
}
}
}

View File

@@ -82,8 +82,8 @@ namespace CompanyManagment.EFCore.Migrations
.HasColumnType("nvarchar(255)");
b.Property<string>("Title")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<string>("VersionCode")
.HasMaxLength(20)

View File

@@ -1291,7 +1291,9 @@ public class InstitutionContractRepository : RepositoryBase<long, InstitutionCon
return new InstitutionContractListWorkshop()
{
EmployeeCount = w.PersonnelCount,
Price = w.Price.ToMoney(),
WorkshopName = workshopSelected?.WorkshopName ?? w.WorkshopName,
WorkshopServices = new WorkshopServicesViewModel()
{
Contract = w.Services.Contract,
@@ -1300,7 +1302,8 @@ public class InstitutionContractRepository : RepositoryBase<long, InstitutionCon
Insurance = w.Services.Insurance,
InsuranceInPerson = w.Services.InsuranceInPerson,
RollCall = w.Services.RollCall,
RollCallInPerson = w.Services.RollCallInPerson
RollCallInPerson = w.Services.RollCallInPerson,
}
};
}).ToList() ?? [];
@@ -2768,6 +2771,61 @@ public class InstitutionContractRepository : RepositoryBase<long, InstitutionCon
return res;
}
public async Task<GetInstitutionContractWorkshopsDetails> GetContractWorkshopsDetails(long id)
{
var institutionContract = await _context.InstitutionContractSet
.Include(x => x.WorkshopGroup)
.ThenInclude(x => x.InitialWorkshops)
.FirstOrDefaultAsync(x => x.id == id);
if (institutionContract == null)
throw new NotFoundException("قرارداد مؤسسه یافت نشد");
var workshops = institutionContract.WorkshopGroup.InitialWorkshops
.Select(x =>
{
var plan = _planPercentageRepository.GetInstitutionPlanForWorkshop(new WorkshopTempViewModel()
{
CountPerson = x.PersonnelCount,
ContractAndCheckout = x.Services.Contract,
ContractAndCheckoutInPerson = x.Services.ContractInPerson,
Insurance = x.Services.Insurance,
InsuranceInPerson = x.Services.InsuranceInPerson,
RollCall = x.Services.RollCall,
RollCallInPerson = x.Services.RollCallInPerson,
CustomizeCheckout = x.Services.CustomizeCheckout,
});
return new InstitutionContractListWorkshop()
{
EmployeeCount = x.PersonnelCount,
Price = x.Price.ToMoney(),
WorkshopName = x.WorkshopName,
WorkshopServices = new WorkshopServicesViewModel()
{
Contract = x.Services.Contract,
ContractPrice =plan.ContractAndCheckout ,
ContractInPerson = x.Services.ContractInPerson,
ContractInPersonPrice = plan.ContractAndCheckoutInPerson,
CustomizeCheckout = x.Services.CustomizeCheckout,
CustomizeCheckoutPrice = plan.CustomizeCheckout,
Insurance = x.Services.Insurance,
InsurancePrice =plan.Insurance ,
InsuranceInPerson = x.Services.InsuranceInPerson,
InsuranceInPersonPrice = plan.InsuranceInPerson,
RollCall = x.Services.RollCall,
RollCallPrice =plan.RollCall ,
RollCallInPerson = x.Services.RollCallInPerson,
RollCallInPersonPrice = "0",
}
};
}).ToList();
return new GetInstitutionContractWorkshopsDetails()
{
Workshops = workshops
};
}
private InstitutionContractExtensionPaymentResponse CalculateInPersonPayment(
InstitutionContractExtensionPlanDetail selectedPlan, double baseAmount, double tenPercent,

View File

@@ -228,6 +228,9 @@ using CompanyManagement.Infrastructure.Mongo.InstitutionContractInsertTempRepo;
using CompanyManagment.App.Contracts.EmployeeFaceEmbedding;
using CompanyManagment.App.Contracts.Law;
using CompanyManagment.EFCore.Repository;
using _0_Framework.Application.FaceEmbedding;
using _0_Framework.Infrastructure;
using _0_Framework.InfraStructure;
namespace PersonalContractingParty.Config;
@@ -611,6 +614,10 @@ public class PersonalBootstrapper
services.AddTransient<IInsuranceApplication, InsuranceApplication>();
services.AddTransient<IInsuranceRepository, InsuranceRepository>();
// Face Embedding Services
services.AddTransient<IFaceEmbeddingService, FaceEmbeddingService>();
services.AddTransient<IFaceEmbeddingNotificationService, NullFaceEmbeddingNotificationService>();
services.AddDbContext<CompanyContext>(x => x.UseSqlServer(connectionString));
}
}

View File

@@ -88,6 +88,12 @@ public class institutionContractController : AdminBaseController
return await _institutionContractApplication.GetListStats(searchModel);
}
[HttpGet("workshops-details/{id}")]
public async Task<ActionResult<GetInstitutionContractWorkshopsDetails>> GetWorkshopsDetials(long id)
{
var result = await _institutionContractApplication.GetContractWorkshopsDetails(id);
return result;
}
/// <summary>
/// ویرایش
@@ -218,6 +224,7 @@ public class institutionContractController : AdminBaseController
[HttpDelete("{id}")]
public async Task<ActionResult<OperationResult>> Remove(long id)
{
return BadRequest("امکان حذف قرارداد وجود ندارد");
_institutionContractApplication.RemoveContract(id);
return new OperationResult().Succcedded();

View File

@@ -923,6 +923,8 @@ public class IndexModel : PageModel
public IActionResult OnGetDownloadExcel()
{
var institutionContractViewModels = _institutionContract.NewSearch(new() {IsActiveString = "both", TypeOfContract = "both" });
institutionContractViewModels= institutionContractViewModels.GroupBy(x=>x.ContractingPartyId).Select(g=>g.MaxBy(x=>x.ContractStartGr)).ToList();
var bytes = InstitutionContractExcelGenerator.GenerateExcel(institutionContractViewModels);
return File(bytes,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",

View File

@@ -4,24 +4,46 @@
ViewData["Title"] = "File Upload";
}
<h1>Upload File</h1>
<form asp-page-handler="Upload" id="1" method="post" enctype="multipart/form-data">
<div>
<label asp-for="File">Choose a file:</label>
<input asp-for="File" type="file" required>
<h1>Upload APK File</h1>
<div class="card">
<div class="card-body">
<form asp-page-handler="Upload" id="1" method="post" enctype="multipart/form-data">
<div class="form-group">
<label asp-for="File" class="form-label">Choose APK file:</label>
<input asp-for="File" type="file" class="form-control" required accept=".apk">
<small class="form-text text-muted">Please select a valid APK file.</small>
<span asp-validation-for="File" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="VersionName" class="form-label">Version Name:</label>
<input asp-for="VersionName" type="text" class="form-control" placeholder="e.g. 1.0.0" required>
<small class="form-text text-muted">Enter the version name (e.g. 1.0.0, 2.1.3).</small>
<span asp-validation-for="VersionName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="VersionCode" class="form-label">Version Code:</label>
<input asp-for="VersionCode" type="text" class="form-control" placeholder="e.g. 100" required>
<small class="form-text text-muted">Enter the version code (numeric value).</small>
<span asp-validation-for="VersionCode" class="text-danger"></span>
</div>
<div style="margin-top:12px;">
<label asp-for="SelectedApkType">نوع اپلیکیشن:</label>
<select asp-for="SelectedApkType" asp-items="Html.GetEnumSelectList<CompanyManagment.App.Contracts.AndroidApkVersion.ApkType>()"></select>
</div>
<div style="margin-top:12px;">
<label asp-for="IsForce">
<input asp-for="IsForce" type="checkbox">
آپدیت اجباری (Force Update)
</label>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Upload APK</button>
</div>
</form>
</div>
<div style="margin-top:12px;">
<label asp-for="SelectedApkType">نوع اپلیکیشن:</label>
<select asp-for="SelectedApkType" asp-items="Html.GetEnumSelectList<CompanyManagment.App.Contracts.AndroidApkVersion.ApkType>()"></select>
</div>
<div style="margin-top:12px;">
<label asp-for="IsForce">
<input asp-for="IsForce" type="checkbox">
آپدیت اجباری (Force Update)
</label>
</div>
<button type="submit" style="margin-top:12px;">Upload</button>
</form>
</div>
<form asp-page-handler="ShiftDate" id="8" method="post">

View File

@@ -27,6 +27,7 @@ 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
@@ -46,6 +47,16 @@ namespace ServiceHost.Areas.AdminNew.Pages.Company.AndroidApk
[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; }
@@ -70,7 +81,7 @@ namespace ServiceHost.Areas.AdminNew.Pages.Company.AndroidApk
public async Task<IActionResult> OnPostUpload()
{
var result = await _application.CreateAndActive(File, SelectedApkType, IsForce);
var result = await _application.CreateAndActive(File,SelectedApkType, VersionName, VersionCode, IsForce);
ViewData["message"] = result.Message;
return Page();
}

View File

@@ -77,7 +77,10 @@ public class CameraController : CameraBaseController
public IActionResult CameraLogin([FromBody] CameraLoginRequest request)
{
_accountApplication.CameraLogin(request);
return Ok();
return Ok(new
{
WorkshopId = _workshopId,
});
}
@@ -225,8 +228,8 @@ public class CameraController : CameraBaseController
var info = await _androidApkVersionApplication.GetLatestActiveInfo(type, currentVersionCode);
return Ok(new
{
latestVersionCode = 6000,
latestVersionName = "2.0.0",
latestVersionCode = info.LatestVersionCode,
latestVersionName = info.LatestVersionName,
shouldUpdate = info.ShouldUpdate,
isForceUpdate = info.IsForceUpdate,
downloadUrl = info.DownloadUrl,

View File

@@ -0,0 +1,58 @@
using _0_Framework.Application.FaceEmbedding;
using Microsoft.AspNetCore.SignalR;
using ServiceHost.Hubs;
using System.Threading.Tasks;
namespace ServiceHost
{
/// <summary>
/// پیاده‌سازی سرویس اطلاع‌رسانی Face Embedding با استفاده از SignalR
/// </summary>
public class FaceEmbeddingNotificationService : IFaceEmbeddingNotificationService
{
private readonly IHubContext<FaceEmbeddingHub> _hubContext;
public FaceEmbeddingNotificationService(IHubContext<FaceEmbeddingHub> 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
});
}
}
}

View File

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

View File

@@ -185,10 +185,10 @@
</div>
</div>
@if (Model.HasApkToDownload)
@if (Model.HasFaceDetectionApkToDownload)
{
<div class="position-fixed d-md-none d-block" style="bottom:18px;" id="downloadAppLogin">
<a href="/apk/android" type="button" class="btn-login d-block mx-auto w-100 text-white px-3 py-2 d-flex align-items-center">
<a href="/apk/android?type=facedetection" type="button" class="btn-login d-block mx-auto w-100 text-white px-3 py-2 d-flex align-items-center">
<svg width="18" height="18" fill="#ffffff" viewBox="-1 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
@@ -196,7 +196,7 @@
<path d="m3.751.61 13.124 7.546-2.813 2.813zm-2.719-.61 12.047 12-12.046 12c-.613-.271-1.033-.874-1.033-1.575 0-.023 0-.046.001-.068v.003-20.719c-.001-.019-.001-.042-.001-.065 0-.701.42-1.304 1.022-1.571l.011-.004zm19.922 10.594c.414.307.679.795.679 1.344 0 .022 0 .043-.001.065v-.003c.004.043.007.094.007.145 0 .516-.25.974-.636 1.258l-.004.003-2.813 1.593-3.046-2.999 3.047-3.047zm-17.203 12.796 10.312-10.359 2.813 2.813z"></path>
</g>
</svg>
<span class="mx-1">دانلود نرم افزار مخصوص موبایل</span>
<span class="mx-1">دانلود نرم افزار حضورغیاب اندروید</span>
</a>
</div>
}

View File

@@ -19,6 +19,7 @@ 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;
@@ -76,6 +77,7 @@ builder.Services.AddTransient<IAuthHelper, AuthHelper>();
builder.Services.AddTransient<IGoogleRecaptcha, GoogleRecaptcha>();
builder.Services.AddTransient<ISmsService, SmsService>();
builder.Services.AddTransient<IUidService, UidService>();
builder.Services.AddTransient<IFaceEmbeddingNotificationService, FaceEmbeddingNotificationService>();
//services.AddSingleton<IWorkingTest, WorkingTest>();
//services.AddHostedService<JobWorker>();
@@ -406,6 +408,7 @@ app.MapHub<CreateContractTarckingHub>("/trackingHub");
app.MapHub<SendAccountMessage>("/trackingSmsHub");
app.MapHub<HolidayApiHub>("/trackingHolidayHub");
app.MapHub<CheckoutHub>("/trackingCheckoutHub");
// app.MapHub<FaceEmbeddingHub>("/trackingFaceEmbeddingHub");
app.MapRazorPages();
app.MapControllers();

View File

@@ -19,7 +19,7 @@
"sqlDebugging": true,
"dotnetRunMessages": "true",
"nativeDebugging": true,
"applicationUrl": "https://localhost:5004;http://localhost:5003;https://192.168.0.117:5005;http://192.168.0.117:5006;",
"applicationUrl": "https://localhost:5004;http://localhost:5003;",
"jsWebView2Debugging": false,
"hotReloadEnabled": true
},