# Cập nhật Transaction: Thêm ExpenseId và IncomeId

## Tổng quan

Đã cập nhật entity `Transaction` để thêm 2 trường mới:

- **ExpenseId** - ID của chi phí liên kết
- **IncomeId** - ID của doanh thu liên kết

## Mục đích

### ExpenseId

- Được set khi tạo Transaction trong hàm **duyệt chi phí** (Expense Approve)
- Khi chi phí chuyển sang trạng thái `Paid`, hệ thống sẽ:
  1. Tạo Transaction
  2. Set ExpenseId = Expense.Id
  3. Trừ tiền từ ví

### IncomeId

- Được set khi tạo Transaction trong hàm **duyệt doanh thu** (Income Approve)
- Khi doanh thu chuyển sang trạng thái `Received`, hệ thống sẽ:
  1. Tạo Transaction
  2. Set IncomeId = Income.Id
  3. Cộng tiền vào ví

## Cấu trúc dữ liệu

### Transaction Entity - Cập nhật

```csharp
public class Transaction : BaseEntityWithKey<Guid>
{
    // ... existing properties ...

    /// <summary>
    /// ID của chi phí liên kết (nếu có)
    /// </summary>
    public Guid? ExpenseId { get; set; }
    public virtual Expense? Expense { get; set; }

    /// <summary>
    /// ID của doanh thu liên kết (nếu có)
    /// </summary>
    public Guid? IncomeId { get; set; }
    public virtual Income? Income { get; set; }
}
```

## Relationship Chain

### Expense -> Transaction

```
Expense (TransactionId) <-> Transaction (ExpenseId)
```

- Mỗi Expense có thể có 1 Transaction (1-1)
- Transaction.ExpenseId point về Expense
- Khi Expense.Status = Paid, tạo Transaction và set ExpenseId

### Income -> Transaction

```
Income (TransactionId) <-> Transaction (IncomeId)
```

- Mỗi Income có thể có 1 Transaction (1-1)
- Transaction.IncomeId point về Income
- Khi Income.Status = Received, tạo Transaction và set IncomeId

## Database Schema

### Script SQL

File: `Ladaer.BackEnd/Scripts/AddExpenseIdAndIncomeIdToTransaction.sql`

```sql
-- Thêm cột ExpenseId
ALTER TABLE [dbo].[Transaction]
ADD [ExpenseId] [uniqueidentifier] NULL

-- Index
CREATE NONCLUSTERED INDEX [IX_Transaction_ExpenseId]
ON [dbo].[Transaction] ([ExpenseId])

-- Foreign key
ALTER TABLE [dbo].[Transaction]
ADD CONSTRAINT [FK_Transaction_Expense_ExpenseId]
FOREIGN KEY([ExpenseId]) REFERENCES [dbo].[Expense] ([Id])

-- Thêm cột IncomeId
ALTER TABLE [dbo].[Transaction]
ADD [IncomeId] [uniqueidentifier] NULL

-- Index
CREATE NONCLUSTERED INDEX [IX_Transaction_IncomeId]
ON [dbo].[Transaction] ([IncomeId])

-- Foreign key
ALTER TABLE [dbo].[Transaction]
ADD CONSTRAINT [FK_Transaction_Income_IncomeId]
FOREIGN KEY([IncomeId]) REFERENCES [dbo].[Income] ([Id])
```

## Entity Framework Mapping

### TransactionMap.cs

```csharp
public void Map(EntityTypeBuilder<Transaction> builder)
{
    // ... existing mappings ...

    builder.HasOne(t => t.Expense)
        .WithMany()
        .HasForeignKey(t => t.ExpenseId)
        .OnDelete(DeleteBehavior.SetNull);

    builder.HasOne(t => t.Income)
        .WithMany()
        .HasForeignKey(t => t.IncomeId)
        .OnDelete(DeleteBehavior.SetNull);
}
```

## Sử dụng trong Code

### 1. Duyệt chi phí (ExpenseService)

```csharp
public async Task<BaseResult> ApproveExpense(Guid expenseId, CurrentUser currentUser)
{
    var expense = await _expenseRepository.GetByIdAsync(expenseId);

    if (expense.Status != ExpenseStatus.Approved)
    {
        return BaseResult.Failed("Chi phí chưa được phê duyệt");
    }

    // Tạo Transaction
    var transaction = new Transaction
    {
        Id = Guid.NewGuid(),
        Amount = expense.Amount,
        TransactionDirection = TransactionDirection.Outcome,
        TransactionType = TransactionType.Expense,
        Description = expense.Description,
        ExpenseId = expense.Id,  // ← Set ExpenseId
        WalletId = walletId,
        // ... other properties
    };

    await _transactionRepository.AddAsync(transaction);

    // Update Expense
    expense.Status = ExpenseStatus.Paid;
    expense.TransactionId = transaction.Id;

    await _expenseRepository.UpdateAsync(expense);

    return BaseResult.Success();
}
```

### 2. Duyệt doanh thu (IncomeService)

```csharp
public async Task<BaseResult> ApproveIncome(Guid incomeId, CurrentUser currentUser)
{
    var income = await _incomeRepository.GetByIdAsync(incomeId);

    if (income.Status != IncomeStatus.Approved)
    {
        return BaseResult.Failed("Doanh thu chưa được phê duyệt");
    }

    // Tạo Transaction
    var transaction = new Transaction
    {
        Id = Guid.NewGuid(),
        Amount = income.Amount,
        TransactionDirection = TransactionDirection.Income,
        TransactionType = TransactionType.Income,
        Description = income.Description,
        IncomeId = income.Id,  // ← Set IncomeId
        WalletId = walletId,
        // ... other properties
    };

    await _transactionRepository.AddAsync(transaction);

    // Update Income
    income.Status = IncomeStatus.Received;
    income.TransactionId = transaction.Id;

    await _incomeRepository.UpdateAsync(income);

    return BaseResult.Success();
}
```

## Lợi ích

1. **Tra cứu ngược**: Từ Transaction có thể truy ngược về Expense hoặc Income
2. **Audit trail**: Biết được Transaction nào được tạo từ Expense/Income nào
3. **Báo cáo**: Có thể filter Transaction theo ExpenseId hoặc IncomeId
4. **Validation**: Tránh duplicate Transaction cho cùng 1 Expense/Income

## Lưu ý

- **ExpenseId** và **IncomeId** đều **nullable** vì Transaction có thể không liên kết với Expense hay Income
- **Foreign keys** với `OnDelete(DeleteBehavior.SetNull)` để tránh cascade delete
- Chỉ nên set 1 trong 2 cột (ExpenseId HOẶC IncomeId), không set cả 2
