# 预销售单配货记录方案（需求背景与决策）

生成日期：2026-02-02

## 一、需求背景

业务场景为“预销售单配货记录”。预销售单已存在且包含多个明细（PreSalesOrderItem）。配货行为需要被记录，且允许同一明细多次配货（拆分执行、分批扫码）。配货记录的核心价值是对现实行为的如实记录与进度汇总，而非强约束生产流程。

## 二、已确认的关键决策

### 1. 数据关系
- PreSalesOrder 1 — N PreSalesOrderItem
- PreSalesOrderItem 1 — N AllocationRecord（配货记录）
- 外键在 AllocationRecord 侧

### 2. 模型命名
- 配货记录模型英文名：AllocationRecord

### 3. 配货记录字段设计（核心）
- pre_sales_order_item（FK）
- stock_ids（库存明细 ID 数组）
  - 这些 ID 对应 stock.StockChangeDetail
  - 由于系统已有一致做法（consume_detail_ids 为字符串存储），建议使用字符串持久化，展示层转换为 List[int]
- quantity（decimal，执行数量）
- unit（string）
- status（int，1=有效，2=撤销）
- scanned_by（Employee）
- scanned_at（datetime）
- remarks（可选）

### 4. 派生字段（必须是计算字段，不落库）
- allocated_quantity = Σ AllocationRecord.quantity（仅统计 status=有效）
- required_quantity = PreSalesOrderItem.quantity
- progress = allocated_quantity / required_quantity
- allocation_status（计算状态）
  - pending：allocated_quantity == 0
  - allocating：0 < allocated_quantity < required_quantity
  - completed：allocated_quantity >= required_quantity

### 5. 业务规则
- 允许超配：配货记录是事实记录，不限制现实生产过程。
- 允许撤销：撤销后不计入配货进度。
- stock_ids 的合法性与库存状态校验必须在 service 层完成：
  - 必须存在
  - 未冻结/锁定/消费
  - 不允许将校验逻辑耦合在 model 层
  - 必须通过独立函数实现，避免重复代码

### 6. 排除项（当前阶段不做）
- 任务指派与接收功能不做（前端想法不成熟）
- 任务主表（TaskAllocation）暂不落地
- 转销售单属于下一阶段任务
- 价格由前端在转销售单时传入，目前不考虑

## 三、与前端需求的匹配情况

当前方案满足前端“扫码录入、按明细多次执行、进度汇总”的核心需求。
暂不覆盖“任务指派、接收、完成、转单”流程，此部分已明确不在本阶段实施。

## 四、实施计划（仅规划，不含代码）

1. 新增 AllocationRecord 模型
   - 外键到 PreSalesOrderItem
   - stock_ids 使用字符串持久化（与 consume_detail_ids 一致）
2. 在 service 层新增库存明细校验函数
   - 校验 stock_ids 存在且未冻结/锁定/消费
   - 作为通用工具复用，避免重复逻辑
3. 新增配货记录创建与撤销流程（service）
   - 允许超配
   - 撤销不计入进度
4. 在 API/Serializer 层增加派生字段输出
   - allocated_quantity、progress、allocation_status
5. 补充相关测试
   - stock_ids 校验
   - 进度计算与状态派生
   - 撤销与超配逻辑

## 五、实施逻辑细节（service 层校验）

### 1. 校验库存明细存在
- 传入 stock_ids（数组）后，先做整数化与正整数校验
- 使用 `StockChangeDetail` 按 `id__in` + `merchant` 拉取
- 若有缺失 ID，直接报错并终止

### 2. 校验“未被消费”
- 依据 stock 模块字段 `StockChangeDetail.is_consumed`
- 若任一明细 `is_consumed=True`，视为已消费，拒绝写入配货记录

### 3. 校验“未冻结/未锁定”
- 查询 `StockFreeze`，条件：
   - `stock_detail_id__in = stock_ids`
   - `status = StockFreezeStatusEnum.FROZEN`
- 若存在冻结记录，拒绝写入配货记录

### 4. 约束校验位置
- 上述校验必须在 **service 层独立函数** 中完成
- 不写入 model 层，不引入重复代码

## 六、已实现的配货记录 API

### 6.1 创建配货记录
**接口路径：** `POST /api/v1/pre-sales-order-items/{item_id}/allocations/`

**请求参数（JSON Body）：**
```json
{
  "stock_ids": [1, 2, 3],  // 库存明细ID数组（必填）
  "quantity": "10.00",     // 配货数量（必填）
  "unit": "KG",            // 单位（必填）
  "remarks": "备注信息"     // 备注（可选）
}
```

**响应数据（201 Created）：**
```json
{
  "id": 123,
  "pre_sales_order_item": 456,
  "stock_ids": [1, 2, 3],
  "quantity": "10.00",
  "unit": "KG",
  "status": 1,  // 1=有效，2=撤销
  "scanned_by": 78,
  "scanned_by_name": "张三",
  "scanned_at": "2026-02-02T10:30:00Z",
  "remarks": "备注信息",
  "created_at": "2026-02-02T10:30:00Z"
}
```

**业务逻辑：**
- 自动校验 stock_ids 存在、未消费、未冻结
- 自动校验产品匹配（库存明细产品必须与预销售单明细产品一致）
- 创建配货记录的同时自动冻结对应的库存明细（在同一事务中完成）
- 自动记录扫码员工与扫码时间

**错误响应（400 Bad Request）：**
```json
{
  "error": "库存明细 ID [1, 2] 不存在"
}
```
或
```json
{
  "error": "库存明细 ID [3] 已被冻结"
}
```

### 6.2 撤销配货记录
**接口路径：** `DELETE /api/v1/pre-sales-order-items/{item_id}/allocations/{pk}/`

**响应数据（200 OK）：**
```json
{
  "id": 123,
  "pre_sales_order_item": 456,
  "stock_ids": [1, 2, 3],
  "quantity": "10.00",
  "unit": "KG",
  "status": 2,  // 已撤销
  "scanned_by": 78,
  "scanned_by_name": "张三",
  "scanned_at": "2026-02-02T10:30:00Z",
  "remarks": "备注信息",
  "created_at": "2026-02-02T10:30:00Z"
}
```

**业务逻辑：**
- 将配货记录状态置为"撤销"（status=2）
- 撤销的记录不计入配货进度统计
- 不删除记录，保留审计痕迹

**错误响应（404 Not Found）：**
```json
{
  "error": "配货记录不存在"
}
```

### 6.3 预销售单明细新增字段（查询时自动返回）

当查询预销售单详情（`GET /api/v1/pre-sales-orders/{pk}/`）时，明细数据会自动包含以下派生字段：

```json
{
  "items": [
    {
      "id": 456,
      "product": 789,
      "quantity": "100.00",
      "unit": "KG",
      // ... 其他字段 ...
      
      // 新增：配货记录列表
      "allocation_records": [
        {
          "id": 123,
          "stock_ids": [1, 2, 3],
          "quantity": "10.00",
          "status": 1,
          // ... 完整记录 ...
        }
      ],
      
      // 新增：已配货数量（仅统计 status=1 的记录）
      "allocated_quantity": "25.50",
      
      // 新增：配货进度（0.0-1.0）
      "progress": "0.255",
      
      // 新增：配货状态（计算字段，不落库）
      "allocation_status": 2  // 1=pending(0%), 2=allocating(0%-100%), 3=completed(>=100%)
    }
  ]
}
```

**字段说明：**
- `allocation_records`：该明细的所有配货记录（包括撤销的）
- `allocated_quantity`：已配货总量（仅统计 status=1 的有效记录）
- `progress`：配货进度 = allocated_quantity / quantity
- `allocation_status`：配货状态（派生字段）
  - `1` = pending：尚未配货（allocated_quantity = 0）
  - `2` = allocating：配货中（0 < allocated_quantity < quantity）
  - `3` = completed：已完成（allocated_quantity >= quantity，允许超配）

## 七、待后续阶段处理

- 转销售单流程（确认并转单）
- 任务指派/接收/完成等任务主表流程
- 撤销配货记录时的库存解冻逻辑（当前仅标记撤销，未解冻）
