feat(task): 规则模板、任务克隆与统计、组织导入、租户用量(V15)

克隆**配置**(表单/材料清单/命名模板/审核配置/覆盖班级)、刻意不克隆业务数据——
名册克隆过去会让同一证件号在两个任务里各有一行。
统计补上 abnormalCount 与 avgAuditHours(这两个字段以前恒为 null,因为问题表与审核记录
还不存在);规则模板可按 parts 只应用一部分、材料清单覆盖式合并。

顺带修 F-44:TaskService.create 对缺必填项抛 NPE(对外 50000「系统繁忙」),
改为集中校验并指出是哪个字段;F-45:历史整理器导入改走 TaskService.create,
避免两处各生成一次 task_code 迟早撞号。

验证:e2e-m7-taskconfig 69/69(含待办范围判据的回归防线)。
This commit is contained in:
2026-09-30 09:02:55 +08:00
parent aa30fd2383
commit efde4627eb
14 changed files with 1973 additions and 10 deletions
@@ -0,0 +1,51 @@
-- ============================================================================
-- V15:规则模板补审计列 + 两条公共模板
--
-- 一、`collect_rule_template` V1 就建好了,一直没有代码用(F-50 的第二张表)
-- ---------------------------------------------------------------------------
-- 列(`form_schema` / `materials` / `naming_template` / `is_public` / `use_count`)
-- 正好对应契约 §7.13 的 `GET /rule-templates` 与 `POST /rule-templates/{id}/apply`
-- 的 `parts: ["FORM","MATERIALS","NAMING"]` —— 也就是说"当初就是照着这三个部分设计的"。
--
-- 这里补的是实体基类要求的四列(否则运行时才炸,见 F-24/F-35)。
--
-- 二、两条**公共**模板(`tenant_id = 0` 表示平台级,所有租户可见)
-- ---------------------------------------------------------------------------
-- ⚠️ 为什么必须真的插两条数据,而不是让列表返回空:
-- `GET /rule-templates` 的用途是"新建任务时一键套用一套现成配置"。
-- 列表空着的话这个功能**存在但没有任何用处**,而界面上看不出是"还没建模板"
-- 还是"功能没做"。所以随迁移带两条:一条"学校收集标准"、一条"自主认定精简"。
--
-- `materials` 的 JSON 与代码里的内置材料模板(`MaterialDefService` 的
-- `TPL_SCHOOL_ROLL` 等)保持同一形状,这样 `apply` 时可以直接复用那段逻辑。
-- ============================================================================
ALTER TABLE collect_rule_template ADD COLUMN IF NOT EXISTS update_by BIGINT;
ALTER TABLE collect_rule_template ADD COLUMN IF NOT EXISTS update_time TIMESTAMP;
ALTER TABLE collect_rule_template ADD COLUMN IF NOT EXISTS deleted SMALLINT NOT NULL DEFAULT 0;
COMMENT ON COLUMN collect_rule_template.tenant_id IS '0 = 平台级公共模板(所有租户可见);其它值 = 该租户自建';
-- 公共模板的可见性靠 `is_public`,唯一性靠"名字 + 租户",避免重复插入
CREATE UNIQUE INDEX IF NOT EXISTS uk_rule_tpl ON collect_rule_template (
tenant_id, template_name
) WHERE deleted = 0;
-- ── 两条公共模板 ──────────────────────────────
-- `form_schema` 留空 = "用系统默认表单"(不是"空表单")。
-- 这一点必须写清:apply 时遇到空 schema 是**跳过**而不是"把目标任务的表单清空"。
INSERT INTO collect_rule_template
(tenant_id, template_name, cert_type, form_schema, materials, naming_template,
is_public, use_count, create_by, create_time, deleted)
VALUES
(0, '学校收集 · 标准(身份证+一寸照+学籍表)', 1, NULL,
'[{"materialCode":"IDCARD","materialName":"身份证","required":1,"maxCount":2,"maxSizeKb":2048,"targetDpi":300,"sortNo":1},
{"materialCode":"PHOTO1","materialName":"一寸照片","required":1,"maxCount":1,"maxSizeKb":1024,"targetDpi":300,"sortNo":2},
{"materialCode":"SCHOOL_ROLL","materialName":"学籍表","required":1,"maxCount":1,"maxSizeKb":3072,"targetDpi":300,"sortNo":3}]'::jsonb,
'{姓名}-{证件号}-{材料名}', 1, 0, 0, CURRENT_TIMESTAMP, 0),
(0, '自主认定 · 精简(身份证+一寸照+工作证明)', 2, NULL,
'[{"materialCode":"IDCARD","materialName":"身份证","required":1,"maxCount":2,"maxSizeKb":2048,"targetDpi":300,"sortNo":1},
{"materialCode":"PHOTO1","materialName":"一寸照片","required":1,"maxCount":1,"maxSizeKb":1024,"targetDpi":300,"sortNo":2},
{"materialCode":"WORK_PROOF","materialName":"工作证明","required":1,"maxCount":1,"maxSizeKb":3072,"targetDpi":300,"sortNo":3}]'::jsonb,
'{姓名}-{证件号}-{材料名}', 1, 0, 0, CURRENT_TIMESTAMP, 0)
ON CONFLICT DO NOTHING;