﻿# VAT异步任务队列处理器 - 完整指南

## 📋 目录

1. [概述](#概述)
2. [新工作流程](#新工作流程)
3. [快速开始](#快速开始)
4. [详细文档](#详细文档)
5. [常见问题](#常见问题)
6. [支持](#支持)

## 概述

VAT异步任务队列处理器是一个持续运行的PHP脚本，用于处理西班牙（ES）企业增值税（VAT）相关的文档生成任务。

### 主要功能

- ✅ 自动查询待处理的VAT业务记录（301 海牙 + 030 双流程）
- ✅ 自动创建缺失的任务记录
- ✅ 执行异步文档生成
- ✅ 030 流程（PushType=352）：只校验数据字段，任务落库交 RPA 030 程序处理
- ✅ 自动更新推送日期
- ✅ 支持失败重试机制
- ✅ 数据库连接保活
- ✅ 内存监控和告警
- ✅ 详细的日志记录

### 版本信息

- **当前版本：** 3.4
- **PHP版本：** 7.4+
- **修改日期：** 2026-09-02
- **修改类型：** API 流程 OSS 相对路径契约（2026-08-31）+ 结果 COS 键唯一性子目录（2026-09-02，业务流水号）

## 新工作流程

### 流程图

```
┌─────────────────────────────────────────────────────────────┐
│ 启动处理器                                                   │
└────────────────┬────────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────────────┐
│ 查询待处理VAT记录                                            │
│ (EPRRegInfo: Country='ES', PushTaxBureauStatus=5, PushType=301) │
└────────────────┬────────────────────────────────────────────┘
                 │
                 ▼
        ┌────────────────┐
        │ 有待处理记录？  │
        └────┬───────┬──┘
             │       │
            是       否
             │       │
             ▼       ▼
        ┌────────┐  ┌──────────────┐
        │处理    │  │等待下一周期  │
        │记录    │  │(默认8秒)     │
        └────┬───┘  └──────────────┘
             │
             ▼
┌─────────────────────────────────────────────────────────────┐
│ 检查是否已有任务记录                                         │
└────────────────┬────────────────────────────────────────────┘
                 │
        ┌────────┴────────┐
        │                 │
       有                 无
        │                 │
        ▼                 ▼
    ┌────────┐      ┌──────────┐
    │获取    │      │创建新    │
    │任务ID  │      │任务      │
    └────┬───┘      └────┬─────┘
         │               │
         └───────┬───────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────────────┐
│ 执行文档生成                                                 │
└────────────────┬────────────────────────────────────────────┘
                 │
        ┌────────┴────────┐
        │                 │
      成功               失败
        │                 │
        ▼                 ▼
    ┌────────┐      ┌──────────┐
    │更新    │      │检查重试  │
    │推送    │      │次数      │
    │日期    │      └────┬─────┘
    └────┬───┘           │
         │        ┌──────┴──────┐
         │        │             │
         │      可重试        达上限
         │        │             │
         │        ▼             ▼
         │    ┌────────┐    ┌────────┐
         │    │标记为  │    │标记为  │
         │    │待处理  │    │失败    │
         │    └────┬───┘    └────┬───┘
         │         │             │
         └─────┬───┴─────┬───────┘
               │         │
               ▼         ▼
        ┌──────────────────────┐
        │ 更新任务状态         │
        │ 记录日志             │
        └──────────┬───────────┘
                   │
                   ▼
        ┌──────────────────────┐
        │ 继续处理下一个任务   │
        │ 或等待下一周期       │
        └──────────────────────┘
```

### 030 流程（PushType=352，不生成海牙文件）

每轮循环**依次处理：030 流程（352）→ API 海牙任务 → source 301 海牙流程**。030 与 301 的差异：

| 方面 | 301 海牙流程 | 030 流程（352） |
|------|------------|----------------|
| 查询条件 | `PushTaxBureauStatus=5, PushType=301` | `PushTaxBureauStatus=5, PushType=352`（且 `bc.Country IN('中国','香港')`） |
| 文件检查 | 检查营业执照/信用报告等文件 | **只校验数据字段，不做文件检查** |
| 唯一文件逻辑 | 完整文档生成 | 复制签名文件字段值（`LegalSignedFile`）与 `AR`（授权机构，030 模板选择）到任务表 |
| 任务状态 | 生成成功才 COMPLETED | 直接以 `TaskStatus=2` 落库 |
| PushTaxBureauStatus=6 | 由 PHP 文件生成成功后设置 | **PHP 不设置**，由 RPA 030 程序处理完成后设置 |
| 完成标志 | 文件生成结束 | RPA 030 程序回填 `pdf030_fid` |

352 流程任务表写入的关键字段：

- `BusinessSerialNumber` / `EPRRegInfoId` — 业务标识（`BusinessSerialNumber` 为 NOT NULL 无默认值，漏传会导致 INSERT 失败）
- `BusinessMobile` — 业务人员（业务顾问）绑定手机号，来源 `Base_User.F_Mobile`（JOIN `BusinessCounselorId`）
- `CompanyName` — 公司中文名称，来源 `Base_Customer_Company.NameCN`
- `AR` — 授权机构（M/O，强制必填），030 文件模板选择依据（M=原 mokj 模板，O=Onesea 模板），来源 `GeneralTemplateEPR.AR`；301 任务同样写入（301 完成后也由 RPA 030 程序生成 030 文件）
- `PushType=352` — 必须显式传值，否则落库走默认 301

**防重复与「重新生成文件」**：防重复查询带 `AND pdf030_fid IS NULL`，只阻挡「RPA 尚未处理」的任务；`pdf030_fid` 已回填（RPA 处理完成）的任务**不阻挡**，从而支持重建任务（与 301 重新生成行为一致）。

**030 文件只保留最新一份**：RPA 保存 030 文件时（`python/rpa_030_save.py`）按 `EPRBusinessRecordId` 删除同一笔业务下两套流程（301 海牙 / 352 030）的全部旧 `pdf030_fid` 附件，再把各任务 `pdf030_fid` 统一指向最新文件，避免悬空指针。301 与 352 任务 `EPRRegInfoId` 不同但 `EPRBusinessRecordId` 相同，故跨流程清理必须按 `EPRBusinessRecordId` 汇总；同流程待处理任务的取消仍按 `EPRRegInfoId`，避免误伤另一流程进行中的任务。

### API 海牙流程（DataSource=API, PushType=301，公共接口受理的任务）

公共 API（`public/es_hague_api.php`）受理的任务落 `TaskStatus=0(pending), DataSource=API, PushType=301`，由队列消费生成。

**受理阶段（非队列）已完成的文件校验（2026-09-08，对齐 es_haiya）**：香港公司 CR/BR/BR脚码/CR签发人/查册 5 个文件**受理即真实下载**（`downloadFileToLocal`——COS 桶对象优先取新鲜签名 URL `q-sign` 参数，私桶对象直连 403 时签名后可访问，失败报"文件下载失败: <URL>"），生成数据带 `local_path` 供消费端直接复用（文件被清理/跨机器时回退按 `F_FilePath` 重新下载）；非香港公司信用报告受理时真实下载 + `isCreditReportPDF` 内容校验（非有效信用报告 400），校验用临时文件用完即清；身份证/营业执照仍为 URL 轻量探测（生成阶段按需下载）。

1. 每轮查询 `EPRHaiyaProcessingTasks`：`TaskStatus=0 AND DataSource='API' AND PushType=301`（每轮一条）
2. 从 `TaskData.generator_data`（受理时写入的规范化生成数据，含文件描述符/香港公司字段）重建数据，调用 `generateFromCreditReport` 生成海牙合并 PDF + APODERAMIENTO
3. 成功：写 `ResultData`（`file1`=盖章要求（外部静态完整 URL，不变），`file2`=海牙待认证, `file3`=APODERAMIENTO，均为 **OSS 相对路径**（2026-08-31 契约 + 2026-09-02 唯一性子目录：`{API_FLOW_OSS_PREFIX}{当前年}/es_epr_haiya/{业务流水号}/{文件名}`——业务流水号 = bizParam.BusinessSerialNumber（受理必填）兜底 BusinessId，文件名不含唯一因子不加子目录会覆盖同 key；不带域名，与 030 同子目录）；`file4`=030 相对路径由 RPA 生成后经 `python/rpa_030_save.py` merge 回填）→ `TaskStatus=2`（API 流程上传 SaaS 共享桶 usaeu-1259285998，`tencent_cos.api_flow_bucket` 可配；source 流程行为不变），RPA 030 程序按既有规则生成 030 并回填 `pdf030_fid`；API 任务 030 完成后由 `rpa_030_save.py` 调异步结果通知接口通知调用方（成功 `data.files` 文件数组 `[{url,name,type}]`，url=OSS 相对路径；失败 `data=null` 错误原因放 `msg`）
4. 失败：`RetryCount < MaxRetries` 回退 pending 下轮重试；耗尽则 `TaskStatus=3` + 企业微信失败通知（API 流程无业务手机号）+ 异步结果回调（`ES_HAGUE_RESULT_CALLBACK_URL` 默认 test-cloud 回调地址，POST 错误信息给调用方；显式留空禁用）
5. **不读写 source 业务表**（EPRRegInfo/EPRBusinessRecord/Base_AnnexesFile）；不设 PushTaxBureauStatus

与 source 流程的隔离：source 侧任务查询（重试/防重/耗尽标记）均已加 `DataSource='SOURCE'` 过滤，API 任务不会被误当 source 任务处理。**注意：部署新代码前必须重启运行旧代码的 queue_processor**，否则旧查询（无 DataSource 过滤）会把 API pending 任务当 source 重试直至判失败。

### 关键改动

| 方面 | 旧流程 | 新流程 |
|------|-------|-------|
| 状态字段位置 | EPRBusinessRecord表 | EPRRegInfo表 |
| 查询条件 | PushTaxBureauStatus=1 | PushTaxBureauStatus=5, PushType=301/352 |
| 错误消息字段 | PushTaxBureauErrorMsg | Remarks |
| 表关联 | 单表查询 | LEFT JOIN EPRRegInfo |
| 任务创建 | 手动创建 | 自动创建 |
| 推送日期 | 不更新 | 自动更新 |
| 流程类型 | 单一流程 | 301（海牙）+ 352（030）双流程，按 PushType 区分；公共 API 任务按 DataSource='API' 隔离消费 |

## 快速开始

### 1. 数据库准备

```bash
# 执行初始化脚本
sqlcmd -S <server> -d <database> -i database/EPRHaiyaProcessingTasks.sql
```

### 2. 启动处理器

**开发/测试环境：**
```bash
php task/queue_processor.php
```

**生产环境：**
```bash
php task/queue_processor.php --daemon
```

### 3. 监控日志

```bash
# Linux/Mac
tail -f logs/queue_processor_*.log

# Windows PowerShell
Get-Content logs/queue_processor_*.log -Wait
```

### 4. 验证运行

```sql
-- 检查待处理记录
SELECT ID, PushTaxBureauStatus, Remarks
FROM EPRRegInfo
WHERE Country = 'ES' AND PushTaxBureauStatus = 5
LIMIT 10;
```

## 详细文档

### 📖 文档列表

| 文档 | 说明 | 适用场景 |
|------|------|--------|
| [QUICK_REFERENCE.md](QUICK_REFERENCE.md) | 常用命令速查 | 快速上手 |
| [TESTING_CHECKLIST.md](TESTING_CHECKLIST.md) | 测试检查清单 | 测试验证 |
| [IMPLEMENTATION_SUMMARY.md](IMPLEMENTATION_SUMMARY.md) | 实现总结 | 技术细节 |

### 🔧 配置参数

```bash
php task/queue_processor.php [选项]

选项：
  --interval=30     检查间隔（秒），默认8秒
  --max-tasks=5     每次最多处理的任务数，默认1个
  --daemon          以守护进程模式运行（静默模式）
  --help            显示帮助信息

示例：
  php task/queue_processor.php
  php task/queue_processor.php --interval=60 --max-tasks=3
  php task/queue_processor.php --daemon
```

### 📊 监控指标

#### 心跳检测（每60秒）
```
[2026-03-19 10:30:45] ❤️ 心跳 - 运行时间: 3600秒, 已处理: 45个任务, 内存: 256.50MB
```

#### 内存警告（超过800MB）
```
[2026-03-19 10:35:20] ⚠️ 警告: 内存使用过高 850.25MB
```

#### 数据库保活（每5分钟）
```
[2026-03-19 10:40:00] 数据库连接保活成功
```

### 🗄️ 数据库字段

#### EPRRegInfo 表

| 字段 | 类型 | 说明 | 示例 |
|------|------|------|------|
| PushTaxBureauStatus | INT | 推送状态 | 5 |
| Remarks | NVARCHAR | 错误消息/备注 | |

#### EPRHaiyaProcessingTasks 表

| 状态值 | 状态名 | 说明 |
|-------|-------|------|
| 0 | PENDING | 待处理 |
| 1 | PROCESSING | 处理中 |
| 2 | COMPLETED | 已完成 |
| 3 | FAILED | 失败 |
| 4 | CANCELLED | 已取消 |

关键字段（352 流程相关）：

| 字段 | 类型 | 说明 |
|------|------|------|
| PushType | INT | 流程类型：301=海牙流程，352=030流程 |
| pdf030_fid | NVARCHAR(255) | 030 文件 Fid，由 RPA 030 程序回填；防重复只挡该字段为 NULL 的任务 |
| BusinessMobile | NVARCHAR(50) | 业务人员绑定手机号（仅 352 流程写入，301 留 NULL） |
| CompanyName | NVARCHAR(255) | 公司中文名称（仅 352 流程写入，301 留 NULL） |
| AR | NVARCHAR(10) | 授权机构：M=原 mokj 模板，O=Onesea 模板（海牙授权书与 030 文件模板选择共用；source/API × 301/352 四路径都写） |
| EPRRegInfoId | NVARCHAR(255) | 关联的 EPRRegInfo 表 ID |
| BusinessSerialNumber | NVARCHAR | 业务流水号（NOT NULL 无默认值，INSERT 必须传） |

### 📝 日志格式

日志文件位置：`logs/queue_processor_YYYYMMDD.log`

```
[2026-03-19 10:00:00] INFO: 任务队列处理器启动
[2026-03-19 10:00:05] INFO: 查询待处理VAT记录 - count: 3
[2026-03-19 10:00:06] INFO: 开始处理VAT记录 - vat_id: 123
[2026-03-19 10:00:07] INFO: 创建新任务 - vat_id: 123, task_id: 456
[2026-03-19 10:00:08] INFO: 开始处理任务 - task_id: 456, vat_id: 123
[2026-03-19 10:00:30] INFO: 任务处理完成 - task_id: 456
[2026-03-19 10:00:31] INFO: 更新推送日期成功 - vat_id: 123, push_date: 2026-03-19
```

## 常见问题

### Q: 处理器如何启动？
**A:** 
```bash
# 交互模式
php task/queue_processor.php

# 守护进程模式
php task/queue_processor.php --daemon

# 自定义参数
php task/queue_processor.php --interval=60 --max-tasks=3
```

### Q: 如何停止处理器？
**A:**
- 交互模式：按 `Ctrl+C`
- 守护进程模式：`kill <pid>` 或 `taskkill /PID <pid>`

### Q: 日志在哪里？
**A:** `logs/queue_processor_YYYYMMDD.log`

### Q: 如何监控处理器状态？
**A:**
```bash
# 查看实时日志
tail -f logs/queue_processor_*.log

# 查看进程
ps aux | grep queue_processor

# 查看内存使用
top -p <pid>
```

### Q: 为什么没有找到待处理记录？
**A:** 检查以下条件：
1. EPRRegInfo 表中是否有 `PushTaxBureauStatus = 5` 的记录
2. 是否已有对应的任务处于处理中或已完成状态
3. 数据库连接是否正常

### Q: 推送日期没有更新怎么办？
**A:** 检查以下项：
1. 文件生成是否真的成功（查看日志）
2. 任务状态是否为 COMPLETED
3. 是否有权限更新 EPRRegInfo 表

### Q: 如何调整处理速度？
**A:**
```bash
# 增加处理速度
php task/queue_processor.php --interval=10 --max-tasks=5

# 降低资源占用
php task/queue_processor.php --interval=60 --max-tasks=1
```

### Q: 内存持续增长怎么办？
**A:**
1. 检查日志中的内存警告
2. 可能需要增加服务器内存
3. 或调整 `--max-tasks` 参数
4. 定期重启处理器

### Q: 如何处理失败的任务？
**A:** 处理器会自动重试，达到最大重试次数后标记为失败。可以：
1. 修复问题后手动更新任务状态为 PENDING
2. 或删除失败的任务记录重新处理

### Q: 香港公司报"缺少查册文件"怎么办？
**A:** SpecsName=1/2（包装法/包装法+VAT）时查册文件必填，但下载/检测/解析任一一环瞬时失败也可能伪装成此错误。先跑 `php scripts/diagnose_missing_particulars.php [订单号]`（只读，复现全链路+时间线）确认断在哪环；证据不足时重推（EPRRegInfo status 回 5）即可。2026-09-07 起三环节均记失败日志（可查生产日志定位）。

## 支持

### 获取帮助

1. **查看文档**
   - 快速参考：[QUICK_REFERENCE.md](QUICK_REFERENCE.md)
   - 测试清单：[TESTING_CHECKLIST.md](TESTING_CHECKLIST.md)

2. **查看日志**
   ```bash
   tail -f logs/queue_processor_*.log
   ```

3. **检查数据库**
   ```sql
   -- 查看待处理记录
   SELECT * FROM EPRRegInfo WHERE Country = 'ES' AND PushTaxBureauStatus = 5;
   
   -- 查看任务状态
   SELECT * FROM EPRHaiyaProcessingTasks 
   ORDER BY CreatedDate DESC;
   ```

### 报告问题

提供以下信息：
1. 错误日志内容
2. 数据库查询结果
3. 处理器启动参数
4. 系统环境信息

### 性能优化

根据实际情况调整参数：

| 场景 | 推荐参数 |
|------|--------|
| 高吞吐量 | `--interval=10 --max-tasks=5` |
| 低资源占用 | `--interval=60 --max-tasks=1` |
| 平衡 | `--interval=30 --max-tasks=2` |

## 版本历史

### v3.4 - 2026-09-02
- 🔄 API 流程生成结果统一 **OSS 相对路径契约**（2026-08-31，对齐 es_haiya）：ResultData.file2/file3、cos_key/cos_url 与结果通知 files.url 均用相对路径，file1 盖章要求为完整 URL（例外）；python/rpa_030_save.py 移除上传签名
- ✨ API 流程生成结果 COS 键追加唯一性子目录：`{api_flow_oss_prefix}{当前年}/es_epr_haiya/{业务流水号}/{文件名}`——业务流水号 = bizParam.BusinessSerialNumber（受理必填）、PHP 兜底 BusinessId、python 030 兜底任务ID；`src/functions.php` 新增 `sanitize_business_code` 清洗目录名；海牙/APODERAMIENTO/030 同子目录，防同名文件覆盖

### v3.3 - 2026-08-18
- ✨ 030 文件增加 Onesea 模板：EPRHaiyaProcessingTasks 新增 `AR` 列（迁移 2026_08_18_add_ar），030 流程 AR 强制必填（M/O），四个落库路径（source/API × 301/352）都写 AR，RPA 030 程序经 rpa_030_get.py 读取 AR 选模板

### v3.2 - 2026-08-10
- 🐛 030文件保存(rpa_030_save.py)改为按 EPRBusinessRecordId 清理 301/352 两套流程的旧 030 文件，只保留最新一份；其他任务 pdf030_fid 统一指向最新文件避免悬空

### v3.1 - 2026-08-08
- ✨ 新增 030 流程（PushType=352）：只校验数据字段，复制签名文件字段到任务表，任务直接以 TaskStatus=2 落库交 RPA 030 程序处理
- ✨ EPRHaiyaProcessingTasks 新增 `BusinessMobile`（业务手机号）、`CompanyName`（公司中文名称）列（仅 352 流程写入）
- 🐛 修复 352 任务落库失败：create030Task 补传 BusinessSerialNumber/EPRRegInfoId
- 🐛 防重复改为仅挡 `pdf030_fid IS NULL` 的任务，支持「重新生成文件」重建任务

### v3.0 - 2026-04-20
- ✨ 状态字段迁移到 EPRRegInfo 表（PushTaxBureauStatus、Remarks）

### v2.0 - 2026-03-19
- ✨ 重构工作流程：从拉取模式改为推送模式
- ✨ 新增自动任务创建功能
- ✨ 新增推送日期自动更新功能
- ✨ 改进数据库连接保活机制
- ✨ 增强内存监控和告警
- 📚 完整的文档和测试清单

### v1.0 - 旧版本
- 从 task 表拉取任务
- 手动创建任务记录
- 基础的错误处理和重试机制

## 许可证

内部使用

## 更新日志

### 2026-09-03
- `ES_HAGUE_RESULT_CALLBACK_URL` 默认回调地址：`.env` 未配置此键时生效 `https://test-cloud.usaeu.com/prod-api/delivery/rpa/callback`（显式留空禁用）
- 030 防重隔离加固：`process030Flow` 只挡 `DataSource='SOURCE'`、API 侧 `findUnprocessed030TaskByBusinessId` 对称只挡 `DataSource='API'`（两套流程同业务记录不再互相阻挡/重复生成）；`getOldTaskAttachment` 只取 source 任务
- 海牙盖章要求附件改「先插入新记录、成功后再删除旧记录」（排除新 F_Id）；插入失败保留旧记录，避免附件永久丢失（此前为删旧插新，插入失败会丢记录）
- 信用报告检测器解析容错：`OCRDataProcessor::isCreditReportPDF` 取最后一行 `{` 前缀 JSON 解析、解析失败打印完整原始输出；`credit_report_detector.py` 导入第三方库前 `warnings.filterwarnings('ignore')`

### 2026-09-02
- API 流程生成结果 COS 键追加唯一性子目录（对齐 es_haiya）：`{api_flow_oss_prefix}{当前年}/es_epr_haiya/{业务流水号}/{文件名}`——业务流水号 = bizParam.BusinessSerialNumber（受理必填）、PHP 兜底 BusinessId、python 030 侧兜底任务ID；`src/functions.php` 新增 `sanitize_business_code` 清洗目录名；海牙/APODERAMIENTO/030 同子目录

### 2026-08-31
- API 流程生成结果改 **OSS 相对路径契约**（不带域名，域名由对方拼接）：ResultData.file2/file3、cos_key/cos_url、pdf030_result_url 与结果通知 files.url 均用相对路径；file1 盖章要求为外部静态完整 URL（例外）；`python/rpa_030_save.py` 移除上传签名

### 2026-08-18
- EPRHaiyaProcessingTasks 新增 AR 列（M/O，授权机构，030 文件模板选择；迁移 2026_08_18_add_ar）
- 030 流程 AR 强制必填（source validate030ExtendedFields / API validateArField），301 与 030 共用
- 四个落库路径写 AR：createTask(source 301) / create030Task(source 030) / EprApiService::buildTaskFieldData(API 301/352) / updateAsyncVATTaskFields(自愈旧任务)
- rpa_030_get.py：AR 加入必填字段检查，process_ar 归一化大写后交 RPA 选模板

### 2026-08-10
- 030文件保存改为按 EPRBusinessRecordId 清理两套流程旧文件，只保留最新一份
- rpa_030_save.py 纳入版本管理

### 2026-08-08
- 新增 030 流程任务字段：BusinessMobile（业务手机号）、CompanyName（公司中文名称）
- 修复 352 任务落库失败：补传 BusinessSerialNumber/EPRRegInfoId
- 防重复改为仅挡 pdf030_fid IS NULL 的任务，支持「重新生成文件」

### 2026-03-19
- 完成工作流程重构
- 添加完整文档
- 创建测试清单
- 优化日志记录

---

**最后更新：** 2026-09-03  
**维护者：** 开发团队  
**联系方式：** [联系信息]
