# 到货交接单和出货交接单模块规格说明

## 1. 需求概述

在系统中增加两个新模块：到货交接单和出货交接单，用于记录和管理货物的交接过程。

### 1.1 到货交接单

- **交接单号**：唯一标识
- **头程物流商送达时间**：物流商送达时间
- **收货时间**：实际收货时间
- **POD**：图片链接，多张用逗号隔开
- **备注**：交接备注信息
- **创建人**：创建该交接单的用户
- **创建时间**：交接单创建时间
- **修改时间**：交接单最后修改时间

### 1.2 出货交接单

- **交接单号**：唯一标识，格式如 BOL-GOFO-202
- **大包数**：大包数量
- **小包数**：小包数量
- **渠道**：物流渠道
- **交货时间**：实际交货时间
- **POD**：图片链接，多张用逗号隔开
- **备注**：交接备注信息
- **创建人**：创建该交接单的用户
- **创建时间**：交接单创建时间
- **修改时间**：交接单最后修改时间

## 2. 技术方案

### 2.1 数据库设计

#### 2.1.1 到货交接单表 (`arrival_handover_forms`)

| 字段名 | 数据类型 | 约束 | 描述 |
|--------|----------|------|------|
| `Id` | `INT` | `PRIMARY KEY, AUTO_INCREMENT` | 主键ID |
| `HandoverNumber` | `VARCHAR(100)` | `NOT NULL, UNIQUE` | 交接单号 |
| `LogisticsProviderArrivalTime` | `DATETIME` | `NULL` | 头程物流商送达时间 |
| `ReceiptTime` | `DATETIME` | `NULL` | 收货时间 |
| `POD` | `TEXT` | `NULL` | 图片链接，多张用逗号隔开 |
| `Remarks` | `TEXT` | `NULL` | 备注 |
| `Creator` | `VARCHAR(50)` | `NOT NULL` | 创建人 |
| `CreatedAt` | `DATETIME` | `NOT NULL` | 创建时间 |
| `UpdatedAt` | `DATETIME` | `NOT NULL` | 修改时间 |

#### 2.1.2 出货交接单表 (`shipping_handover_forms`)

| 字段名 | 数据类型 | 约束 | 描述 |
|--------|----------|------|------|
| `Id` | `INT` | `PRIMARY KEY, AUTO_INCREMENT` | 主键ID |
| `HandoverNumber` | `VARCHAR(100)` | `NOT NULL, UNIQUE` | 交接单号 |
| `BigBagCount` | `INT` | `NOT NULL` | 大包数 |
| `SmallBagCount` | `INT` | `NOT NULL` | 小包数 |
| `Channel` | `VARCHAR(100)` | `NOT NULL` | 渠道 |
| `DeliveryTime` | `DATETIME` | `NULL` | 交货时间 |
| `POD` | `TEXT` | `NULL` | 图片链接，多张用逗号隔开 |
| `Remarks` | `TEXT` | `NULL` | 备注 |
| `Creator` | `VARCHAR(50)` | `NOT NULL` | 创建人 |
| `CreatedAt` | `DATETIME` | `NOT NULL` | 创建时间 |
| `UpdatedAt` | `DATETIME` | `NOT NULL` | 修改时间 |

### 2.2 模型设计

#### 2.2.1 到货交接单模型 (`ArrivalHandoverFormEntity`)

```csharp
using System;
using SqlSugar;

namespace MDL.Models
{
    [SugarTable("arrival_handover_forms")]
    public class ArrivalHandoverFormEntity
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }

        [SugarColumn(Length = 100, IsNullable = false, IsUnique = true)]
        public string HandoverNumber { get; set; }

        [SugarColumn(IsNullable = true)]
        public DateTime? LogisticsProviderArrivalTime { get; set; }

        [SugarColumn(IsNullable = true)]
        public DateTime? ReceiptTime { get; set; }

        [SugarColumn(ColumnDataType = "TEXT", IsNullable = true)]
        public string POD { get; set; }

        [SugarColumn(ColumnDataType = "TEXT", IsNullable = true)]
        public string Remarks { get; set; }

        [SugarColumn(Length = 50, IsNullable = false)]
        public string Creator { get; set; }

        [SugarColumn(IsNullable = false)]
        public DateTime CreatedAt { get; set; } = DateTime.UtcNow;

        [SugarColumn(IsNullable = false)]
        public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
    }
}
```

#### 2.2.2 出货交接单模型 (`ShippingHandoverFormEntity`)

```csharp
using System;
using SqlSugar;

namespace MDL.Models
{
    [SugarTable("shipping_handover_forms")]
    public class ShippingHandoverFormEntity
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }

        [SugarColumn(Length = 100, IsNullable = false, IsUnique = true)]
        public string HandoverNumber { get; set; }

        [SugarColumn(IsNullable = false)]
        public int BigBagCount { get; set; }

        [SugarColumn(IsNullable = false)]
        public int SmallBagCount { get; set; }

        [SugarColumn(Length = 100, IsNullable = false)]
        public string Channel { get; set; }

        [SugarColumn(IsNullable = true)]
        public DateTime? DeliveryTime { get; set; }

        [SugarColumn(ColumnDataType = "TEXT", IsNullable = true)]
        public string POD { get; set; }

        [SugarColumn(ColumnDataType = "TEXT", IsNullable = true)]
        public string Remarks { get; set; }

        [SugarColumn(Length = 50, IsNullable = false)]
        public string Creator { get; set; }

        [SugarColumn(IsNullable = false)]
        public DateTime CreatedAt { get; set; } = DateTime.UtcNow;

        [SugarColumn(IsNullable = false)]
        public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
    }
}
```

### 2.3 仓库设计

#### 2.3.1 到货交接单仓库接口 (`IArrivalHandoverFormRepository`)

```csharp
using System.Collections.Generic;
using System.Threading.Tasks;
using MDL.Models;

namespace DAL.Interfaces
{
    public interface IArrivalHandoverFormRepository
    {
        Task<int> InsertAsync(ArrivalHandoverFormEntity form);
        Task<ArrivalHandoverFormEntity> GetByIdAsync(int id);
        Task<ArrivalHandoverFormEntity> GetByHandoverNumberAsync(string handoverNumber);
        Task<List<ArrivalHandoverFormEntity>> GetAllAsync();
        Task<int> UpdateAsync(ArrivalHandoverFormEntity form);
        Task<int> DeleteAsync(int id);
        Task<bool> ExistsByHandoverNumberAsync(string handoverNumber);
        Task<(List<ArrivalHandoverFormEntity> Forms, int TotalCount)> GetArrivalHandoverFormsBatchAsync(
            int page, 
            int pageSize, 
            string sortBy, 
            string sortOrder,
            string handoverNumber,
            string creator);
    }
}
```

#### 2.3.2 出货交接单仓库接口 (`IShippingHandoverFormRepository`)

```csharp
using System.Collections.Generic;
using System.Threading.Tasks;
using MDL.Models;

namespace DAL.Interfaces
{
    public interface IShippingHandoverFormRepository
    {
        Task<int> InsertAsync(ShippingHandoverFormEntity form);
        Task<ShippingHandoverFormEntity> GetByIdAsync(int id);
        Task<ShippingHandoverFormEntity> GetByHandoverNumberAsync(string handoverNumber);
        Task<List<ShippingHandoverFormEntity>> GetAllAsync();
        Task<int> UpdateAsync(ShippingHandoverFormEntity form);
        Task<int> DeleteAsync(int id);
        Task<bool> ExistsByHandoverNumberAsync(string handoverNumber);
        Task<(List<ShippingHandoverFormEntity> Forms, int TotalCount)> GetShippingHandoverFormsBatchAsync(
            int page, 
            int pageSize, 
            string sortBy, 
            string sortOrder,
            string handoverNumber,
            string channel,
            string creator);
    }
}
```

### 2.4 服务设计

#### 2.4.1 到货交接单服务接口 (`IArrivalHandoverFormService`)

```csharp
using System.Collections.Generic;
using System.Threading.Tasks;
using MDL.Models;

namespace BLL.Interfaces
{
    public interface IArrivalHandoverFormService
    {
        Task<int> CreateArrivalHandoverFormAsync(ArrivalHandoverFormEntity form);
        Task<ArrivalHandoverFormEntity> GetArrivalHandoverFormByIdAsync(int id);
        Task<ArrivalHandoverFormEntity> GetArrivalHandoverFormByNumberAsync(string handoverNumber);
        Task<List<ArrivalHandoverFormEntity>> GetAllArrivalHandoverFormsAsync();
        Task<int> UpdateArrivalHandoverFormAsync(ArrivalHandoverFormEntity form);
        Task<int> DeleteArrivalHandoverFormAsync(int id);
        Task<string> GenerateArrivalHandoverNumberAsync();
        Task<(List<ArrivalHandoverFormEntity> Forms, int TotalCount)> GetArrivalHandoverFormsBatchAsync(
            int page, 
            int pageSize, 
            string sortBy, 
            string sortOrder,
            string handoverNumber,
            string creator);
    }
}
```

#### 2.4.2 出货交接单服务接口 (`IShippingHandoverFormService`)

```csharp
using System.Collections.Generic;
using System.Threading.Tasks;
using MDL.Models;

namespace BLL.Interfaces
{
    public interface IShippingHandoverFormService
    {
        Task<int> CreateShippingHandoverFormAsync(ShippingHandoverFormEntity form);
        Task<ShippingHandoverFormEntity> GetShippingHandoverFormByIdAsync(int id);
        Task<ShippingHandoverFormEntity> GetShippingHandoverFormByNumberAsync(string handoverNumber);
        Task<List<ShippingHandoverFormEntity>> GetAllShippingHandoverFormsAsync();
        Task<int> UpdateShippingHandoverFormAsync(ShippingHandoverFormEntity form);
        Task<int> DeleteShippingHandoverFormAsync(int id);
        Task<string> GenerateShippingHandoverNumberAsync();
        Task<(List<ShippingHandoverFormEntity> Forms, int TotalCount)> GetShippingHandoverFormsBatchAsync(
            int page, 
            int pageSize, 
            string sortBy, 
            string sortOrder,
            string handoverNumber,
            string channel,
            string creator);
    }
}
```

### 2.5 控制器设计

#### 2.5.1 到货交接单控制器 (`ArrivalHandoverFormController`)

```csharp
using BLL.Interfaces;
using MDL.Models;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;

namespace CONTROLLER.Controllers
{
    [Route("api/arrival-handover")]
    [ApiController]
    public class ArrivalHandoverFormController : ControllerBase
    {
        private readonly IArrivalHandoverFormService _arrivalHandoverFormService;

        public ArrivalHandoverFormController(IArrivalHandoverFormService arrivalHandoverFormService)
        {
            _arrivalHandoverFormService = arrivalHandoverFormService;
        }

        [HttpPost("create")]
        public async Task<IActionResult> CreateArrivalHandoverForm([FromBody] ArrivalHandoverFormEntity form)
        {
            try
            {
                if (string.IsNullOrEmpty(form.HandoverNumber))
                {
                    form.HandoverNumber = await _arrivalHandoverFormService.GenerateArrivalHandoverNumberAsync();
                }
                form.CreatedAt = DateTime.UtcNow;
                form.UpdatedAt = DateTime.UtcNow;
                var result = await _arrivalHandoverFormService.CreateArrivalHandoverFormAsync(form);
                return Ok(new
                {
                    code = 0,
                    message = "success",
                    data = result
                });
            }
            catch (Exception ex)
            {
                return Ok(new
                {
                    code = 9999,
                    message = ex.Message
                });
            }
        }

        [HttpGet("get/{id}")]
        public async Task<IActionResult> GetArrivalHandoverFormById(int id)
        {
            try
            {
                var form = await _arrivalHandoverFormService.GetArrivalHandoverFormByIdAsync(id);
                return Ok(new
                {
                    code = 0,
                    message = "success",
                    data = form
                });
            }
            catch (Exception ex)
            {
                return Ok(new
                {
                    code = 9999,
                    message = ex.Message
                });
            }
        }

        [HttpGet("get-by-number/{handoverNumber}")]
        public async Task<IActionResult> GetArrivalHandoverFormByNumber(string handoverNumber)
        {
            try
            {
                var form = await _arrivalHandoverFormService.GetArrivalHandoverFormByNumberAsync(handoverNumber);
                return Ok(new
                {
                    code = 0,
                    message = "success",
                    data = form
                });
            }
            catch (Exception ex)
            {
                return Ok(new
                {
                    code = 9999,
                    message = ex.Message
                });
            }
        }

        [HttpGet("list")]
        public async Task<IActionResult> GetArrivalHandoverForms(
            [FromQuery] int page = 1,
            [FromQuery] int pageSize = 10,
            [FromQuery] string sortBy = "CreatedAt",
            [FromQuery] string sortOrder = "desc",
            [FromQuery] string handoverNumber = "",
            [FromQuery] string creator = "")
        {
            try
            {
                var (forms, totalCount) = await _arrivalHandoverFormService.GetArrivalHandoverFormsBatchAsync(
                    page, pageSize, sortBy, sortOrder, handoverNumber, creator);
                return Ok(new
                {
                    code = 0,
                    message = "success",
                    data = new
                    {
                        forms,
                        totalCount
                    }
                });
            }
            catch (Exception ex)
            {
                return Ok(new
                {
                    code = 9999,
                    message = ex.Message
                });
            }
        }

        [HttpPut("update")]
        public async Task<IActionResult> UpdateArrivalHandoverForm([FromBody] ArrivalHandoverFormEntity form)
        {
            try
            {
                form.UpdatedAt = DateTime.UtcNow;
                var result = await _arrivalHandoverFormService.UpdateArrivalHandoverFormAsync(form);
                return Ok(new
                {
                    code = 0,
                    message = "success",
                    data = result
                });
            }
            catch (Exception ex)
            {
                return Ok(new
                {
                    code = 9999,
                    message = ex.Message
                });
            }
        }

        [HttpDelete("delete/{id}")]
        public async Task<IActionResult> DeleteArrivalHandoverForm(int id)
        {
            try
            {
                var result = await _arrivalHandoverFormService.DeleteArrivalHandoverFormAsync(id);
                return Ok(new
                {
                    code = 0,
                    message = "success",
                    data = result
                });
            }
            catch (Exception ex)
            {
                return Ok(new
                {
                    code = 9999,
                    message = ex.Message
                });
            }
        }

        [HttpGet("generate-number")]
        public async Task<IActionResult> GenerateArrivalHandoverNumber()
        {
            try
            {
                var number = await _arrivalHandoverFormService.GenerateArrivalHandoverNumberAsync();
                return Ok(new
                {
                    code = 0,
                    message = "success",
                    data = number
                });
            }
            catch (Exception ex)
            {
                return Ok(new
                {
                    code = 9999,
                    message = ex.Message
                });
            }
        }
    }
}
```

#### 2.5.2 出货交接单控制器 (`ShippingHandoverFormController`)

```csharp
using BLL.Interfaces;
using MDL.Models;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;

namespace CONTROLLER.Controllers
{
    [Route("api/shipping-handover")]
    [ApiController]
    public class ShippingHandoverFormController : ControllerBase
    {
        private readonly IShippingHandoverFormService _shippingHandoverFormService;

        public ShippingHandoverFormController(IShippingHandoverFormService shippingHandoverFormService)
        {
            _shippingHandoverFormService = shippingHandoverFormService;
        }

        [HttpPost("create")]
        public async Task<IActionResult> CreateShippingHandoverForm([FromBody] ShippingHandoverFormEntity form)
        {
            try
            {
                if (string.IsNullOrEmpty(form.HandoverNumber))
                {
                    form.HandoverNumber = await _shippingHandoverFormService.GenerateShippingHandoverNumberAsync();
                }
                form.CreatedAt = DateTime.UtcNow;
                form.UpdatedAt = DateTime.UtcNow;
                var result = await _shippingHandoverFormService.CreateShippingHandoverFormAsync(form);
                return Ok(new
                {
                    code = 0,
                    message = "success",
                    data = result
                });
            }
            catch (Exception ex)
            {
                return Ok(new
                {
                    code = 9999,
                    message = ex.Message
                });
            }
        }

        [HttpGet("get/{id}")]
        public async Task<IActionResult> GetShippingHandoverFormById(int id)
        {
            try
            {
                var form = await _shippingHandoverFormService.GetShippingHandoverFormByIdAsync(id);
                return Ok(new
                {
                    code = 0,
                    message = "success",
                    data = form
                });
            }
            catch (Exception ex)
            {
                return Ok(new
                {
                    code = 9999,
                    message = ex.Message
                });
            }
        }

        [HttpGet("get-by-number/{handoverNumber}")]
        public async Task<IActionResult> GetShippingHandoverFormByNumber(string handoverNumber)
        {
            try
            {
                var form = await _shippingHandoverFormService.GetShippingHandoverFormByNumberAsync(handoverNumber);
                return Ok(new
                {
                    code = 0,
                    message = "success",
                    data = form
                });
            }
            catch (Exception ex)
            {
                return Ok(new
                {
                    code = 9999,
                    message = ex.Message
                });
            }
        }

        [HttpGet("list")]
        public async Task<IActionResult> GetShippingHandoverForms(
            [FromQuery] int page = 1,
            [FromQuery] int pageSize = 10,
            [FromQuery] string sortBy = "CreatedAt",
            [FromQuery] string sortOrder = "desc",
            [FromQuery] string handoverNumber = "",
            [FromQuery] string channel = "",
            [FromQuery] string creator = "")
        {
            try
            {
                var (forms, totalCount) = await _shippingHandoverFormService.GetShippingHandoverFormsBatchAsync(
                    page, pageSize, sortBy, sortOrder, handoverNumber, channel, creator);
                return Ok(new
                {
                    code = 0,
                    message = "success",
                    data = new
                    {
                        forms,
                        totalCount
                    }
                });
            }
            catch (Exception ex)
            {
                return Ok(new
                {
                    code = 9999,
                    message = ex.Message
                });
            }
        }

        [HttpPut("update")]
        public async Task<IActionResult> UpdateShippingHandoverForm([FromBody] ShippingHandoverFormEntity form)
        {
            try
            {
                form.UpdatedAt = DateTime.UtcNow;
                var result = await _shippingHandoverFormService.UpdateShippingHandoverFormAsync(form);
                return Ok(new
                {
                    code = 0,
                    message = "success",
                    data = result
                });
            }
            catch (Exception ex)
            {
                return Ok(new
                {
                    code = 9999,
                    message = ex.Message
                });
            }
        }

        [HttpDelete("delete/{id}")]
        public async Task<IActionResult> DeleteShippingHandoverForm(int id)
        {
            try
            {
                var result = await _shippingHandoverFormService.DeleteShippingHandoverFormAsync(id);
                return Ok(new
                {
                    code = 0,
                    message = "success",
                    data = result
                });
            }
            catch (Exception ex)
            {
                return Ok(new
                {
                    code = 9999,
                    message = ex.Message
                });
            }
        }

        [HttpGet("generate-number")]
        public async Task<IActionResult> GenerateShippingHandoverNumber()
        {
            try
            {
                var number = await _shippingHandoverFormService.GenerateShippingHandoverNumberAsync();
                return Ok(new
                {
                    code = 0,
                    message = "success",
                    data = number
                });
            }
            catch (Exception ex)
            {
                return Ok(new
                {
                    code = 9999,
                    message = ex.Message
                });
            }
        }
    }
}
```

## 3. 数据库SQL脚本

### 3.1 到货交接单表创建脚本

```sql
CREATE TABLE IF NOT EXISTS `arrival_handover_forms` (
  `Id` INT NOT NULL AUTO_INCREMENT,
  `HandoverNumber` VARCHAR(100) NOT NULL,
  `LogisticsProviderArrivalTime` DATETIME NULL,
  `ReceiptTime` DATETIME NULL,
  `POD` TEXT NULL,
  `Remarks` TEXT NULL,
  `Creator` VARCHAR(50) NOT NULL,
  `CreatedAt` DATETIME NOT NULL,
  `UpdatedAt` DATETIME NOT NULL,
  PRIMARY KEY (`Id`),
  UNIQUE INDEX `UQ_HandoverNumber` (`HandoverNumber`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

### 3.2 出货交接单表创建脚本

```sql
CREATE TABLE IF NOT EXISTS `shipping_handover_forms` (
  `Id` INT NOT NULL AUTO_INCREMENT,
  `HandoverNumber` VARCHAR(100) NOT NULL,
  `BigBagCount` INT NOT NULL,
  `SmallBagCount` INT NOT NULL,
  `Channel` VARCHAR(100) NOT NULL,
  `DeliveryTime` DATETIME NULL,
  `POD` TEXT NULL,
  `Remarks` TEXT NULL,
  `Creator` VARCHAR(50) NOT NULL,
  `CreatedAt` DATETIME NOT NULL,
  `UpdatedAt` DATETIME NOT NULL,
  PRIMARY KEY (`Id`),
  UNIQUE INDEX `UQ_HandoverNumber` (`HandoverNumber`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

## 4. 实现计划

1. **创建模型类**：在MDL项目中创建ArrivalHandoverFormEntity和ShippingHandoverFormEntity
2. **创建仓库接口**：在DAL项目中创建IArrivalHandoverFormRepository和IShippingHandoverFormRepository
3. **实现仓库类**：在DAL项目中实现ArrivalHandoverFormRepository和ShippingHandoverFormRepository
4. **创建服务接口**：在BLL项目中创建IArrivalHandoverFormService和IShippingHandoverFormService
5. **实现服务类**：在BLL项目中实现ArrivalHandoverFormService和ShippingHandoverFormService
6. **创建控制器**：在CONTROLLER项目中创建ArrivalHandoverFormController和ShippingHandoverFormController
7. **更新依赖注入**：在Program.cs中注册新的服务和仓库
8. **测试API**：验证所有CRUD操作是否正常工作

## 5. 验收标准

1. 数据库表结构正确创建
2. 所有API接口正常工作
3. 交接单号生成规则正确
4. POD字段支持多个图片链接（用逗号隔开）
5. 分页查询功能正常
6. 所有CRUD操作返回正确的响应格式