# Daya Loan Integration System (سیستم یکپارچه‌سازی وام دایا)

## 📌 Overview

سیستم یکپارچه‌سازی با سرویس وام دایا برای شارژ خودکار کیف پول کاربران که وام دایا دریافت کرده‌اند.

**مقادیر شارژ:**
- **کیف پول اصلی (Balance)**: 56,000,000 تومان
- **کیف پول شبکه/کارمزد (NetworkBalance)**: 56,000,000 تومان  
- **کیف پول تخفیف (DiscountBalance)**: 56,000,000 تومان
- **مجموع**: 168,000,000 تومان

**نکته مهم:** کیف پول باشگاه (ClubWallet) باید توسط کاربر در فرانت‌آفیس به صورت دستی شارژ شود.

---

## 🗂️ Architecture

### Domain Layer

#### **DayaLoanStatus Enum**
```csharp
public enum DayaLoanStatus
{
    NotRequested = 0,    // درخواست نشده
    PendingReceive = 1,  // در انتظار دریافت وام (فعال شده)
    Received = 2,        // وام دریافت شده
    Rejected = 3,        // رد شده
    UnderReview = 4      // در حال بررسی
}
```

#### **DayaLoanContract Entity**
```csharp
public class DayaLoanContract : BaseAuditableEntity
{
    public long UserId { get; set; }
    public string NationalCode { get; set; }
    public string? ContractNumber { get; set; }
    public DayaLoanStatus Status { get; set; }
    public bool IsProcessed { get; set; }
    public DateTime? LastCheckDate { get; set; }
    public DateTime? ProcessedDate { get; set; }
    public long? TransactionId { get; set; }
    
    // Navigation Properties
    public virtual User User { get; set; }
    public virtual Transactions? Transaction { get; set; }
}
```

#### **User Entity Extensions**
```csharp
public class User : BaseAuditableEntity
{
    // ... existing properties ...
    
    public bool HasReceivedDayaCredit { get; set; }
    public DateTime? DayaCreditReceivedAt { get; set; }
    public virtual ICollection<DayaLoanContract>? DayaLoanContracts { get; set; }
}
```

---

### Application Layer

#### **Commands**

##### 1. ProcessDayaLoanApprovalCommand
شارژ کیف پول کاربر بعد از تایید وام دایا

**Request:**
```csharp
public record ProcessDayaLoanApprovalCommand : IRequest<ProcessDayaLoanApprovalResponseDto>
{
    public long UserId { get; init; }
    public string ContractNumber { get; init; }
    public long WalletAmount { get; init; } = 56_000_000;
    public long LockedWalletAmount { get; init; } = 56_000_000;
    public long DiscountWalletAmount { get; init; } = 56_000_000;
}
```

**Response:**
```csharp
public class ProcessDayaLoanApprovalResponseDto
{
    public long UserId { get; set; }
    public long TransactionId { get; set; }
    public string ContractNumber { get; set; }
    public long MainWalletBalance { get; set; }
    public long LockedWalletBalance { get; set; }
    public long DiscountWalletBalance { get; set; }
    public string Message { get; set; }
}
```

**Business Logic:**
1. بررسی اینکه کاربر قبلاً اعتبار دایا را دریافت نکرده باشد
2. ایجاد Transaction با:
   - Type: DepositExternal1
   - Amount: 168M تومان
   - RefId: شماره قرارداد دایا
3. شارژ سه نوع کیف پول (Balance, NetworkBalance, DiscountBalance)
4. ثبت UserWalletChangeLog برای Balance و NetworkBalance (⚠️ DiscountBalance لاگ ندارد)
5. به‌روزرسانی فلگ‌های کاربر (HasReceivedDayaCredit, DayaCreditReceivedAt)
6. انتشار DayaLoanApprovedEvent

##### 2. CheckDayaLoanStatusCommand
استعلام وضعیت وام از سرویس دایا

**Request:**
```csharp
public record CheckDayaLoanStatusCommand : IRequest<CheckDayaLoanStatusResponseDto>
{
    public List<string> NationalCodes { get; init; }
}
```

**Response:**
```csharp
public class CheckDayaLoanStatusResponseDto
{
    public List<DayaLoanCheckResult> Results { get; set; }
    public int TotalChecked { get; set; }
    public int SuccessCount { get; set; }
}

public class DayaLoanCheckResult
{
    public string NationalCode { get; set; }
    public DayaLoanStatus Status { get; set; }
    public string? ContractNumber { get; set; }
}
```

**✅ Current Status:** این Command کاملاً پیاده‌سازی شده و به API واقعی Daya متصل است.

#### **API Integration Details:**
- **Endpoint**: `POST /api/merchant/contracts`
- **Base URL**: `https://testdaya.tadbirandishan.com`
- **Authentication**: `merchant-permission-key` header
- **Request Body**:
  ```json
  {
    "nationalCodes": ["1234567890", "0987654321"]
  }
  ```
- **Response Structure**:
  ```json
  {
    "succeed": true,
    "code": 200,
    "message": "Success",
    "data": [
      {
        "nationalCode": "1234567890",
        "contractNumber": "DAYA-12345",
        "statusDescription": "فعال شده (در انتظار تسویه)",
        "dateTime": "2024-12-06T10:30:00"
      }
    ]
  }
  ```
- **Status Mapping**:
  - "فعال شده (در انتظار تسویه)" → PendingReceive
  - "تایید شده" → Received
  - "رد شده" → Rejected
  - Default → UnderReview
- **Cache Duration**: 20 minutes (per Daya API spec)
- **Multiple Contracts**: If user has multiple contracts, system takes the latest one by DateTime

---

### Infrastructure Layer

#### **IDayaLoanApiService Implementations**

**1. MockDayaLoanApiService** (Testing):
- Returns mock data based on NationalCode patterns
- Instant response for fast testing
- No external dependencies

**2. DayaLoanApiService** (Production):
- ✅ Fully implemented with HttpClient
- Posts to `/api/merchant/contracts` endpoint
- Handles API errors gracefully
- Maps Persian status descriptions to enum values
- Returns empty results on error (prevents worker crashes)

**Configuration** (`appsettings.json`):
```json
{
  "DayaApi": {
    "UseMock": false,
    "BaseAddress": "https://testdaya.tadbirandishan.com",
    "MerchantPermissionKey": "14752708$Db5Wk5h...",
    "CacheDurationMinutes": 20
  }
}
```

**Service Registration** (`ConfigureServices.cs`):
- Reads `DayaApi:UseMock` from configuration
- If `true`: Uses MockDayaLoanApiService
- If `false`: Uses DayaLoanApiService with HttpClient
- HttpClient configured with BaseAddress, headers, and 30s timeout

#### **Background Worker: DayaLoanCheckWorker**
Worker خودکار که هر 15 دقیقه کاربران با وام pending را چک می‌کند.

**Location:** `CMSMicroservice.WebApi/Workers/DayaLoanCheckWorker.cs`

**Schedule:** `*/15 * * * *` (هر 15 دقیقه)

**Logic:**
1. Query کاربرانی که `HasReceivedDayaCredit == false` و دارای `NationalCode` هستند
2. فراخوانی `CheckDayaLoanStatusCommand` با لیست کدملی‌ها
3. برای هر نتیجه با Status=PendingReceive و ContractNumber موجود:
   - فراخوانی `ProcessDayaLoanApprovalCommand`
   - لاگ نتیجه عملیات
4. Retry خودکار در صورت خطا (Hangfire AutomaticRetry)

**Registration:** در `Program.cs` ثبت شده است:
```csharp
DayaLoanCheckWorker.Schedule(recurringJobManager);
```

---

## 🔄 Process Flow

```
1. کاربر درخواست وام دایا می‌دهد (خارج از سیستم)
   ↓
2. Worker هر 15 دقیقه کاربران pending را چک می‌کند
   ↓
3. CheckDayaLoanStatusCommand → فراخوانی API دایا
   ↓
4. اگر Status = PendingReceive و ContractNumber موجود بود:
   ↓
5. ProcessDayaLoanApprovalCommand اجرا می‌شود:
   - ایجاد Transaction (168M تومان)
   - شارژ Balance (+56M)
   - شارژ NetworkBalance (+56M)
   - شارژ DiscountBalance (+56M)
   - ثبت WalletChangeLog (برای Balance و NetworkBalance)
   - تنظیم HasReceivedDayaCredit = true
   ↓
6. DayaLoanApprovedEvent منتشر می‌شود
   ↓
7. EventHandler می‌تواند عملیات جانبی انجام دهد (مثل ارسال اطلاع‌رسانی)
```

---

## 💾 Database Schema

### DayaLoanContracts Table
```sql
CREATE TABLE [CMS].[DayaLoanContracts] (
    [Id] bigint IDENTITY(1,1) PRIMARY KEY,
    [UserId] bigint NOT NULL FOREIGN KEY REFERENCES Users(Id),
    [NationalCode] nvarchar(max) NOT NULL,
    [ContractNumber] nvarchar(max) NULL,
    [Status] int NOT NULL,
    [IsProcessed] bit NOT NULL,
    [LastCheckDate] datetime2 NULL,
    [ProcessedDate] datetime2 NULL,
    [TransactionId] bigint NULL FOREIGN KEY REFERENCES Transactionss(Id),
    [Created] datetime2 NOT NULL,
    [CreatedBy] nvarchar(max) NULL,
    [LastModified] datetime2 NULL,
    [LastModifiedBy] nvarchar(max) NULL,
    [IsDeleted] bit NOT NULL
);
```

### User Table Extensions
```sql
ALTER TABLE [CMS].[Users]
ADD [HasReceivedDayaCredit] bit NOT NULL DEFAULT 0,
    [DayaCreditReceivedAt] datetime2 NULL;
```

**Migration:** `20251201191716_AddDayaLoanIntegration.cs`

---

## ⚠️ Important Notes

### ⚠️ CRITICAL: Don't Remove Business Logic on Errors!
- **وقتی با خطا مواجه شدیم، NEVER پاک نکنید بخشی از بیزینس را**
- **اول 5 بار تلاش کنید که خطا را برطرف کنید**
- اگر خطا برطرف نشد، آن را به حال خود رها کنید (Comment + TODO)
- Developer دستی خطا را بررسی و حل خواهد کرد

**مثال درست:**
```csharp
// TODO: این قسمت خطا دارد - نیاز به بررسی
// Error: CS1234 - Type not found
// var discountLog = new UserWalletChangeLog { ... };
// await _context.UserWalletChangeLogs.AddAsync(discountLog);
```

**مثال غلط (ممنوع!):**
```csharp
// ❌ پاک کردن لاگ DiscountBalance برای حل خطا - WRONG!
// این کار باعث از دست رفتن بخشی از بیزینس می‌شود
```

### 1. UserWalletChangeLog Limitation
- فیلدهای موجود: `CurrentBalance`, `ChangeValue`, `CurrentNetworkBalance`, `ChangeNerworkValue`
- **مشکل:** فیلدی برای `DiscountBalance` وجود ندارد
- **راه‌حل فعلی:** تغییرات DiscountBalance در لاگ ثبت نمی‌شود، فقط در جدول UserWallets ذخیره می‌شود
- **پیشنهاد آینده:** اضافه کردن فیلدهای `CurrentDiscountBalance` و `ChangeDiscountValue` به UserWalletChangeLog

### 2. Daya API Integration
- **وضعیت فعلی:** CheckDayaLoanStatusCommandHandler یک skeleton است
- **TODO:** پیاده‌سازی API واقعی دایا در Handler
- **Placeholder Code:**
  ```csharp
  // TODO: فراخوانی سرویس دایا
  // در حال حاضر داده Mock برمی‌گردانیم
  ```

### 3. Transaction Type
- از `TransactionType.DepositExternal1` استفاده می‌شود
- `RefId` = شماره قرارداد دایا
- این اطلاعات برای پیگیری و تطبیق با دایا ضروری است

### 4. One-Time Credit
- هر کاربر فقط **یک بار** می‌تواند اعتبار دایا دریافت کند
- بررسی توسط `HasReceivedDayaCredit` flag
- تلاش برای دریافت مجدد با خطا مواجه می‌شود

---

## 🧪 Testing

### Manual Testing via Hangfire Dashboard
1. به Hangfire Dashboard بروید: `/hangfire`
2. در بخش "Recurring Jobs" job با نام `daya-loan-check` را پیدا کنید
3. دکمه "Trigger now" را بزنید
4. در بخش "Jobs" می‌توانید لاگ‌ها را ببینید

### Testing Commands via gRPC (آینده)
```bash
# فراخوانی ProcessDayaLoanApproval
grpcurl -d '{
  "userId": 123,
  "contractNumber": "DAYA-12345"
}' localhost:5001 ProcessDayaLoanApproval

# فراخوانی CheckDayaLoanStatus
grpcurl -d '{
  "nationalCodes": ["1234567890"]
}' localhost:5001 CheckDayaLoanStatus
```

---

## ✅ Completed Implementation

### High Priority (All Done)
- ✅ پیاده‌سازی API واقعی دایا در DayaLoanApiService (December 6, 2025)
  - HTTP POST to `/api/merchant/contracts`
  - Request/Response models with JSON serialization
  - Status description mapping (Persian → Enum)
  - Error handling and logging
  - Configurable via appsettings.json
- ✅ Conditional service registration (Mock vs Real)
- ✅ HttpClient configuration with authentication
- ✅ Worker fully operational with real API

### Low Priority (Optional)
- [ ] اضافه کردن Proto definitions برای Daya commands
- [ ] Admin UI for Daya contract management
- [ ] Unit tests for API service
- [ ] اضافه کردن gRPC service endpoints
- [ ] تست Worker در محیط development

### Medium Priority
- [ ] ایجاد BFF handlers برای عملیات دایا
- [ ] ایجاد صفحات BackOffice برای مدیریت وام دایا
- [ ] اضافه کردن فیلتر برای مشاهده کاربران با وام دایا
- [ ] نمایش تاریخچه Daya Loan Contracts

### Low Priority
- [ ] اضافه کردن Unit Tests برای ProcessDayaLoanApprovalCommand
- [ ] اضافه کردن Integration Tests برای DayaLoanCheckWorker
- [ ] اضافه کردن Monitoring/Alerting برای خطاهای API دایا
- [ ] بهینه‌سازی Query برای یافتن کاربران pending
- [ ] اضافه کردن فیلدهای DiscountBalance به UserWalletChangeLog

---

## 🔗 Related Files

### Domain
- `CMSMicroservice.Domain/Enums/DayaLoanStatus.cs`
- `CMSMicroservice.Domain/Entities/DayaLoanContract.cs`
- `CMSMicroservice.Domain/Entities/User.cs` (updated)
- `CMSMicroservice.Domain/Events/DayaLoanApprovedEvent.cs`

### Application
- `CMSMicroservice.Application/DayaLoanCQ/Commands/ProcessDayaLoanApproval/`
  - ProcessDayaLoanApprovalCommand.cs
  - ProcessDayaLoanApprovalCommandHandler.cs
  - ProcessDayaLoanApprovalCommandValidator.cs
  - ProcessDayaLoanApprovalResponseDto.cs
- `CMSMicroservice.Application/DayaLoanCQ/Commands/CheckDayaLoanStatus/`
  - CheckDayaLoanStatusCommand.cs
  - CheckDayaLoanStatusCommandHandler.cs
  - CheckDayaLoanStatusResponseDto.cs
- `CMSMicroservice.Application/DayaLoanCQ/EventHandlers/`
  - DayaLoanApprovedEventHandler.cs

### Infrastructure
- `CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs` (updated)
- `CMSMicroservice.Infrastructure/Persistence/Migrations/20251201191716_AddDayaLoanIntegration.cs`

### WebApi
- `CMSMicroservice.WebApi/Workers/DayaLoanCheckWorker.cs`
- `CMSMicroservice.WebApi/Program.cs` (updated)

---

## 🧪 Testing

### Manual Testing

#### 1. ایجاد کاربر تست با کدملی شروع شده با "1"
```sql
-- کاربری که Mock Service برایش وام تایید می‌کند
INSERT INTO CMS.Users (NationalCode, FirstName, LastName, Mobile, HasReceivedDayaCredit)
VALUES ('1234567890', 'Test', 'User', '09121234567', 0);
```

#### 2. اجرای دستی Worker از Hangfire Dashboard
- باز کردن: `https://localhost:5001/hangfire`
- انتخاب Job: `daya-loan-check`
- کلیک روی "Trigger now"

#### 3. بررسی Logs
```bash
# در Console پروژه CMS
[INFO] DayaLoanCheckWorker started at 2024-12-02 10:30:00
[INFO] Found 1 users with pending Daya loan status
[WARN] ⚠️ Using MOCK Daya API Service - Replace with real implementation!
[INFO] Mock Daya API returned 1 results
[INFO] Daya loan processed for user 123. Contract: MOCK-DAYA-1234567890-638123456789
[INFO] DayaLoanCheckWorker completed. Checked: 1, Processed: 1
```

#### 4. بررسی Database
```sql
-- چک کردن DayaLoanContract
SELECT * FROM CMS.DayaLoanContracts WHERE NationalCode = '1234567890';

-- چک کردن UserWallet
SELECT * FROM CMS.UserWallets WHERE UserId = 123;
-- Balance باید 56,000,000 باشد
-- NetworkBalance باید 56,000,000 باشد
-- DiscountBalance باید 56,000,000 باشد

-- چک کردن Transaction
SELECT * FROM CMS.Transactionss WHERE RefId LIKE 'MOCK-DAYA-%';
-- Amount باید 168,000,000 باشد

-- چک کردن User Flag
SELECT HasReceivedDayaCredit, DayaCreditReceivedAt FROM CMS.Users WHERE Id = 123;
-- HasReceivedDayaCredit باید 1 باشد
```

#### 5. تست Mock Service Scenarios
```csharp
// کدملی شروع با "1" → PendingReceive + ContractNumber
// کدملی شروع با "2" → Rejected
// سایر کدملی‌ها → PendingReceive (بدون ContractNumber)
```

### Integration Testing با Real API

زمانی که API واقعی دایا آماده شد:

1. **تغییر ConfigureServices:**
```csharp
// در CMSMicroservice.Infrastructure/ConfigureServices.cs
services.AddScoped<IDayaLoanApiService, DayaLoanApiService>(); // Real
// services.AddScoped<IDayaLoanApiService, MockDayaLoanApiService>(); // Mock - حذف شود
```

2. **تنظیم HttpClient:**
```csharp
services.AddHttpClient<IDayaLoanApiService, DayaLoanApiService>(client =>
{
    client.BaseAddress = new Uri(configuration["DayaApi:BaseUrl"]);
    client.Timeout = TimeSpan.FromSeconds(30);
});
```

3. **اضافه کردن به appsettings.json:**
```json
{
  "DayaApi": {
    "BaseUrl": "https://api.daya.ir",
    "ApiKey": "YOUR_API_KEY_HERE"
  }
}
```

---

## 🐛 Troubleshooting

### مشکل: Worker اجرا نمی‌شود

**علت احتمالی:** Hangfire Server شروع نشده

**راه حل:**
```csharp
// در Program.cs چک کنید که این خط وجود دارد:
builder.Services.AddHangfireServer();
```

---

### مشکل: کاربران پیدا نمی‌شوند

**علت احتمالی:** همه کاربران قبلاً اعتبار دریافت کرده‌اند

**راه حل:**
```sql
-- Reset کردن وضعیت کاربران برای تست
UPDATE CMS.Users SET HasReceivedDayaCredit = 0, DayaCreditReceivedAt = NULL;
```

---

### مشکل: کیف پول شارژ نمی‌شود

**علت احتمالی:** کاربر کیف پول ندارد

**راه حل:**
```csharp
// کد Handler خودکار UserWallet می‌سازد اگر موجود نباشد:
if (wallet == null)
{
    wallet = new UserWallet { UserId = request.UserId, Balance = 0, ... };
    await _context.UserWallets.AddAsync(wallet, cancellationToken);
}
```

---

### مشکل: Mock API همیشه نتیجه یکسان برمی‌گرداند

**راه حل:** کدملی کاربر را تغییر دهید:
- کدملی شروع با **"1"** → وام تایید می‌شود ✅
- کدملی شروع با **"2"** → وام رد می‌شود ❌
- سایر → در انتظار (بدون ContractNumber) ⏳

---

### مشکل: Exception در ProcessDayaLoanApproval

**خطای احتمالی:** `User has already received Daya credit`

**علت:** کاربر قبلاً اعتبار دریافت کرده

**راه حل:**
```sql
-- فقط برای محیط Development
UPDATE CMS.Users SET HasReceivedDayaCredit = 0 WHERE Id = 123;
```

---

### مشکل: Migration اعمال نمی‌شود

**راه حل:**
```bash
cd CMS/src/CMSMicroservice.WebApi
dotnet ef database update
```

یا در Package Manager Console:
```powershell
Update-Database
```

---

## 📊 Monitoring

### Hangfire Dashboard

**URL:** `https://localhost:5001/hangfire`

**Metrics:**
- Succeeded jobs
- Failed jobs
- Processing jobs
- Scheduled jobs

**Job Details:**
- Job ID: `daya-loan-check`
- Schedule: `*/15 * * * *` (Every 15 minutes)
- Next Run: نمایش داده می‌شود در Dashboard

### Application Logs

**Successful Run:**
```
[INFO] DayaLoanCheckWorker started at {Time}
[INFO] Found {Count} users with pending Daya loan status
[INFO] Daya loan processed for user {UserId}. Contract: {ContractNumber}
[INFO] DayaLoanCheckWorker completed. Checked: {Total}, Processed: {Success}
```

**Error Scenarios:**
```
[ERROR] Error processing Daya loan for user {UserId}
[ERROR] Error calling Daya API service
[ERROR] Error in DayaLoanCheckWorker
```

---

## 🔒 Security Considerations

1. **API Key Management:**
   - هرگز API Key را در کد Commit نکنید
   - از User Secrets برای Development استفاده کنید
   - از Azure Key Vault یا مشابه برای Production استفاده کنید

2. **Rate Limiting:**
   - Worker هر 15 دقیقه اجرا می‌شود → حداکثر 96 بار در روز
   - اگر API دایا محدودیت دارد، باید تنظیم شود

3. **Data Validation:**
   - کدملی باید 10 رقمی باشد
   - فقط یک بار برای هر کاربر پردازش می‌شود

---

## 📈 Performance Optimization

### Batch Processing

اگر تعداد کاربران زیاد باشد، می‌توان Query را بهینه کرد:

```csharp
// پردازش دسته‌ای (100 کاربر در هر بار)
var pendingUsers = await _context.Users
    .Where(u => u.HasReceivedDayaCredit == false && u.NationalCode != null)
    .Take(100) // Limit
    .Select(u => new { u.Id, u.NationalCode })
    .ToListAsync();
```

### Caching

می‌توان نتایج API را برای مدت کوتاهی Cache کرد:

```csharp
// Cache result for 5 minutes
[MemoryCache]
public async Task<List<DayaLoanStatusResult>> CheckLoanStatusAsync(...)
```

---

## 📚 References

- [Hangfire Documentation](https://docs.hangfire.io/)
- [MediatR Pattern](https://github.com/jbogard/MediatR)
- [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)

---

**Created:** 2024-12-01  
**Last Updated:** 2024-12-02  
**Status:** ✅ 100% Implemented (Mock API in use - Real API integration pending)  
**Migration:** `20251201191716_AddDayaLoanIntegration`  
**Test Coverage:** Manual testing documented  
**Next Steps:** Replace MockDayaLoanApiService with real API implementation when available
