# R1.3.8 发票管理任务报告

## 交付结果

- 产品仓分支：`feat/r138-invoices`。
- 产品提交：
  - `60c903e feat: add invoice lifecycle and CLI`。
  - `dc7e34b fix: reject empty invoice updates`。
- 产品 PR：[pikainc/pikaoa#9](https://github.com/pikainc/pikaoa/pull/9)。
- 执行记录：[pikasTech/unidesk#1993](https://github.com/pikasTech/unidesk/issues/1993)。

## 业务能力

- 发票是服务端生成 UUIDv7 的平级 REST 资源，伙伴、合同、合同版本和附件只保存 typed ID。
- `00007_invoices.sql` 使用 `numeric(20,2)` 保存金额、税额和价税合计，并由服务和数据库共同校验定点金额与合计关系。
- 附件使用独立 `invoice_attachments` 关联记录，不形成嵌套资源或附件载荷副本。
- 发票支持创建、读取、允许字段部分更新、分页列表、按币种汇总和不可删除废弃。
  - 废弃保留操作者、时间与非空原因。
  - 默认有效列表和汇总排除废弃记录。
- 空 PATCH `{}` 返回 typed HTTP 400：
  - `code=invalid_argument`。
  - `field=body`。
  - 发票实体、`updatedAt`、store、audit 和 outbox 均保持不变。
- `partnerCategoryId` 在 PostgreSQL List/Summary 中通过 `business_partner_categories` 的 `EXISTS` 条件真实过滤，不清空条件或返回未过滤全量结果。

## 平台契约

- 模块与 capability 统一使用 `invoices`、`invoices.read/manage/void`。
  - 管理员拥有全部能力。
  - 普通员工系统角色获得目录中的全部 `*.read`。
- HTTP 写入 actor 仅来自 identity session，不接受请求载荷伪造 actor。
- 业务、审计和 `invoices.created/updated/voided` outbox 事件在同一 PostgreSQL 事务内写入。
- OTel span、结构化日志和 Prometheus 指标统一使用 `invoices` 模块前缀；指标只包含有界 operation/result 标签。
- 跨模块引用存在性和一致性结果只投影为 `blocking=false` warning。
  - 发票自身 UUIDv7、金额、日期、状态和废弃事实继续返回字段级 typed error。
- CLI 覆盖 `invoices create|get|update|list|summary|void`。
  - 文本与 JSON 都返回 requestId/traceId。
  - 失败返回非零退出码。

## 验证证据

- `go test ./...`：通过。
- `go vet ./...`：通过。
- `git diff --check`：通过。
- `make build`：`pikaoa-api`、`pikaoa-worker`、`pikaoa` 三入口通过。
- host `httptest` 真实 HTTP/CLI 验证：
  - 管理员创建、读取、汇总、废弃和默认排除通过。
  - 普通员工读取通过，写入被 `authorization_denied` 拒绝。
  - 空 PATCH 返回 correlated typed error，重新读取的业务字段与 `updatedAt` 不变。
- 服务层空 PATCH 回归验证：
  - store 读取和写入次数均为零。
  - 引用校验、audit 和 outbox 路径均未进入。
  - 完整发票值与 `updatedAt` 保持不变。
- PostgreSQL 集成测试代码覆盖大额定点金额、业务/audit/outbox 同事务回滚、伙伴分类 List/Summary 和废弃事实。

## 未执行与风险

- 当前环境未声明 `PIKAOA_TEST_DATABASE_URL`，所以 PostgreSQL 集成测试在本轮按契约 skip，不能宣称真实 PostgreSQL 已通过。
- UniDesk owning YAML 只发现正式 `pikaoa`、`pikaoa-ci` 和 PK01 数据库目标，没有专用 PikaOA 测试集群 target。
  - 本轮没有猜测集群。
  - 本轮没有部署 k8s。
  - 本轮没有访问正式 namespace、公网或 PK01 数据。
- 分类查询依赖伙伴迁移 `00004_partners.sql` 提供 `business_partner_categories`。
  - PR 合并前必须先吸收伙伴基线。
  - 必须在具有该迁移的专用测试数据库复跑 PostgreSQL 集成测试。
- 合同、合同版本和附件模块接入后应提供真实 `ReferenceValidator`。
  - 其一致性异常仍只能输出非阻塞 warning。
  - 不得改变安全业务成功终态。
