# 需求文档

## 简介

本功能为法国 LEKO 与 CITEO 文件生成增加统一 API 数据流。API_Flow 直接使用请求业务字段生成文件，对 Source_DB 保持零读写；其中法国公司 LEKO 所需的法律形式、NAF/APE 和总部 SIRET 继续由服务端通过现有 `InseeApiClient` 调用 INSEE 官方 SIRENE API 获取。现有 Source_Flow 的数据库查询、INSEE 查询、文件生成、状态及附件写回行为保持不变。

项目中实际标识为“LEKO”，功能目录名仍保留 `lkeo-citeo-api-file-generation`。本文依据当前项目中的 `EprRegProcessor`、`InseeApiClient`、`XlsxGenerator`、`DocxGenerator`、`CiteoPoaService`、`SourceRepository` 和 `UNIFIED_API_DESIGN.md` 修订。

## 术语表

- **SaaS**：向统一接口提交业务请求的上游服务。
- **Unified_API_Gateway**：按 `Country + PushType` 路由请求的统一入口。
- **French_File_API**：法国 LEKO/CITEO 同步文件生成 API。
- **Source_DB**：`sqlsrv_source` 及 SourceRepository、SourceStatusUpdater、SourceAttachmentRepository 所访问的数据。
- **Source_Flow**：现有通过 Source_DB 取得业务数据并执行后续处理的流程。
- **API_Flow**：以 API_Request 为业务输入，对 Source_DB 零读写，但可访问完成业务所需外部服务的流程。
- **INSEE_API**：INSEE 官方 SIRENE 3.11 HTTP API，是外部服务，不属于 Source_DB。
- **INSEE_Data**：服务端取得并映射的法律形式、NAF/APE、总部 SIRET 等数据，不是 API_Request 字段。
- **API_Request**：包含 PushType、Country、Data 和 bizParam 的 JSON 对象。
- **bizParam**：业务关联信息对象，原样回传；法国流程不凭空要求未参与生成的子字段。
- **LEKO_File**：基于 `Leko_Template.xlsx` 生成的 XLSX。
- **POA_File**：基于 LEKO 或 CITEO DOCX 模板转换得到的 PDF。
- **OSS**：保存生成文件并返回完整下载 URL 的对象存储。
- **EU_Country_Code**：当前生成逻辑中的欧盟国家二字码集合。

## 需求

### 需求 1：统一信封与路由

用户故事：作为 SaaS 调用方，我希望使用统一信封生成法国文件，以便稳定集成。

#### 验收标准

1. WHEN Unified_API_Gateway 接收请求，THE Unified_API_Gateway SHALL 校验 PushType、Country、Data 和 bizParam 均存在且分别为预期类型。
2. WHEN PushType 为 `FR_EPR_REGISTER_LEKO_FILE` 且 Country 为 `FR`，THE Unified_API_Gateway SHALL 路由到 LEKO API_Flow。
3. WHEN PushType 为 `FR_EPR_REGISTER_CITEO_FILE` 且 Country 为 `FR`，THE Unified_API_Gateway SHALL 路由到 CITEO API_Flow。
4. WHEN 请求进入 LEKO API_Flow，THE API_Validator SHALL 要求 `bizParam.BusinessSerialNumber` 为非空字符串或整数，因为现有 LEKO OSS 目录使用该值。
5. WHEN 请求进入 CITEO API_Flow，THE API_Validator SHALL 不要求 bizParam 中存在 BusinessSerialNumber、BusinessId 或 Businessld；已有字段仅作为关联信息原样回传。
6. IF Country 与 PushType 国家前缀不一致，THEN THE Unified_API_Gateway SHALL 返回 code 为 400 的统一错误响应。

### 需求 2：API_Flow 与 Source_DB 隔离

用户故事：作为维护者，我希望 API_Flow 不依赖 source 库，同时保留必要的 INSEE 官方查询。

#### 验收标准

1. WHILE French_File_API 执行 API_Flow，THE French_File_API SHALL 对 Source_DB 保持零次读取和零次写入。
2. WHILE French_File_API 执行 API_Flow，THE French_File_API SHALL 对 SourceRepository、SourceStatusUpdater、SourceAttachmentRepository 和 TargetRepository 保持零次调用。
3. WHEN Source_DB 不可连接且请求及所需外部服务可用，THE French_File_API SHALL 继续处理 API_Request。
4. WHILE LEKO API_Flow 处理法国公司且 RegNumber 可派生 9 位 SIREN，THE French_File_API SHALL 允许且必须通过现有 InseeApiClient 访问 INSEE_API；该外部 HTTP 调用不构成 Source_DB 依赖。
5. WHILE Source_Flow 运行，THE French_File_API SHALL 保留其输入、Source_DB 读写、INSEE 查询、重试、状态更新、附件处理和输出行为，不因 API_Flow 改造而变化。

### 需求 3：LEKO 最小请求契约

用户故事：作为调用方，我希望只提交文件生成和 INSEE 查询实际需要的数据。

#### 验收标准

1. WHEN PushType 为 `FR_EPR_REGISTER_LEKO_FILE`，THE API_Validator SHALL 要求 Data 包含非空的 NameEng、RegAddressEng、CompanyAddressLine1En、CompanyAddressPostcode、CityEngName、CountryTwoCode、RegNumber、LegalPersonFullNamePinYin、LegalPersonEmail、LegalPersonPhone 和 LegalPersonGender。
2. WHERE Data.CountryTwoCode 为 `CN`，THE API_Validator SHALL 要求 AreaName 非空。
3. WHERE Data.CountryTwoCode 为 `FR`，THE API_Validator SHALL 要求 RegisteredCapital 和 RegisteredCapitalCurrency 非空。
4. WHERE Data.CountryTwoCode 属于 EU_Country_Code，THE API_Validator SHALL 要求 VATNumber 非空。
5. THE API_Validator SHALL NOT 要求或消费 Data.InseeLegalForm、Data.InseeNafCode、Data.InseeSiret；三项不属于 LEKO API 最小请求字段集。
6. WHEN NameEng 超过 50 个字符或 CompanyAddressLine1En 超过 100 个字符，THE API_Validator SHALL 返回包含字段名和上限的校验错误。
7. WHEN CountryTwoCode 为 `HK`，THE Mapper SHALL 将 AreaName 设为 `香港`，与现有 Source_Flow 行为一致。
8. WHEN CountryTwoCode 既非 `CN` 也非 `HK` 且 AreaName 为空，THE Mapper SHALL 使用大写 CountryTwoCode。
9. THE API_Validator SHALL NOT 要求 CompanyAddressLine2En，因为当前 LEKO XLSX 和 POA 生成器均未使用该字段。

### 需求 4：LEKO INSEE 官方查询与映射

用户故事：作为法国公司调用方，我希望服务端延续现有 INSEE 查询，以获得可信的官方字段。

#### 验收标准

1. WHEN LEKO Data.CountryTwoCode 为 `FR`，THE French_File_API SHALL 从 RegNumber 删除所有非数字字符并取前 9 位作为 SIREN 查询键。
2. WHEN 派生 SIREN 长度恰为 9，THE French_File_API SHALL 调用 InseeApiClient.fetchCompanyData(SIREN)。
3. WHEN 查询 INSEE，THE InseeApiClient SHALL 使用 `https://api.insee.fr/api-sirene/3.11/siren/{SIREN}`，并使用配置 `services.insee.api_key` 对应的 `X-INSEE-Api-Key-Integration` 请求头。
4. WHEN SIREN 响应返回首个 `periodesUniteLegale`，THE InseeApiClient SHALL 将 `categorieJuridiqueUniteLegale` 通过现有法律形式表映射为 legal_form，将 `activitePrincipaleUniteLegale` 去除句点映射为 naf_code，并将 SIREN 与 `nicSiegeUniteLegale` 拼接为 siret_siege。
5. WHEN siret_siege 可得，THE InseeApiClient SHALL 继续调用 `https://api.insee.fr/api-sirene/3.11/siret/{SIRET}` 获取总部地址；该地址继续保留在 INSEE_Data 中，但 LEKO XLSX 三个目标列只消费 legal_form、naf_code 和 siret_siege。
6. WHEN 法律形式代码为空，THE InseeApiClient SHALL 映射为 `Non renseignée`；WHEN 代码不在现有映射表中，SHALL 映射为 `Code juridique inconnu ({code})`。
7. WHEN INSEE 活动代码缺失，THE InseeApiClient SHALL 使用 `N/A`；WHEN 总部 NIC 缺失，SHALL 使用 `N/A` 作为 siret_siege 且不发起 SIRET 请求。
8. WHEN INSEE HTTP 状态不是 200，THE InseeApiClient SHALL 抛出 `INSEE API error (HTTP {status})`；LEKO API_Flow SHALL 按现有策略最多尝试 5 次、相邻尝试间隔 6 秒，最终仍失败则返回安全的业务失败响应且不生成或返回文件。
9. WHEN 法国 RegNumber 无法派生恰好 9 位 SIREN，THE French_File_API SHALL 不调用 INSEE_API，并使用与现有 fetchInseeData 一致的空 INSEE_Data；XLSX 随后按现有默认映射写入 `Autre / Other`、空字符串和空字符串。
10. WHEN CountryTwoCode 不为 `FR`，THE French_File_API SHALL 不调用 INSEE_API，并使用空 INSEE_Data。

### 需求 5：LEKO 文件生成

#### 验收标准

1. WHEN LEKO 请求通过校验且 INSEE 处理成功，THE French_File_API SHALL 基于 `Leko_Template.xlsx` 和 `Leko_Template.docx` 生成 XLSX 与 POA PDF。
2. WHEN 生成 XLSX，THE French_File_API SHALL 将 INSEE_Data.legal_form、naf_code、siret_siege 分别写入 J、L、O 列，不从 Data 读取同名调用方字段。
3. WHEN 生成 XLSX，THE French_File_API SHALL 保持当前 A:AJ 的业务字段、固定值、当前年份、随机整数和法式小数映射不变。
4. WHEN 生成 LEKO POA，THE French_File_API SHALL 使用 NameEng、RegAddressEng、去除“市”或末尾 `shi` 的 CityEngName、当前日期及 LegalPersonFullNamePinYin，并生成签名图片。
5. WHEN 生成成功，THE French_File_API SHALL 上传两个文件到 OSS；XLSX 文件名以 `.xlsx` 结尾，POA 文件名为 `POA-{净化后的NameEng}.pdf`。

### 需求 6：CITEO 最小请求契约与生成

#### 验收标准

1. WHEN PushType 为 `FR_EPR_REGISTER_CITEO_FILE`，THE API_Validator SHALL 仅固定要求 Data.NameEng、CountryTwoCode、CityEngName、RegAddressEng 和 LegalPersonFullNamePinYin 非空。
2. WHERE CountryTwoCode 属于 EU_Country_Code 且不为 `FR`，THE API_Validator SHALL 额外要求 VATNumber，并将其作为 BusinessLicenseNo。
3. WHERE CountryTwoCode 不属于 EU_Country_Code 或等于 `FR`，THE API_Validator SHALL 额外要求 RegNumber，并将其作为 BusinessLicenseNo。
4. THE CITEO API_Flow SHALL NOT 要求 AreaName、公司地址行、邮编、资本、法人联系方式、性别或任何 INSEE 字段。
5. WHEN 请求通过校验，THE French_File_API SHALL 基于 `Citeo_Template.docx` 使用上述实际字段和当前日期生成含签名、保留全部页面的 POA PDF，并上传为 `POA-{净化后的NameEng}.pdf`。
6. IF 请求仅包含 Id 或 EPRRegInfoId 而缺少上述 Data，THEN THE API_Validator SHALL 返回 code 400 且不查询 Source_DB。

### 需求 7：同步响应

#### 验收标准

1. WHEN LEKO 成功，THE API SHALL 返回 HTTP/code 200、msg=`success`、ProcessMode=`sync`，file1 为 XLSX 完整 OSS URL、file2 为 POA 完整 OSS URL，file3-file10 为空字符串。
2. WHEN CITEO 成功，THE API SHALL 返回 HTTP/code 200、msg=`success`、ProcessMode=`sync`，file1 为 POA 完整 OSS URL，file2-file10 为空字符串。
3. WHEN 返回成功或可解析请求的错误响应，THE API SHALL 原样回传完整 bizParam。
4. WHEN 返回非空文件槽，THE API SHALL 返回可直接下载的完整 HTTP(S) OSS URL。

### 需求 8：错误、安全与文档一致性

#### 验收标准

1. IF JSON、信封或 Data 校验失败，THEN THE API SHALL 返回 HTTP/code 400，并一次列出全部字段错误。
2. IF 模板、DOCX、PDF 或 OSS 处理失败，THEN THE API SHALL 返回安全的 code 400 响应，不暴露本地路径、堆栈、凭证或内部端点。
3. IF INSEE 最终失败，THEN THE API SHALL 返回可识别的 INSEE 查询失败类别，不回显 API Key，不降级为调用方传入 INSEE 字段，也不访问 Source_DB 补数。
4. IF 未分类异常发生，THEN THE API SHALL 返回 HTTP/code 500 和通用消息。
5. WHEN 记录日志，THE API SHALL 隐去认证令牌、INSEE API Key、注册号、VAT、联系方式及其他配置的敏感字段。
6. WHEN 本需求修订完成，THE `UNIFIED_API_DESIGN.md` SHALL 从 LEKO 请求示例和字段总表的法国输入契约中删除 InseeLegalForm、InseeNafCode、InseeSiret，并明确它们是服务端 INSEE 派生输出。
7. WHEN 文档描述 API_Flow，THE 文档 SHALL 明确“Source_DB 零读写”不等于“禁止外部 API”，并描述法国 LEKO 对 InseeApiClient 的必要依赖。
8. WHEN 文档描述字段，THE 文档 SHALL 分别列出 LEKO、CITEO 和 bizParam 的最小字段，且只包含当前生成、INSEE 查询、OSS 路径或响应透传实际需要的输入。
9. WHEN 文档描述 Source_Flow，THE 文档 SHALL 明确保留其既有行为不变。

### 需求 9：传输约束

#### 验收标准

1. THE API SHALL 仅接受 POST 和 `application/json`。
2. WHERE Bearer 认证启用，THE API SHALL 校验令牌；失败返回 HTTP/code 401。
3. WHEN 请求体超限，THE API SHALL 返回 HTTP/code 400。
4. WHEN 客户端超出限流，THE API SHALL 返回 HTTP/code 429。
