# 实施计划：LEKO/CITEO API 文件生成

## 概述

以 PHP 8.2、Laravel 11 和 PHPUnit 10 实施法国统一同步文件生成 API。任务按契约与 DTO、传输入口、INSEE 解析、LEKO/CITEO 纯生成器、统一编排、安全隔离和回归验收的顺序推进；API_Flow 只消费请求业务字段，对 Source_DB 零读写，但法国 LEKO 在可派生 9 位 SIREN 时必须调用现有 INSEE 官方客户端。所有测试任务均为交付必需项。

## 任务

- [ ] 1. 建立请求契约、DTO 与可重复测试基础
  - [ ] 1.1 接入 PHP 属性测试基础设施
    - 在 `composer.json` 中以精确版本加入兼容 PHP 8.2/PHPUnit 10 的 `giorgiosironi/eris`，配置至少 100 次样本执行和统一 Property 注释格式。
    - 建立国家码、Unicode、嵌套 bizParam、注册号和多错误请求生成器，供后续属性测试复用。
    - _需求：3.1-3.9、4.1-4.10、6.1-6.5、7.1-7.4、8.1-8.5、9.1-9.4_

  - [ ] 1.2 实现统一信封 DTO、PushType 枚举和 Mapper
    - 创建只读 `FrenchFileGenerationInput`、`InseeData` 和法国 PushType 类型，保留完整原始 bizParam，并只读提取 LEKO 的 BusinessSerialNumber。
    - Mapper 仅执行 trim、国家码大写及省份默认映射，不读取或映射 `CompanyAddressLine2En`、`InseeLegalForm`、`InseeNafCode`、`InseeSiret`。
    - 共享现有欧盟国家码定义，避免 LEKO、CITEO 和旧流程规则漂移。
    - _需求：1.1、1.4、1.5、3.5、3.7-3.9、6.4、7.3_

  - [ ] 1.3 实现按 PushType 分支的聚合请求校验
    - 创建统一 FormRequest/Validator，校验 `PushType`、`Country`、对象型 `Data`、对象型 `bizParam`，并一次返回全部字段错误。
    - LEKO 仅固定要求十一项 Data 字段和 `bizParam.BusinessSerialNumber`，叠加 CN/FR/EU 条件规则及 50/100 字符上限。
    - CITEO 固定要求五项 Data 字段，EU 且非 FR 要求 VATNumber，其余要求 RegNumber；允许空 bizParam，不要求 BusinessSerialNumber、BusinessId 或 Businessld。
    - 拒绝国家前缀不一致以及仅含 Id/EPRRegInfoId 的新 API 请求，且不得触发 Repository。
    - _需求：1.1、1.4-1.6、3.1-3.6、3.9、6.1-6.4、6.6、8.1_

  - [ ] 1.4 编写属性测试：按流程的信封与 bizParam 判定
    - **Property 1：按流程的信封与 bizParam 判定**
    - 覆盖公共四字段类型、国家/PushType 一致性、LEKO 流水号和 CITEO 任意对象型 bizParam。
    - **验证：需求 1.1、1.4、1.5、1.6**

  - [ ] 1.5 编写属性测试：LEKO 条件校验模型
    - **Property 3：LEKO 条件校验模型**
    - 对照参考模型验证十一项固定字段及 CN、FR、EU 条件字段的完整错误集合。
    - **验证：需求 3.1、3.2、3.3、3.4**

  - [ ] 1.6 编写属性测试：未消费字段不改变契约
    - **Property 4：未消费字段不改变契约**
    - 随机增删或修改 CompanyAddressLine2En、三个 Insee 输入及其他未消费字段，断言校验和生成输入不变，伪造 Insee 值不能覆盖官方值。
    - **验证：需求 3.5、3.9、6.4**

  - [ ] 1.7 编写属性测试：LEKO 长度约束
    - **Property 5：LEKO 长度约束**
    - 使用 Unicode 字符串覆盖 50/51 和 100/101 字符边界，断言错误包含字段名和上限。
    - **验证：需求 3.6**

  - [ ] 1.8 编写属性测试：省份默认映射
    - **Property 6：省份默认映射**
    - 验证非空 AreaName 原样保留、HK 强制为“香港”、其他非 CN/HK 空值映射为大写国家码。
    - **验证：需求 3.7、3.8**

  - [ ] 1.9 编写属性测试：CITEO 条件校验和执照号
    - **Property 12：CITEO 条件校验和执照号**
    - 覆盖全部 EU/非 EU 国家，验证固定字段、VATNumber/RegNumber 条件和 BusinessLicenseNo 选择。
    - **验证：需求 6.1、6.2、6.3**

  - [ ] 1.10 编写属性测试：聚合校验错误
    - **Property 15：聚合校验错误**
    - 为同时存在缺失、类型、国家条件和长度违规的请求断言单次 400 包含参考模型的全部错误键。
    - **验证：需求 8.1**

  - [ ] 1.11 编写请求校验与 Mapper 单元边界测试
    - 逐项删除 LEKO/CITEO 最小字段；覆盖 CITEO 空对象 bizParam、LEKO 字符串/整数流水号、国家不一致及仅 Id/EPRRegInfoId 请求。
    - 明确断言三个 Insee 字段和 CompanyAddressLine2En 非必填、非消费字段，覆盖 CN/HK/其他国家省份映射。
    - _需求：1.1-1.6、3.1-3.9、6.1-6.4、6.6、8.1_

- [ ] 2. 实现统一路由与传输中间件
  - [ ] 2.1 创建法国文件生成控制器和 POST 路由
    - 在 `routes/api.php` 注册 `POST /api/epr/fr/file-generation`，由控制器按白名单精确分派 LEKO/CITEO，禁止动态类名或方法调用。
    - 绑定统一请求校验和后续编排服务，不修改旧 `/api/epr/generate-citeo-poa` 路由。
    - _需求：1.2、1.3、9.1_

  - [ ] 2.2 实现 JSON、请求体大小、可选 Bearer 和限流中间件
    - 仅接受 `application/json`；无效 JSON、空体和超限统一返回 HTTP/code 400，非 POST 维持 405。
    - 按配置启用 Bearer 精确匹配，认证失败返回 HTTP/code 401；令牌、大小和限流阈值只从服务端配置读取。
    - 配置法国端点限流并统一返回 HTTP/code 429；避免复用会记录请求正文或落盘敏感内容的 `ParseLargeJsonBody` 行为。
    - 在 `bootstrap/app.php` 注册必要别名，并补充 `config/services.php`/`.env.example` 的非秘密配置键。
    - _需求：8.5、9.1-9.4_

  - [ ] 2.3 编写属性测试：方法、媒体类型与认证
    - **Property 19：方法、媒体类型与认证**
    - 组合请求方法、Content-Type、认证开关和 Authorization，验证只有允许组合进入业务校验。
    - **验证：需求 9.1、9.2**

  - [ ] 2.4 编写属性测试：限流窗口
    - **Property 20：限流窗口**
    - 对任意正整数阈值 N 验证窗口前 N 次放行、第 N+1 次起 429、窗口重置后重新放行。
    - **验证：需求 9.4**

  - [ ] 2.5 编写路由和传输边界测试
    - 覆盖精确路由分派、错误 PushType/国家组合、JSON 媒体类型、请求体 N/N+1 字节、Bearer 开关、401 的统一 JSON 和 429 响应。
    - 断言传输层拒绝的请求不调用编排器、INSEE、生成器、OSS 或 Repository。
    - _需求：1.2、1.3、1.6、9.1-9.4_

- [ ] 3. 抽取并复用 INSEE Resolver 与重试策略
  - [ ] 3.1 实现可测试的 LekoInseeResolver
    - 从 CountryTwoCode 和 RegNumber 派生查询决策：仅 FR、去除非数字、取前 9 位且长度恰为 9 时调用现有 `InseeApiClient`。
    - 非 FR 或无法派生 9 位 SIREN 时返回与旧 `fetchInseeData` 一致的空 INSEE_Data，不调用 HTTP。
    - 注入重试器/Sleeper，固定最多 5 次、相邻失败等待 6 秒；最终失败转换为可分类业务异常，不产生数据库状态、通知或文件副作用。
    - _需求：2.4、4.1、4.2、4.8-4.10、8.3_

  - [ ] 3.2 固化 InseeApiClient 官方端点和映射兼容性
    - 保持 SIRENE 3.11 SIREN/SIRET URL、`services.insee.api_key` 和 `X-INSEE-Api-Key-Integration` 请求头。
    - 保持首个期间、法律形式表、NAF 去点、SIREN+NIC、总部地址、空/未知法律形式、缺失活动代码/NIC 和 HTTP 非 200 异常语义。
    - 仅做支撑 Resolver 复用所需的最小可测试重构，不改变 Source_Flow 客户端行为。
    - _需求：4.3-4.8_

  - [ ] 3.3 编写属性测试：INSEE 查询决策与 SIREN 键
    - **Property 7：INSEE 查询决策与 SIREN 键**
    - 生成非 FR、带标点/字母及 0/8/9/10+ 位数字的 RegNumber，断言调用条件、次数和准确 SIREN。
    - **验证：需求 2.4、4.1、4.2、4.9、4.10**

  - [ ] 3.4 编写属性测试：INSEE 响应映射
    - **Property 8：INSEE 响应映射**
    - 随机生成结构合法或字段缺失的首个期间，验证法律形式、NAF、SIRET 和有/无 NIC 时的 SIRET 查询次数及默认值。
    - **验证：需求 4.4、4.5、4.6、4.7**

  - [ ] 3.5 编写属性测试：INSEE 重试与安全失败
    - **Property 9：INSEE 重试与安全失败**
    - 对连续失败序列断言最多 5 次调用、前四次各等待 6 秒、最终不调用生成器/OSS/Source 组件，公开错误不含 Key 或内部端点。
    - **验证：需求 4.8、8.3**

  - [ ] 3.6 扩展 INSEE 单元与 HTTP fake 测试
    - 覆盖准确 URL/header、已知/未知/空法律形式、NAF 去点、NIC 有无、总部地址和非 200 消息。
    - 使用 fake Sleeper 覆盖第 1-5 次成功及连续 5 次失败，不真实等待；确保非 FR/无效 SIREN 零 HTTP 调用。
    - _需求：4.1-4.10、8.3_

- [ ] 4. 实现 LEKO 纯 API 文件生成流程
  - [ ] 4.1 创建 LekoApiFileGenerator 并复用现有生成组件
    - 接收请求 Data、服务端 InseeData 和 BusinessSerialNumber，组装仅供生成器使用的记录对象及 `_insee`。
    - 复用 `XlsxGenerator`、`DocxGenerator`、`PdfConverter` 和 `OssUploader` 生成 XLSX 与 LEKO POA；J/L/O 只能读取服务端 `_insee`，空值沿用 `Autre / Other`、空 NAF、空 SIRET。
    - 保持 A:AJ 固定值、当前年份、随机范围、法式小数、城市后缀、日期和签名语义，并保证所有成功/失败路径清理临时文件。
    - _需求：3.5、5.1-5.4、8.2_

  - [ ] 4.2 实现 LEKO OSS 上传与结果对象
    - 使用净化后的公司名生成 `POA-{NameEng}.pdf`，保持现有 LEKO XLSX 文件名和基于 BusinessSerialNumber 的受控 OSS 目录。
    - 仅在两个文件均生成成功后上传；返回 XLSX、POA 两个完整 HTTP(S) URL，任一上传失败整体失败且不返回部分 URL。
    - _需求：1.4、5.5、7.1、7.4、8.2_

  - [ ] 4.3 编写属性测试：LEKO XLSX 参考模型
    - **Property 10：LEKO XLSX 参考模型**
    - 注入时钟和随机源，对照当前 A:AJ 参考模型验证全部业务列；断言 J/L/O 仅取服务端 INSEE 数据或现有默认值。
    - **验证：需求 5.2、5.3**

  - [ ] 4.4 编写属性测试：LEKO POA 与上传契约
    - **Property 11：LEKO POA 与上传契约**
    - 验证 POA 文本、日期、同一法人签名，以及恰上传一个 `.xlsx` 和一个净化名称的 POA PDF。
    - **验证：需求 5.4、5.5**

  - [ ] 4.5 编写 LEKO 生成器单元与真实模板集成测试
    - 使用 `Leko_Template.xlsx` 检查 A:AJ 关键单元格和 J/L/O 官方值覆盖伪造请求 Insee 字段，检查空 INSEE 默认值。
    - 使用 `Leko_Template.docx` 检查 XML、media、日期、城市后缀和签名；验证 PDF 转换及两个 OSS fake URL。
    - 注入模板、DOCX、PDF、OSS 故障，断言临时文件清理和无部分成功响应。
    - _需求：5.1-5.5、7.1、7.4、8.2_

- [ ] 5. 抽取 CITEO 纯生成器并保持旧流程兼容
  - [ ] 5.1 从 CiteoPoaService 抽取 CiteoPoaGenerator
    - 将模板替换、BusinessLicenseNo 分支、签名、DOCX/PDF 生成、文件净化和 OSS 上传抽为不依赖 SourceRepository 的纯生成服务。
    - 纯生成器只消费 CITEO 最小字段和时钟，PDF 转换明确传 `firstPageOnly=false` 保留全部页面，上传 `POA-{净化NameEng}.pdf`。
    - _需求：6.1-6.5、8.2_

  - [ ] 5.2 适配旧 CiteoPoaService 和旧 Id API
    - 保留旧 `CiteoPoaService::generate(EprRegInfoId)` 的 SourceRepository 查询、校验、旧路由和响应契约，只将已取得记录委托给新纯生成器。
    - 确保 API_Flow 直接调用纯生成器，不经过旧服务或任何 Repository。
    - _需求：2.1、2.2、2.5、6.6_

  - [ ] 5.3 编写属性测试：CITEO POA 生成契约
    - **Property 13：CITEO POA 生成契约**
    - 对合法最小 Data 和任意时钟验证占位值、BusinessLicenseNo、签名、全页转换参数及净化文件名。
    - **验证：需求 6.5**

  - [ ] 5.4 编写 CITEO 单元、模板集成与旧流程适配测试
    - 覆盖 EU 非 FR 使用 VATNumber、FR/非 EU 使用 RegNumber、城市后缀、当前日期、签名和 OSS URL。
    - 使用真实 CITEO DOCX 与多页 PDF 夹具验证全部页面保留，无未替换占位符且签名 media 存在。
    - 更新 `CiteoPoaServiceTest`，断言旧服务仍查询一次源记录并委托生成器，旧控制器响应不变；新 API 请求不调用旧服务。
    - _需求：2.5、6.1-6.6、7.2、7.4_

- [ ] 6. 组装法国 API 编排和统一同步响应
  - [ ] 6.1 实现 FrenchFileGenerationService
    - LEKO 分支按“校验/映射 → INSEE Resolver → LEKO 生成器”顺序执行；INSEE 最终失败必须发生在生成和上传前。
    - CITEO 分支直接调用纯生成器且绝不调用 INSEE；两个分支均不得注入或访问 Source/Target Repository。
    - _需求：1.2、1.3、2.1-2.4、4.8-4.10、5.1、6.5、6.6_

  - [ ] 6.2 实现统一响应工厂和控制器异常映射
    - 成功固定返回 HTTP/code 200、`msg=success`、`ProcessMode=sync` 和深度原样 bizParam。
    - LEKO 映射 file1=XLSX、file2=POA；CITEO 映射 file1=POA；始终补齐 file1-file10，其余为空字符串，并校验非空槽为完整 HTTP(S) URL。
    - 将校验/INSEE/模板/DOCX/PDF/OSS 分类为安全 400，将未分类 Throwable 转为固定通用 500，所有错误 `data=null`；可解析请求错误原样回传 bizParam。
    - _需求：7.1-7.4、8.1-8.4_

  - [ ] 6.3 编写属性测试：统一同步响应模型
    - **Property 14：统一同步响应模型**
    - 对两个 PushType、任意合法绝对 URL 和嵌套 bizParam 验证状态、模式、文件槽、空字符串及深度原样回传。
    - **验证：需求 7.1、7.2、7.3、7.4**

  - [ ] 6.4 编写编排与响应功能测试
    - 分别覆盖 LEKO/CITEO 成功请求、CITEO 空 bizParam、LEKO 官方 INSEE 数据、非 FR/无效 SIREN 默认路径和固定 file1-file10。
    - 覆盖可解析校验错误 bizParam 回传、INSEE 最终失败时零生成/上传，以及无效 OSS 相对 URL 不进入成功响应。
    - _需求：1.2-1.5、4.8-4.10、7.1-7.4、8.1-8.4_

- [ ] 7. 强化安全异常、日志脱敏和 Source_DB 隔离
  - [ ] 7.1 实现分类业务异常和敏感信息脱敏器
    - 为 INSEE、模板、DOCX、PDF、OSS 定义稳定的公开错误类别，不回显底层路径、堆栈、凭证、API Key 或内部端点。
    - 对 Bearer、INSEE Key、RegNumber/SIREN、VAT、电话、邮箱、地址和配置敏感键统一掩码或不可逆指纹；日志仅保留 request_id、类别、尝试序号和安全元数据。
    - 禁止统一 API 记录完整请求、完整响应业务数据、原始 Authorization 或临时文件路径。
    - _需求：8.2-8.5_

  - [ ] 7.2 建立 API_Flow 的依赖隔离护栏
    - 通过服务容器和构造函数边界确保 API 编排图不包含 `SourceRepository`、`SourceStatusUpdater`、`SourceAttachmentRepository`、`TargetRepository` 或 `sqlsrv_source` 连接。
    - 增加调用即失败的 Repository spy 和数据库 listener，明确 INSEE HTTP 是允许的外部依赖而非 Source_DB 访问。
    - _需求：2.1-2.4、6.6、8.3_

  - [ ] 7.3 编写属性测试：API_Flow 的 Source 隔离
    - **Property 2：API_Flow 的 Source 隔离**
    - 对所有合法 LEKO/CITEO 请求，在 Source_DB 不可连接且所有 Source/Target Repository 调用即失败时，验证零读写、零组件调用且实际外部依赖可用时仍成功。
    - **验证：需求 2.1、2.2、2.3**

  - [ ] 7.4 编写属性测试：生成故障安全响应
    - **Property 16：生成故障安全响应**
    - 为模板、DOCX、PDF、OSS 故障注入包含路径、堆栈和凭证的底层消息，断言公开结果为安全 400 且 `data=null`。
    - **验证：需求 8.2**

  - [ ] 7.5 编写属性测试：未分类异常安全响应
    - **Property 17：未分类异常安全响应**
    - 对任意未分类 Throwable 断言 HTTP/code 500、固定通用消息、`data=null` 且不回显内部消息。
    - **验证：需求 8.4**

  - [ ] 7.6 编写属性测试：日志脱敏
    - **Property 18：日志脱敏**
    - 随机生成 Bearer、INSEE Key、注册号、VAT、联系方式和配置敏感值，断言所有相关日志均不含原值。
    - **验证：需求 8.5**

  - [ ] 7.7 编写 Source_DB 断连和副作用隔离集成测试
    - 将 `sqlsrv_source` 配置为不可连接并监听查询，分别完成 CITEO、非 FR LEKO 和 fake INSEE 的法国 LEKO 请求。
    - 断言仅 Id/EPRRegInfoId 请求、INSEE 连续失败和生成故障均不调用 Repository、状态更新、附件、通知或数据库补数。
    - _需求：2.1-2.4、4.8、6.6、8.3_

- [ ] 8. 建立统一 API 文档契约自动化测试
  - [ ] 8.1 编写文档契约自动化测试
    - 解析 `UNIFIED_API_DESIGN.md` 中的 LEKO/CITEO JSON 示例并通过生产 Validator，断言最小字段表与代码规则一致。
    - 静态断言法国端点、application/json、可选 Bearer、大小限制、限流和统一错误状态与生产路由/中间件一致。
    - 静态断言三个 Insee 字段只标记为服务端派生且 CompanyAddressLine2En 不属于最小输入，并包含 Source_DB/INSEE 边界、5×6 秒、Source_Flow 兼容说明、完整 OSS URL、同步模式、bizParam 原样回传和正确 file1-file10 槽位。
    - _需求：8.6-8.9、9.1-9.4_

- [ ] 9. 完成端到端集成与 Source_Flow 回归
  - [ ] 9.1 编写法国统一 API 集成测试
    - 通过完整 HTTP 栈、fake INSEE、真实模板和 fake OSS 验证法国 LEKO 两段 INSEE 查询后生成 XLSX/POA，以及 CITEO 最小空 bizParam 请求生成全页 POA。
    - 校验 XLSX 单元格、DOCX XML/media、PDF 页数、文件名、绝对 URL、file1-file10、错误状态和 bizParam 深度相等。
    - _需求：1.1-1.6、2.1-2.4、3.1-3.9、4.1-4.10、5.1-5.5、6.1-6.6、7.1-7.4、8.1-8.5、9.1-9.4_

  - [ ] 9.2 增加 Source_Flow golden 回归测试
    - 保留并扩展 `ProcessEprCommandTest`、`EprRegProcessorTest`、`InseeApiClientTest`、`XlsxGeneratorTest` 和 `CiteoPoaServiceTest`。
    - 对比抽取前后的查询条件、SIREN 派生、INSEE 5 次/6 秒、J/L/O 默认、文件结果、状态、Target、附件和通知调用；验证旧 CITEO Id API 路由/响应不变。
    - _需求：2.5_

  - [ ] 9.3 执行完整自动化验收并达到覆盖率目标
    - 运行全部 PHPUnit 单元、属性、功能、集成、文档契约和回归测试，确认每个 Property 由且仅由一个属性测试覆盖且至少执行 100 个样本。
    - 运行 PHP 诊断/格式检查，修复所有语法、类型和静态问题；确认新增/修改代码覆盖率不低于 80%。
    - _需求：1.1-9.4_

- [ ] 10. 最终检查点
  - 确保所有测试通过，如有问题请询问用户。

## 说明

- 所有任务均为必需任务，不使用可选 `*` 标记。
- 实施必须按顺序增量完成，每一步复用前一步产物，最终由统一控制器、编排服务和响应工厂完成接线，不保留孤立代码。
- API_Flow 禁止任何 Source_DB 或 Source/Target Repository 访问；法国 LEKO 对现有 `InseeApiClient` 的 INSEE 官方查询是明确允许且必需的外部依赖。
