---
name: BackendReport
description: Hướng dẫn tạo báo cáo Backend sử dụng Stored Procedure và Dapper.
---

# Backend Report Development Skill

Tài liệu này hướng dẫn các bước tiêu chuẩn để phát triển báo cáo Backend trong dự án LADA, sử dụng Stored Procedure và Dapper.

## 1. Tạo Stored Procedure

- **Vị trí**: `Ladaer.BackEnd/Scripts/sp_[ReportName].sql`
- **Quy tắc**:
  - Đặt tên theo format `sp_Get[ReportName]` hoặc `sp_Get[ReportName]By[GroupBy]`
  - Sử dụng tham số `@StartDate`, `@EndDate` cho bộ lọc thời gian
  - Sử dụng `STRING_SPLIT` cho bộ lọc nhiều giá trị (VD: `@DriverIds`)
  - Set `NOCOUNT ON` để tối ưu hiệu suất

```sql
CREATE PROCEDURE [dbo].[sp_GetMyReportByGroup]
    @StartDate DATE = NULL,
    @EndDate DATE = NULL,
    @GroupIds NVARCHAR(MAX) = NULL
AS
BEGIN
    SET NOCOUNT ON;

    -- Mặc định nếu không truyền tham số
    IF @StartDate IS NULL SET @StartDate = '1900-01-01';
    IF @EndDate IS NULL SET @EndDate = '9999-12-31';

    SELECT
        GroupId,
        SUM(Amount) AS TotalAmount
    FROM MyTable
    WHERE IsDeleted = 0
        AND CAST(CreatedTime AS DATE) >= @StartDate
        AND CAST(CreatedTime AS DATE) <= @EndDate
        AND (@GroupIds IS NULL OR GroupId IN (
            SELECT CAST(value AS INT) FROM STRING_SPLIT(@GroupIds, ',')
        ))
    GROUP BY GroupId
    ORDER BY GroupId;
END
```

## 2. Tạo DTO Layer

### 2.1 Item DTO
- **Vị trí**: `Infrastructures/DTO/Reports/Items/[Module]/[ReportName]Item.cs`

```csharp
public class MyReportItem
{
    public int GroupId { get; set; }
    public string GroupName { get; set; } = string.Empty;
    public decimal TotalAmount { get; set; }
    // Các trường khác...
}
```

### 2.2 Result DTO
- **Vị trí**: `Infrastructures/DTO/Reports/Results/[ReportName]Result.cs`
- Kế thừa từ `BaseResult`
- Tự động tính `TotalItem` khi set `Items`

```csharp
public class MyReportResult : BaseResult
{
    private List<MyReportItem> _items = new();

    public List<MyReportItem> Items
    {
        get => _items;
        set
        {
            _items = value ?? new List<MyReportItem>();
            UpdateTotalItem();
        }
    }

    public MyReportItem TotalItem { get; private set; } = new();
    public int TotalRecord { get; set; }

    private void UpdateTotalItem()
    {
        if (_items == null || !_items.Any()) return;
        TotalItem = new MyReportItem
        {
            GroupId = 0,
            GroupName = "TỔNG CỘNG",
            TotalAmount = _items.Sum(x => x.TotalAmount)
        };
    }
}
```

## 3. Tạo Service Layer

- **Vị trí**: `Infrastructures/Services/Report/[Module]/[ReportName]ReportService.cs`
- Inject `IDbConnection` để sử dụng Dapper
- Xử lý filter từ `SmartTableParam.Search.PredicateObject`

```csharp
public interface IMyReportService
{
    Task<MyReportResult> GetMyReportAsync(SmartTableParam param);
}

public class MyReportService : IMyReportService
{
    private readonly IDbConnection _dbConnection;

    public MyReportService(IDbConnection dbConnection)
    {
        _dbConnection = dbConnection;
    }

    public async Task<MyReportResult> GetMyReportAsync(SmartTableParam param)
    {
        var result = new MyReportResult();
        const string storeName = "sp_GetMyReportByGroup";

        try
        {
            // Parse filters từ SmartTableParam
            var parameters = new DynamicParameters();
            parameters.Add("StartDate", fromDate);
            parameters.Add("EndDate", toDate);
            parameters.Add("GroupIds", groupIds);

            result.Items = (await _dbConnection.QueryAsync<MyReportItem>(
                storeName,
                parameters,
                commandType: CommandType.StoredProcedure)).ToList();

            result.TotalRecord = result.Items.Count;
        }
        catch (Exception ex)
        {
            result.Result = Lada.Framework.DTO.Result.SystemError;
            result.Message = ex.Message;
        }

        return result;
    }
}
```

## 4. Đăng ký Dependency Injection

- **Vị trí**: `Infrastructures/Startup/DependencyRegister.cs`

```csharp
services.AddTransient<IMyReportService, MyReportService>();
```

## 5. Tạo Controller

- **Vị trí**: `Controllers/Reports/[Module]/[ReportName]ReportController.cs`
- Sử dụng `SmartTableParam` làm request body
- Authorization theo format `[ReportName].GetXxx`

```csharp
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class MyReportController : ControllerBase
{
    private readonly IMyReportService _service;

    public MyReportController(IMyReportService service) => _service = service;

    [HttpPost("GetByGroup")]
    [Authorize(Roles = "MyReport.GetByGroup")]
    public async Task<IActionResult> GetByGroup([FromBody] SmartTableParam param)
    {
        try
        {
            var results = await _service.GetMyReportAsync(param);
            return Ok(results);
        }
        catch (Exception ex)
        {
            return BadRequest(new { message = ex.Message });
        }
    }
}
```

## 6. Viết Tài liệu API

- **Vị trí**: `docs/[Module]/API_[ReportName].md`
- Tham khảo skill `ApiDocumentation` để viết theo chuẩn

## 7. Checklist kiểm tra

- [ ] Stored Procedure đã test với các bộ lọc khác nhau
- [ ] DTO Item có đủ các trường cần thiết
- [ ] DTO Result tự động tính TotalItem
- [ ] Service xử lý đúng các filter từ SmartTableParam
- [ ] DI đã được đăng ký
- [ ] Controller có Authorization đúng
- [ ] Tài liệu API đầy đủ
- [ ] Build thành công
