# VAT处理任务表 - 迁移指南

## 📋 表名变更总结

由于 `AsyncVATTasks` 表名与其他项目冲突，已将所有任务相关的表重命名为新的表名。

### 表名对应关系

| 旧表名 | 新表名 | 说明 |
|-------|-------|------|
| `AsyncVATTasks` | `EPRHaiyaProcessingTasks` | 主任务表 |
| `AsyncVATTaskLogs` | `VATProcessingTaskLogs` | 任务日志表 |

---

## 🗄️ 新表结构详解

### EPRHaiyaProcessingTasks（主任务表）

**用途：** 存储VAT文档生成的异步任务数据

**字段说明：**

#### 主键和基本信息
- `Id` (INT, PK, IDENTITY)：任务唯一标识
- `EPRBusinessRecordId` (NVARCHAR(255), NOT NULL)：关联的业务记录ID（API 流程 = bizParam.BusinessId）
- `EPRRegInfoId` (NVARCHAR(255), NULL)：关联的 EPRRegInfo 表 ID（source 流程 JOIN 查询写入；API 流程无该记录为 NULL）
- `PushType` (INT, NOT NULL, DEFAULT 301)：流程类型（301=海牙文件生成, 352=030 文件流程）；防重复/重试查询按此过滤
- `BusinessSerialNumber` (NVARCHAR(255), NOT NULL)：业务流水号（INSERT 必须显式传入，无默认值）
- `DataSource` (NVARCHAR(20), NOT NULL, DEFAULT 'SOURCE')：任务来源（SOURCE=原 source 流程从业务表读取；API=public 接口直传）；队列/防重查询按此与 source 流程隔离
- `TaskStatus` (INT, NOT NULL, DEFAULT 0)：任务状态
  - 0: 待处理 (PENDING)
  - 1: 处理中 (PROCESSING)
  - 2: 已完成 (COMPLETED)
  - 3: 失败 (FAILED)
- `Priority` (INT, NOT NULL, DEFAULT 0)：优先级（数值越大优先级越高）

#### 时间戳
- `CreatedAt` (DATETIME, NOT NULL, DEFAULT GETDATE())：创建时间
- `StartedAt` (DATETIME, NULL)：开始处理时间
- `CompletedAt` (DATETIME, NULL)：完成时间
- `UpdatedAt` (DATETIME, NOT NULL, DEFAULT GETDATE())：最后更新时间

#### 错误和结果
- `ErrorMessage` (NVARCHAR(MAX), NULL)：错误信息
- `ResultData` (NVARCHAR(MAX), NULL)：结果数据（JSON格式）
- `TaskData` (NVARCHAR(MAX), NULL)：任务数据（JSON格式）

#### 重试相关
- `RetryCount` (INT, NOT NULL, DEFAULT 0)：当前重试次数
- `MaxRetries` (INT, NOT NULL, DEFAULT 3)：最大重试次数

#### 法人基本信息
- `LegalPersonFullNamePinYin` (NVARCHAR(255), NULL)：法人全名拼音
- `LegalPersonIdNumber` (NVARCHAR(50), NULL)：法人身份证号
- `LegalPersonIDCardType` (NVARCHAR(50), NULL)：法人身份证类型
- `LegalPersonCountry` (NVARCHAR(100), NULL)：法人国籍
- `LegalPersonGender` (NVARCHAR(10), NULL)：法人性别（H:男性, M:女性）
- `LegalPersonBirthDate` (DATE, NULL)：法人出生日期
- `LegalPersonCityEngName` (NVARCHAR(255), NULL)：法人城市（英文）
- `LegalPersonAddressProvinceEn` (NVARCHAR(255), NULL)：法人省份（英文）

#### 公司基本信息
- `CompanyAddressLine1En` (NVARCHAR(255), NULL)：公司地址第一行（英文）
- `CompanyAddressLine2En` (NVARCHAR(255), NULL)：公司地址第二行（英文）
- `CityEngName` (NVARCHAR(255), NULL)：公司城市（英文）
- `CompanyAddressPostcode` (NVARCHAR(50), NULL)：公司邮编
- `CompanyAddressProvinceEn` (NVARCHAR(255), NULL)：公司省份（英文）
- `CompanyCountry` (NVARCHAR(100), NULL)：公司国家
- `CompanyCountryCode` (NVARCHAR(10), NULL)：公司国家代码
- `BusinessCode` (NVARCHAR(100), NULL)：业务编码
- `BusinessMobile` (NVARCHAR(50), NULL)：业务人员（业务顾问）绑定手机号；仅 source 352(030) 流程写入，301 与 API 流程留 NULL
- `CompanyName` (NVARCHAR(255), NULL)：公司中文名称；仅 352(030) 流程写入（source 取 Base_Customer_Company.NameCN，API 取 Data.NameCN），301 流程留 NULL

#### 文件相关
- `LegalSignedFile` (NVARCHAR(MAX), NULL)：法人签名文件
- `Attachment` (NVARCHAR(MAX), NULL)：附件
- `HaiyaFid` (NVARCHAR(255), NULL)：海牙认证文件ID
- `ApoFid` (NVARCHAR(255), NULL)：授权书文件ID
- `CreditReportFilePath` (NVARCHAR(MAX), NULL)：企业信用报告文件路径

#### 其他字段
- `AR` (NVARCHAR(10), NULL)：授权机构（M=原 mokj 模板，O=Onesea 模板）；海牙授权书与 030 文件模板选择共用，030 流程强制必填，四个落库路径（source/API × 301/352）均写入（迁移 `2026_08_18_add_ar.sql`）
- `pdf_result` (INT, NOT NULL, DEFAULT 0)：PDF 处理状态（0=待处理, 1=处理中, 2=处理成功, 3=处理失败；新增数据自动写 0）
- `pdf_result_url` (NVARCHAR(MAX), NULL)：PDF 处理结果 URL

#### 030 文件相关（RPA 流程）
- `pdf030_fid` (NVARCHAR(255), NULL)：030 文件 Fid（与 HaiyaFid/ApoFid 同语义，RPA 流程上传后回填）；RPA 保存新文件时按 EPRBusinessRecordId 清理两套流程旧 030 文件

#### 索引
```sql
INDEX IX_EPRHaiyaProcessingTasks_Status (TaskStatus)
INDEX IX_EPRHaiyaProcessingTasks_VATId (EPRBusinessRecordId)
INDEX IX_EPRHaiyaProcessingTasks_Priority_CreatedAt (Priority DESC, CreatedAt ASC)
INDEX IX_EPRHaiyaProcessingTasks_CreatedAt (CreatedAt)
INDEX IX_EPRHaiyaProcessingTasks_Status_Priority (TaskStatus, Priority DESC, CreatedAt ASC)
INDEX IX_EPRHaiyaProcessingTasks_VATId_Status (EPRBusinessRecordId, TaskStatus)
INDEX IX_EPRHaiyaProcessingTasks_DataSource (DataSource, CreatedAt DESC)
```

---

### VATProcessingTaskLogs（任务日志表）

**用途：** 记录任务处理过程中的日志信息

**字段说明：**
- `Id` (INT, PK, IDENTITY)：日志唯一标识
- `TaskId` (INT, NOT NULL, FK)：关联的任务ID
- `LogLevel` (NVARCHAR(20), NOT NULL)：日志级别（info, warning, error）
- `Message` (NVARCHAR(MAX), NOT NULL)：日志信息
- `CreatedAt` (DATETIME, NOT NULL, DEFAULT GETDATE())：创建时间

**外键约束：**
```sql
CONSTRAINT FK_VATProcessingTaskLogs_TaskId 
    FOREIGN KEY (TaskId) 
    REFERENCES EPRHaiyaProcessingTasks(Id) 
    ON DELETE CASCADE
```

**索引：**
```sql
INDEX IX_VATProcessingTaskLogs_TaskId (TaskId)
INDEX IX_VATProcessingTaskLogs_CreatedAt (CreatedAt)
```

---

## 🔄 迁移步骤

### 步骤1：备份旧数据（可选）

```sql
-- 备份旧表数据
SELECT * INTO AsyncVATTasks_Backup FROM AsyncVATTasks;
SELECT * INTO AsyncVATTaskLogs_Backup FROM AsyncVATTaskLogs;
```

### 步骤2：创建新表

执行 `database/EPRHaiyaProcessingTasks.sql` 脚本：

```bash
sqlcmd -S <server> -d <database> -i database/EPRHaiyaProcessingTasks.sql
```

### 步骤3：迁移数据（如果有旧数据）

```sql
-- 迁移主任务表数据
INSERT INTO EPRHaiyaProcessingTasks (
    EPRBusinessRecordId, TaskStatus, Priority, CreatedAt, StartedAt, CompletedAt,
    ErrorMessage, ResultData, TaskData, RetryCount, MaxRetries,
    LegalPersonFullNamePinYin, LegalPersonIdNumber, LegalPersonIDCardType,
    LegalPersonCountry, LegalPersonGender, LegalPersonBirthDate,
    LegalPersonCityEngName, LegalPersonAddressProvinceEn,
    CompanyAddressLine1En, CompanyAddressLine2En, CityEngName,
    CompanyAddressPostcode, CompanyAddressProvinceEn, CompanyCountry,
    CompanyCountryCode, LegalSignedFile, Attachment, HaiyaFid, ApoFid,
    CreditReportFilePath, pdf_result, pdf_result_url, BusinessCode
)
SELECT
    EPRBusinessRecordId, TaskStatus, Priority, CreatedAt, StartedAt, CompletedAt,
    ErrorMessage, ResultData, TaskData, RetryCount, MaxRetries,
    LegalPersonFullNamePinYin, LegalPersonIdNumber, LegalPersonIDCardType,
    LegalPersonCountry, LegalPersonGender, LegalPersonBirthDate,
    LegalPersonCityEngName, LegalPersonAddressProvinceEn,
    CompanyAddressLine1En, CompanyAddressLine2En, CityEngName,
    CompanyAddressPostcode, CompanyAddressProvinceEn, CompanyCountry,
    CompanyCountryCode, LegalSignedFile, Attachment, HaiyaFid, ApoFid,
    CreditReportFilePath, pdf_result, pdf_result_url, BusinessCode
FROM AsyncVATTasks;

-- 迁移日志表数据
INSERT INTO VATProcessingTaskLogs (TaskId, LogLevel, Message, CreatedAt)
SELECT TaskId, LogLevel, Message, CreatedAt
FROM AsyncVATTaskLogs;
```

### 步骤4：验证数据

```sql
-- 验证主表数据
SELECT COUNT(*) as TotalTasks FROM EPRHaiyaProcessingTasks;

-- 验证日志表数据
SELECT COUNT(*) as TotalLogs FROM VATProcessingTaskLogs;

-- 验证数据一致性
SELECT 
    COUNT(*) as TotalTasks,
    SUM(CASE WHEN TaskStatus = 0 THEN 1 ELSE 0 END) as PendingTasks,
    SUM(CASE WHEN TaskStatus = 1 THEN 1 ELSE 0 END) as ProcessingTasks,
    SUM(CASE WHEN TaskStatus = 2 THEN 1 ELSE 0 END) as CompletedTasks,
    SUM(CASE WHEN TaskStatus = 3 THEN 1 ELSE 0 END) as FailedTasks
FROM EPRHaiyaProcessingTasks;
```

### 步骤5：更新代码

所有代码已自动更新，包括：
- ✅ `src/AsyncTaskManager.php` - 所有SQL查询已更新
- ✅ `task/queue_processor.php` - 所有SQL查询已更新

### 步骤6：删除旧表（可选）

```sql
-- 删除旧表（确保数据已迁移且备份完成）
DROP TABLE AsyncVATTaskLogs;
DROP TABLE AsyncVATTasks;
```

---

## 📊 数据统计查询

### 查看任务统计

```sql
SELECT 
    'EPRHaiyaProcessingTasks' AS TableName,
    COUNT(*) AS TotalRecords,
    SUM(CASE WHEN TaskStatus = 0 THEN 1 ELSE 0 END) AS PendingTasks,
    SUM(CASE WHEN TaskStatus = 1 THEN 1 ELSE 0 END) AS ProcessingTasks,
    SUM(CASE WHEN TaskStatus = 2 THEN 1 ELSE 0 END) AS CompletedTasks,
    SUM(CASE WHEN TaskStatus = 3 THEN 1 ELSE 0 END) AS FailedTasks
FROM EPRHaiyaProcessingTasks;
```

### 查看日志统计

```sql
SELECT 
    'VATProcessingTaskLogs' AS TableName,
    COUNT(*) AS TotalLogs,
    COUNT(DISTINCT TaskId) AS UniqueTasks,
    SUM(CASE WHEN LogLevel = 'info' THEN 1 ELSE 0 END) AS InfoLogs,
    SUM(CASE WHEN LogLevel = 'warning' THEN 1 ELSE 0 END) AS WarningLogs,
    SUM(CASE WHEN LogLevel = 'error' THEN 1 ELSE 0 END) AS ErrorLogs
FROM VATProcessingTaskLogs;
```

### 查看最近的任务

```sql
SELECT TOP 10
    Id, EPRBusinessRecordId, TaskStatus, CreatedAt, StartedAt, CompletedAt,
    RetryCount, ErrorMessage
FROM EPRHaiyaProcessingTasks
ORDER BY CreatedAt DESC;
```

### 查看失败的任务

```sql
SELECT 
    Id, EPRBusinessRecordId, TaskStatus, CreatedAt, CompletedAt,
    RetryCount, MaxRetries, ErrorMessage
FROM EPRHaiyaProcessingTasks
WHERE TaskStatus = 3
ORDER BY CompletedAt DESC;
```

---

## 🔍 代码变更清单

### 修改的文件

| 文件 | 变更 | 说明 |
|------|------|------|
| `src/AsyncTaskManager.php` | 表名替换 | 所有SQL查询中的AsyncVATTasks → EPRHaiyaProcessingTasks |
| `src/AsyncTaskManager.php` | 表名替换 | 所有SQL查询中的AsyncVATTaskLogs → VATProcessingTaskLogs |
| `task/queue_processor.php` | SQL更新 | getPendingVATRecords函数中的表名更新 |

### 变更的SQL语句

**createTask方法：**
```sql
-- 旧
INSERT INTO AsyncVATTasks (...)

-- 新
INSERT INTO EPRHaiyaProcessingTasks (...)
```

**updateTaskStatus方法：**
```sql
-- 旧
UPDATE AsyncVATTasks SET TaskStatus = :status

-- 新
UPDATE EPRHaiyaProcessingTasks SET TaskStatus = :status
```

**getTask方法：**
```sql
-- 旧
SELECT * FROM AsyncVATTasks WHERE Id = :task_id

-- 新
SELECT * FROM EPRHaiyaProcessingTasks WHERE Id = :task_id
```

**getPendingTasks方法：**
```sql
-- 旧
SELECT TOP {$limit} * FROM AsyncVATTasks WHERE TaskStatus = :status

-- 新
SELECT TOP {$limit} * FROM EPRHaiyaProcessingTasks WHERE TaskStatus = :status
```

**addTaskLog方法：**
```sql
-- 旧
INSERT INTO AsyncVATTaskLogs (TaskId, LogLevel, Message)

-- 新
INSERT INTO VATProcessingTaskLogs (TaskId, LogLevel, Message)
```

---

## ✅ 迁移检查清单

- [ ] 备份旧数据（如有）
- [ ] 执行 `database/EPRHaiyaProcessingTasks.sql` 创建新表
- [ ] 验证新表结构
- [ ] 迁移旧数据（如有）
- [ ] 验证数据一致性
- [ ] 更新代码（已自动完成）
- [ ] 测试处理器功能
- [ ] 删除旧表（可选）
- [ ] 更新文档和配置

---

## 🚀 部署步骤

### 1. 数据库初始化

```bash
# 执行建表脚本
sqlcmd -S <server> -d <database> -i database/EPRHaiyaProcessingTasks.sql
```

### 2. 验证表结构

```bash
# 查看新表是否创建成功
sqlcmd -S <server> -d <database> -Q "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'VATProcessing%'"
```

### 3. 启动处理器

```bash
# 测试模式
php task/queue_processor.php

# 生产模式
php task/queue_processor.php --daemon
```

### 4. 监控日志

```bash
# 查看处理器日志
tail -f logs/queue_processor_*.log
```

---

## 📝 常见问题

### Q: 旧表中有数据，如何迁移？

**A:** 按照"迁移步骤"中的步骤3执行SQL迁移脚本。

### Q: 迁移后旧表是否需要删除？

**A:** 建议先备份，验证新表数据完整后再删除旧表。

### Q: 代码是否需要修改？

**A:** 不需要，所有代码已自动更新。

### Q: 如何回滚到旧表？

**A:** 如果有备份，可以恢复备份的旧表，然后恢复旧版本的代码。

### Q: 新表和旧表可以共存吗？

**A:** 可以，但建议最终只保留一个表以避免混淆。

---

## 📞 支持

如有问题，请查看：
- 数据库初始化脚本：`database/EPRHaiyaProcessingTasks.sql`（增量迁移见 `database/migrations/`）
- 处理器文档：`docs/QUEUE_PROCESSOR_README.md`
- 快速参考：`docs/QUICK_REFERENCE.md`

---

**表名迁移指南完成** ✅

所有表名已更新为 `EPRHaiyaProcessingTasks` 和 `VATProcessingTaskLogs`，代码已自动适配。
