diff --git a/.tmp-rules-1-0.yaml b/.tmp-rules-1-0.yaml new file mode 100644 index 0000000..cf5a576 --- /dev/null +++ b/.tmp-rules-1-0.yaml @@ -0,0 +1,2898 @@ +metadata: + type_id: 行政卷宗.行政处罚 + name: 烟草专卖行政处罚卷宗 + version: '1.0' + last_updated: '2026-04-18' + parent: 行政卷宗 + inherits_from: + - base.common + - base.administrative_case + classification_keywords: + - 行政处罚 + - 烟草专卖 + - 处罚决定书 + - 立案报告 + - 询问笔录 + description: '烟草专卖行政处罚卷宗审核。 + + 覆盖:立案、现场检查、证据先行登记保存、取证核价、询问笔录、权利告知、 + + 调查终结、处理审批、事先告知、处罚决定、送达、执行、结案全流程。 + + ' + # 开 medium 风险规则的 LLM 救援(跨子文档对齐失败交给 rescue 模块 + # 的 L1 判定语义等价,如"投诉举报" vs "举报")。 + rescue_profile: + rescue_risk: [medium] + +# TOC 页定位(dossier_segmenter 使用) +# keywords: 本类卷宗的目录标题(OCR 空白自动规整,"卷 宗 目 录" 也命中) +# anti_keywords: 卷内"内部目录",避免被误判为卷宗级 TOC +# 两个列表均为"扩展默认值",下面显式列出本类卷宗实际会遇到的项 —— +# 默认的 卷宗目录 / 卷内目录 / Contents 等仍自动生效。 +toc: + keywords: + # 实际 OCR 观察到的目录标题(均为 defaults 一部分,显式列出作自注释) + - 卷宗目录 + - 卷内目录 + anti_keywords: + # 卷内子文档自带的"目录"标题,不是卷宗级 TOC —— 必须排除 + - 证据材料目录 # 重大执法行为法制审核送审表 里的证据清单 + - 物品目录 # 抽样取证物品清单等 + +# 跨子文档派生字段 —— 给规则里的 `activate_if` / 对级 `when` 用 +derived_fields: + # 当事人类型:决定一条规则里"个人字段 pair"还是"单位字段 pair"该不该对齐 + # + # 按 USCC 第 2 位判定(GB 32100-2015 法人和其他组织统一社会信用代码): + # 1 = 机关 → 单位 + # 2 = 个体工商户 → 个人(法律归类:自然人工商业) + # 3 = 农民专业合作社 → 单位 + # 4 = 事业单位 → 单位 + # 5 = 企业 → 单位 + # 8 = 社团 → 单位 + # 9 = 其他组织 → 单位 + # + # 个体户虽然有 USCC 和营业执照,但当事人栏填个人信息(姓名/身份证), + # 所以单位 pair 应跳过;执照字段作为辅助证据另行处理。 + # + # 注:不看"字号"——当前 OCR 常把案件文号误抽到 字号 字段; + # 执照名称/执照统一社会信用代码 在个体户里也存在,因此不作为单位标志。 + # 表达式必须单行(evaluate 不支持多行条件)。 + - name: 当事人类型 + type: string + # 嵌套 IfExp 走短路(BoolOp 求值所有分支,`not None` 会走 null-propagation + # 返回 None 而被 IfExp 当 False 走到 else,导致对 None 调 .startswith 崩溃) + # - USCC 空/缺失 → 个人 + # - USCC 以 '92' 开头 → 个人(个体工商户) + # - 其它 (91/93/94/95/… 开头) → 单位 + compute: "'个人' if 处罚决定书.统一社会信用代码 == None else ('个人' if 处罚决定书.统一社会信用代码.startswith('92') else '单位')" + desc: 案件当事人类型(个人 / 单位)—— 按 USCC 第 2 位判,个体户 (92xxx) 判为个人 + + # 证据复制(提取)单可能同时存在多张居民身份证(当事人、举报人、 + # 未成年人、相关人等)。抽取侧把整组居民身份证记录按 multi_entity 抽 + # 下来,由这里挑出归属当事人的那一份;规则再用 + # `证据复制(提取)单当事人.身份证*` 对齐处罚决定书/审批表/终结报告。 + # 只有一张身份证时引擎自动短路,不计 LLM 调用。 + - name: 证据复制(提取)单当事人 + type: object + compute_by: llm + prompt: |- + 以下是证据复制(提取)单中全部居民身份证记录: + + {居民身份证} + + 当事人姓名:{处罚决定书.当事人} + 当事人身份证号:{处罚决定书.身份证号码} + + 请挑出归属「当事人本人」(被处罚对象)的那一份身份证,按原字段 + 结构返回一个 JSON 对象(严格包含 身份证姓名/身份证性别/身份证民族/ + 身份证住址/身份证号/身份证背面,空值写 null)。 + + 优先以身份证号匹配当事人身份证号;若号码缺失,用姓名匹配。匹配不到 + 或无法判断归属时返回 JSON null。除该 JSON 外不要输出任何解释文字。 + depends_on: + - 居民身份证 + - 处罚决定书.当事人 + - 处罚决定书.身份证号码 + + # 询问笔录可能包含多份笔录记录(同一卷宗针对多人询问)。抽取侧把所 + # 有被询问人按 multi_entity 抽下来,由这里挑当事人那份;规则再用 + # `询问笔录当事人.被询问人*` 做一致性校验。只有一份被询问人记录时 + # 引擎自动短路,不计 LLM 调用。 + - name: 询问笔录当事人 + type: object + compute_by: llm + prompt: |- + 以下是询问笔录中全部「被询问人」记录: + + {被询问人} + + 当事人姓名:{处罚决定书.当事人} + 当事人身份证号(若有):{处罚决定书.身份证号码} + + 请挑出归属「当事人本人」(被处罚对象)的那一份笔录记录,按原字段 + 结构返回一个 JSON 对象(严格包含 被询问人姓名/被询问人性别/被询问人民族/ + 被询问人证件/被询问人电话/被询问人住址/被询问人经营地址,空值写 null)。 + + 优先以证件号匹配当事人身份证号;若号码缺失,用姓名匹配。匹配不到 + 或无法判断归属时返回 JSON null。除该 JSON 外不要输出任何解释文字。 + depends_on: + - 被询问人 + - 处罚决定书.当事人 + - 处罚决定书.身份证号码 + +sub_documents: +- id: 先行登记保存证据处理通知书 + name: 先行登记保存证据处理通知书 + required: false + classifier: + title_patterns: + - 先行登记保存证据处理通知书 + keywords: [] + min_score: 0.5 + extract: + - group: 基本信息 + fields: + - name: 处理方式 + type: verbatim + vlm_extract_mode: always + desc: 证据做出如下处理→选中的选项,要看打勾的选项 +- id: 卷内备考表 + name: 卷内备考表 + required: true + classifier: + title_patterns: + - 卷内备考表 + keywords: [] + min_score: 0.5 + extract: + - group: 基本信息 + fields: + - name: 立卷时间 + type: verbatim + desc: 立卷时间 +- id: 卷宗封面 + name: 卷宗封面 + required: false + classifier: + title_patterns: + - ^##?\s*卷\s*宗\s*$ + keywords: + - 此卷共计 + - 归档日期 + - 保存期限 + min_score: 1.0 + extract: + - group: 基本信息 + fields: + - name: 处理结果 + type: string + desc: 处理结果 +- id: 处罚决定书 + name: 处罚决定书 + required: true + classifier: + title_patterns: + - 处罚决定书 + keywords: [] + min_score: 0.5 + extract: + - group: 基本信息 + fields: + - name: 字号 + type: verbatim + desc: 字号 + - name: 当事人 + type: verbatim + desc: 当事人 + - name: 性别 + type: enum + allowed: + - 男 + - 女 + desc: 性别 + - name: 民族 + type: verbatim + desc: 民族 + - name: 烟草专卖许可证号 + type: verbatim + desc: 烟草专卖许可证号 + - name: 经营地址 + type: string + desc: 经营地址 + - name: 统一社会信用代码 + type: uscc + desc: 统一社会信用代码 + - name: 落款日期 + type: date + desc: 落款日期 + - name: 身份证住址 + type: string + desc: 身份证住址 + - name: 身份证号码 + type: chinese-id + desc: 身份证号码 + - group: 罚款信息 + fields: + - name: 罚款项目 + type: string + desc: 正文→罚款项目 + - name: 罚款基数 + type: money + desc: 正文→罚款项目金额基数 + - name: 罚款比例 + type: string + desc: 正文→罚款百分比 保留原格式如"50%" + - name: 罚款总额 + type: money + desc: 正文→罚款总金额 + - name: 罚款说明 + type: string + desc: 正文→罚款说明 + - name: 证据列举 + type: string + desc: 正文→证据列举 + - group: 权利告知 + fields: + - name: 救济途径 + type: string + desc: 正文→救济途径 +- id: 抽样取证物品清单 + name: 抽样取证物品清单 + required: false + classifier: + title_patterns: + - 抽样取证物品清单 + keywords: [] + min_score: 0.5 + extract: + - group: 基本信息 + fields: + - name: 品种规格 + type: string + desc: 表格内容→品种规格、样品基数 + - name: 表格有内容 + type: enum + allowed: + - 有 + - 无 + desc: 表格是否有内容 输出 有/无 + - name: 当事人签名 + type: enum + allowed: + - 有 + - 无 + desc: 当事人签名栏 输出 有/无 +- id: 案件处理审批表 + name: 案件处理审批表 + required: true + classifier: + title_patterns: + - 案件处理审批表 + keywords: [] + min_score: 0.5 + extract: + - group: 基本信息 + fields: + - name: 立案编号 + type: verbatim + desc: 立案编号 + - name: 立案日期 + type: date + desc: 立案日期 + - name: 案由 + type: string + desc: 案由 + - name: 案件来源 + type: string + desc: 案件来源 + - group: 当事人-单位 + fields: + - name: 单位名称 + type: string + desc: 当事人→单位→名称 + - name: 单位法代 + type: verbatim + desc: 当事人→单位→法定代表人(负责人) + - name: 单位电话 + type: verbatim + desc: 当事人→单位→联系电话 + - name: 单位地址 + type: string + desc: 当事人→单位→地址 + - group: 当事人-个人 + fields: + - name: 个人姓名 + type: verbatim + desc: 当事人→个人(个体工商户)→姓名 + - name: 个人性别 + type: verbatim + desc: 当事人→个人→性别 + - name: 个人年龄 + type: verbatim + desc: 当事人→个人→年龄 + - name: 个人民族 + type: verbatim + desc: 当事人→个人→民族 + - name: 个人证件 + type: verbatim + desc: 当事人→个人→证件类型及号码 + - name: 个人电话 + type: verbatim + desc: 当事人→个人→联系电话 + - name: 个人住址 + type: string + desc: 当事人→个人→住址 + - group: 审批意见 + fields: + - name: 承办人意见 + type: string + desc: 承办人意见→内容 + - name: 承办人日期 + type: date + desc: 承办人意见→日期 + - name: 承办人签名1 + type: enum + allowed: + - 有 + - 无 + desc: 承办人意见→签名1 输出 有/无 + - name: 承办人签名2 + type: enum + allowed: + - 有 + - 无 + desc: 承办人意见→签名2 输出 有/无 + - name: 承办部门意见 + type: string + desc: 承办部门意见→内容 + - name: 承办部门日期 + type: date + desc: 承办部门意见→日期 + - name: 承办部门签名 + type: enum + allowed: + - 有 + - 无 + desc: 承办部门意见→签名 输出 有/无 + - name: 法制部门意见 + type: string + desc: 法制部门意见→内容 + - name: 法制部门日期 + type: date + desc: 法制部门意见→日期 + - name: 法制部门审核人签名 + type: enum + allowed: + - 有 + - 无 + desc: 法制部门意见→审核人签名 输出 有/无 + - name: 法制部门负责人签名 + type: enum + allowed: + - 有 + - 无 + desc: 法制部门意见→负责人签名 输出 有/无 + - name: 负责人意见 + type: string + desc: 负责人意见→内容 + - name: 负责人日期 + type: date + desc: 负责人意见→日期 + - name: 负责人签名 + type: enum + allowed: + - 有 + - 无 + desc: 负责人意见→签名 输出 有/无 +- id: 案件调查终结报告 + name: 案件调查终结报告 + required: true + classifier: + title_patterns: + - 案件调查终结报告 + keywords: [] + min_score: 0.5 + extract: + - group: 基本信息 + fields: + - name: 立案日期 + type: date + desc: 立案日期 + - name: 案由 + type: string + desc: 案由 + - name: 案件来源 + type: string + desc: 案件来源 + - group: 当事人-单位 + fields: + - name: 单位名称 + type: string + desc: 当事人→单位→名称 + - name: 单位法代 + type: verbatim + desc: 当事人→单位→法定代表人(负责人) + - name: 单位电话 + type: verbatim + desc: 当事人→单位→联系电话 + - name: 单位地址 + type: string + desc: 当事人→单位→地址 + - group: 当事人-个人 + fields: + - name: 个人姓名 + type: verbatim + desc: 当事人→个人(个体工商户)→姓名 + - name: 个人性别 + type: enum + allowed: + - 男 + - 女 + desc: 当事人→个人→性别 + - name: 个人年龄 + type: verbatim + desc: 当事人→个人→年龄 + - name: 个人民族 + type: verbatim + desc: 当事人→个人→民族 + - name: 个人证件 + type: verbatim + desc: 当事人→个人→证件类型及号码 + - name: 个人电话 + type: verbatim + desc: 当事人→个人→联系电话 + - name: 个人住址 + type: string + desc: 当事人→个人→住址 + - group: 处理意见 + fields: + - name: 处理意见日期 + type: date + desc: 处理意见→日期 + - name: 处理意见承办人签名1 + type: enum + allowed: + - 有 + - 无 + desc: 处理意见→承办人签名1 输出 有/无 + - name: 处理意见承办人签名2 + type: enum + allowed: + - 有 + - 无 + desc: 处理意见→承办人签名2 输出 有/无 +- id: 涉案物品核价表 + name: 涉案物品核价表 + required: false + classifier: + title_patterns: + - 涉案物品核价表 + keywords: [] + min_score: 0.5 + extract: + - group: 基本信息 + fields: + - name: 核价组印章 + type: enum + allowed: + - 有 + - 无 + desc: 涉案卷烟价格管理小组印章 输出 有/无 + - name: 核价明细 + type: string + desc: 表格内容→品种规格、数量(单位:条)、单价(元)、合计(元)、备注 + - name: 表格全文 + type: string + desc: 核价表完整内容 +- id: 涉案物品返还清单 + name: 涉案物品返还清单 + required: false + classifier: + title_patterns: + - 涉案物品返还清单 + keywords: [] + min_score: 0.5 + extract: + - group: 基本信息 + fields: + - name: 日期 + type: date + desc: 日期 + - name: 补偿信息 + type: verbatim + desc: 补偿信息 + - name: 返还明细 + type: string + desc: 表格内容→品种规格、数量(单位:条)、单价(元)、合计(元)、备注 + - name: 返还确认 + type: verbatim + desc: 返还确认 + - name: 接收人签名 + type: enum + allowed: + - 有 + - 无 + desc: 接收人→签名 输出 有/无 + - name: 接收单位印章 + type: enum + allowed: + - 有 + - 无 + desc: 接收单位→印章 输出 有/无 +- id: 现场笔录 + name: 现场笔录 + required: true + classifier: + title_patterns: + - 现场笔录 + keywords: [] + min_score: 0.5 + extract: + - group: 基本信息 + fields: + - name: 检查时间 + type: verbatim + desc: 检查时间 + - name: 检查地点 + type: verbatim + desc: 检查地点 + - group: 被检查人 + fields: + - name: 单位名称 + type: string + desc: 被检查人→单位→名称 + - name: 单位法代 + type: verbatim + desc: 法定代表人(负责人) + - name: 单位许可证号 + type: verbatim + desc: 烟草专卖许可证号码 + - name: 个人姓名 + type: verbatim + desc: 被检查人→个人→姓名 + - name: 个人性别 + type: enum + allowed: + - 男 + - 女 + desc: 被检查人→个人→性别 + - name: 个人证件 + type: verbatim + desc: 被检查人→个人→证件类型及号码 + - name: 地址 + type: string + desc: 被检查人→地址 + - name: 电话 + type: verbatim + desc: 被检查人→联系电话 + - name: 现场负责人 + type: verbatim + desc: 现场负责人→姓名、性别、证件类型及号码、与被检查人关系 + - group: 签名意见 + fields: + - name: 意见 + type: verbatim + desc: 被检查人或现场负责人→意见 + - name: 意见日期 + type: date + desc: 被检查人或现场负责人(签名)→日期 + - name: 意见签名 + type: enum + allowed: + - 有 + - 无 + desc: 被检查人或现场负责人(签名)输出 有/无 +- id: 立案报告表 + name: 立案报告表 + required: true + classifier: + title_patterns: + - 立案报告表 + keywords: [] + min_score: 0.5 + extract: + - group: 基本信息 + fields: + - name: 立案编号 + type: verbatim + desc: 立案编号 如"郁烟立〔2024〕第35号" + - name: 案由 + type: string + desc: 案由 + - name: 案件来源 + type: string + desc: 案件来源 如"投诉举报" + - name: 案发时间 + type: verbatim + desc: 案发时间 + - name: 案发地点 + type: verbatim + desc: 案发地点 + - group: 当事人-单位 + fields: + - name: 单位名称 + type: string + desc: 当事人→单位→名称 + - name: 单位法代 + type: verbatim + desc: 当事人→单位→法定代表人(负责人) + - name: 单位电话 + type: verbatim + desc: 当事人→单位→联系电话 + - name: 单位地址 + type: string + desc: 当事人→单位→地址 + - group: 当事人-个人 + fields: + - name: 个人姓名 + type: verbatim + desc: 当事人→个人(个体工商户)→姓名 + - name: 个人性别 + type: verbatim + desc: 当事人→个人→性别 + - name: 个人年龄 + type: verbatim + desc: 当事人→个人→年龄 + - name: 个人民族 + type: verbatim + desc: 当事人→个人→民族 + - name: 个人证件 + type: verbatim + desc: 当事人→个人→证件类型及号码 + - name: 个人身份证号 + type: chinese-id + desc: 当事人→个人→居民身份证号码 + - name: 个人电话 + type: verbatim + desc: 当事人→个人→联系电话 + - name: 个人住址 + type: string + desc: 当事人→个人→住址 + - group: 案情 + fields: + - name: 案情摘要 + type: string + desc: 案情摘要正文 + - name: 案情品种 + type: string + desc: 案情摘要中的品种规格、单位、数量 + - group: 审批意见 + fields: + - name: 承办人意见 + type: string + desc: 承办人意见→意见 + - name: 承办人日期 + type: date + desc: 承办人意见→日期 + - name: 承办人签名1 + type: enum + allowed: + - 有 + - 无 + desc: 承办人意见→签名1 输出 有/无 + - name: 承办人签名2 + type: enum + allowed: + - 有 + - 无 + desc: 承办人意见→签名2 输出 有/无 + - name: 承办部门意见 + type: string + desc: 承办部门意见→意见 + - name: 承办部门日期 + type: date + desc: 承办部门意见→日期 + - name: 承办部门签名 + type: enum + allowed: + - 有 + - 无 + desc: 承办部门意见→签名 输出 有/无 + - name: 负责人意见 + type: string + desc: 负责人意见→意见内容 + - name: 负责人日期 + type: date + desc: 负责人意见→日期 + - name: 负责人签名 + type: enum + allowed: + - 有 + - 无 + desc: 负责人意见→签名 输出 有/无 +- id: 结案报告表 + name: 结案报告表 + required: true + classifier: + title_patterns: + - 结案报告表 + keywords: [] + min_score: 0.5 + extract: + - group: 基本信息 + fields: + - name: 当事人 + type: verbatim + desc: 当事人 + - name: 执行情况 + type: string + desc: 执行情况 + - group: 审批意见 + fields: + - name: 承办人结案理由 + type: string + desc: 承办人结案理由→内容 + - name: 承办人结案日期 + type: date + desc: 承办人结案理由→日期 + - name: 承办人结案签名1 + type: enum + allowed: + - 有 + - 无 + desc: 承办人结案理由→签名1 输出 有/无 + - name: 承办人结案签名2 + type: enum + allowed: + - 有 + - 无 + desc: 承办人结案理由→签名2 输出 有/无 + - name: 承办部门意见 + type: string + desc: 承办部门意见→内容 + - name: 承办部门日期 + type: date + desc: 承办部门意见→日期 + - name: 承办部门签名 + type: enum + allowed: + - 有 + - 无 + desc: 承办部门意见→签名 输出 有/无 + - name: 负责人意见 + type: string + desc: 负责人意见→内容 + - name: 负责人日期 + type: date + desc: 负责人意见→日期 + - name: 负责人签名 + type: enum + allowed: + - 有 + - 无 + desc: 负责人意见→签名 输出 有/无 +- id: 缴款凭证 + name: 缴款凭证 + required: false + classifier: + title_patterns: + - 缴款凭证 + - 广东省非税收入一般缴款书[((]电子[))] + - 广东省非税收入一般缴款书(电子) + keywords: + - 非税收入 + - 缴款书 + - 收费项目 + - 收入项目 + min_score: 0.5 + extract: + - group: 基本信息 + fields: + - name: 收入项目 + type: string + desc: 收入项目名称(电子非税缴款书上可能写作"收费项目") + - name: 金额 + type: money + desc: 金额 + - name: 备注 + type: verbatim + desc: 备注 +- id: 行政处罚事先告知书 + name: 行政处罚事先告知书 + required: true + classifier: + title_patterns: + - 行政处罚事先告知书 + keywords: [] + min_score: 0.5 + extract: + - group: 基本信息 + fields: + - name: 当事人 + type: verbatim + desc: 当事人 + - name: 正文前称呼 + type: string + desc: 正文前称呼 + - name: 权利告知 + type: string + desc: 正文→权利告知 +- id: 证据先行登记保存批准书 + name: 证据先行登记保存批准书 + required: false + classifier: + title_patterns: + - 证据先行登记保存批准书 + keywords: [] + min_score: 0.5 + extract: + - group: 基本信息 + fields: + - name: 标题下方文本 + type: string + desc: 标题下方文本 + - name: 表格下方文字 + type: string + desc: 表格下方文字 含"对先行登记保存的证据,应当在...日内处理" + - name: 表格品规 + type: string + desc: 表格内容→品种规格、单位、数量 + - name: 表格全文 + type: string + desc: 表格完整内容 + - name: 盖章 + type: enum + allowed: + - 有 + - 无 + desc: 行政机关盖章 输出 有/无 + - group: 承办人 + fields: + - name: 承办人日期 + type: date + desc: 承办人→日期 + - name: 承办人签名1 + type: enum + allowed: + - 有 + - 无 + desc: 承办人→签名1 输出 有/无 + - name: 承办人签名2 + type: enum + allowed: + - 有 + - 无 + desc: 承办人→签名2 输出 有/无 + - group: 负责人 + fields: + - name: 负责人意见 + type: verbatim + desc: 负责人意见并签名→意见内容 + - name: 负责人意见有无 + type: enum + allowed: + - 有 + - 无 + desc: 负责人意见并签名→是否有意见 输出 有/无 + - name: 负责人日期 + type: date + desc: 负责人意见并签名→日期 + - name: 负责人签名姓名 + type: verbatim + desc: 负责人意见并签名→签名姓名 + - name: 负责人签名有无 + type: enum + allowed: + - 有 + - 无 + desc: 负责人意见并签名→是否有签名 输出 有/无 +- id: 证据先行登记保存通知书 + name: 证据先行登记保存通知书 + required: false + classifier: + title_patterns: + - 证据先行登记保存通知书 + keywords: [] + min_score: 0.5 + extract: + - group: 基本信息 + fields: + - name: 表格下方文字 + type: string + desc: 表格下方文字 + - name: 表格品规 + type: string + desc: 表格内容→品种规格、单位、数量 + - name: 表格全文 + type: string + desc: 表格完整内容 + - name: 盖章 + type: enum + allowed: + - 有 + - 无 + desc: 行政机关盖章 输出 有/无 + - name: 拒绝签名说明 + type: string + desc: 正文→拒绝签名说明 + - name: 当事人签名 + type: enum + allowed: + - 有 + - 无 + desc: 当事人签名 输出 有/无 + - group: 承办人 + fields: + - name: 承办人日期 + type: date + desc: 承办人→日期 + - name: 承办人签名1 + type: enum + allowed: + - 有 + - 无 + desc: 承办人→签名1 输出 有/无 + - name: 承办人签名2 + type: enum + allowed: + - 有 + - 无 + desc: 承办人→签名2 输出 有/无 +- id: 证据复制(提取)单 + name: 证据复制(提取)单 + required: true + classifier: + title_patterns: + - 证据复制[((]提取[))]单 + - 证据复制(提取)单 + keywords: [] + min_score: 0.5 + extract: + - group: 基本信息 + fields: + - name: 复制时间 + type: verbatim + desc: |- + 证据复制(提取)单每页末尾的「复制(提取)时间」字段。 + 一份卷宗通常有多份证据(每页一份,说明事项各异)。 + 当**多份存在**时,取**第一份**的时间(整体活动起点, + 通常也是询问笔录期间制作的那份)。 + 示例:"2024 年 4 月 19 日 19 时 30 分"。 + - name: 复制地点 + type: verbatim + desc: |- + 证据复制(提取)单每页末尾的「复制(提取)地点」字段。 + 多份存在时取**第一份**的地点(与 复制时间 同一页)。 + 示例:"郁南县南江口镇西江路 124 号"。 + - name: 现场时间 + type: verbatim + desc: |- + 证据复制(提取)单中**现场检查相片**所属那份证据的 + 「复制(提取)时间」。特征:说明事项含"现场/外观/ + 查获时拍摄/查获违法走私卷烟时"等,与执法人员到场 + 同一时段。用于和 现场笔录.检查时间 对齐。 + 如没有纯"现场检查"相片,取第一份时间。 + - name: 现场地址 + type: string + desc: |- + 证据复制(提取)单中**现场检查相片**所属那份证据的 + 「复制(提取)地点」,与 现场时间 同一份。 + 用于和 现场笔录.检查地点 对齐。 + - name: 邮件回执 + type: verbatim + desc: 邮件回执 + - group: 居民身份证 + fields: + - name: 居民身份证 + type: multi_entity + desc: |- + 证据复制(提取)单中**每一张**居民身份证图片对应的一份记录。 + 一份证据复制单通常包含多张身份证(当事人、举报人、未成年人、相关人等), + 请把每张身份证都抽取为数组中的一项,**不要**只抽"当事人那份"。 + 由派生字段「证据复制(提取)单当事人」按姓名+身份证号挑出归属当事人的那一份。 + 如果只有一张身份证,返回只包含一项的数组即可(引擎会自动把那一项判为当事人)。 + fields: + - name: 身份证姓名 + type: verbatim + desc: 该份身份证上印的姓名 + - name: 身份证性别 + type: verbatim + desc: 该份身份证上印的性别(男/女) + - name: 身份证民族 + type: verbatim + desc: 该份身份证上印的民族 + - name: 身份证住址 + type: string + desc: 该份身份证上印的住址 + - name: 身份证号 + type: chinese-id + desc: 该份身份证上印的公民身份号码(18 位) + - name: 身份证背面 + type: enum + allowed: + - 有 + - 无 + desc: 该份身份证是否包含背面(签发机关/有效期那面)有/无 + - group: 许可证 + fields: + - name: 许可证企业名称 + type: string + desc: 烟草专卖零售许可证→企业名称 + - name: 许可证经营场所 + type: string + desc: 烟草专卖零售许可证→经营场所 + - name: 许可证号 + type: verbatim + desc: 烟草专卖零售许可证→许可证号 + - name: 许可证负责人 + type: verbatim + desc: 烟草专卖零售许可证→负责人姓名 + - group: 营业执照 + fields: + - name: 执照名称 + type: string + desc: 营业执照→名称 + - name: 执照住所 + type: string + desc: 营业执照→住所 + - name: 执照法代 + type: verbatim + desc: 营业执照→法定代表人 + - name: 执照统一社会信用代码 + type: uscc + desc: 营业执照→统一社会信用代码 +- id: 询问笔录 + name: 询问笔录 + required: true + classifier: + title_patterns: + - 询问笔录 + keywords: [] + min_score: 0.5 + extract: + - group: 基本信息 + fields: + - name: 询问时间 + type: verbatim + desc: 询问时间 + - name: 询问地点 + type: verbatim + desc: 询问地点 + - group: 被询问人 + fields: + - name: 被询问人 + type: multi_entity + desc: |- + 询问笔录中**每一份**笔录记录对应的被询问人基本信息。 + 一份卷宗可能包含多次询问笔录(针对不同人员),请把**每一份**笔录 + 中的被询问人都抽取为数组中的一项,**不要**只抽"当事人那份"。 + 由派生字段「询问笔录当事人」按姓名+证件号挑出归属当事人的那一份。 + 只有一份被询问人记录时,引擎自动把那一份判为当事人。 + fields: + - name: 被询问人姓名 + type: verbatim + desc: 被询问人→姓名 + - name: 被询问人性别 + type: enum + allowed: + - 男 + - 女 + desc: 被询问人→性别 + - name: 被询问人民族 + type: verbatim + desc: 被询问人→民族 + - name: 被询问人证件 + type: verbatim + desc: 被询问人→证件类型及号码(通常是"居民身份证:xxx") + - name: 被询问人电话 + type: verbatim + desc: 被询问人→联系电话 + - name: 被询问人住址 + type: string + desc: 被询问人→住址 + - name: 被询问人经营地址 + type: string + desc: 被询问人→经营地址 + - group: 笔录正文 + fields: + - name: 执法人员信息 + type: string + desc: 正文→执法人员信息 + - name: 权利告知 + type: string + desc: 正文→权利告知内容 + - name: 被询问人核实 + type: string + desc: 正文→被询问人核实 + - name: 拒绝签名说明 + type: string + desc: 正文→拒绝签名说明 + - group: 签名 + fields: + - name: 被询问人签名 + type: enum + allowed: + - 有 + - 无 + desc: 被询问人(签名)输出 有/无 + - name: 询问人签名1 + type: enum + allowed: + - 有 + - 无 + desc: 询问人(签名)1 输出 有/无 + - name: 询问人签名2 + type: enum + allowed: + - 有 + - 无 + desc: 询问人(签名)2 输出 有/无 +- id: 送达回证 + name: 送达回证 + required: true + classifier: + title_patterns: + - 送达回证 + keywords: [] + min_score: 0.5 + # 注意:本子文档内可能拼接多份送达回证表格(立案通知/先行登记保存通知/事先告知书/ + # 处罚决定书等各一份)。以下字段只抽取"送达文书名称"含"行政处罚决定书"的那份; + # 其他送达回证忽略(由专门的规则处理)。 + extract: + - group: 基本信息 + fields: + - name: 受送达人 + type: verbatim + desc: 本子文档内若含多份送达回证,只抽"送达文书名称"含"行政处罚决定书"那份表格里的受送达人 + - name: 回证编号 + type: verbatim + desc: 本子文档内若含多份送达回证,只抽"送达文书名称"含"行政处罚决定书"那份表格上方的回证编号 + - name: 送达方式 + type: verbatim + desc: 本子文档内若含多份送达回证,只抽"送达文书名称"含"行政处罚决定书"那份的送达方式 + - name: 送达地点 + type: verbatim + desc: 本子文档内若含多份送达回证,只抽"送达文书名称"含"行政处罚决定书"那份的送达地点 + - name: 送达文书名称 + type: string + desc: 本子文档内若含多份送达回证,只抽"送达文书名称"含"行政处罚决定书"的那一项(作为后续其他字段的定位基准) + - name: 送达文书文号 + type: verbatim + desc: 本子文档内若含多份送达回证,只抽"送达文书名称"含"行政处罚决定书"那份的送达文书文号 + - group: 签收 + fields: + - name: 签收日期 + type: date + desc: 本子文档内若含多份送达回证,只抽"送达文书名称"含"行政处罚决定书"那份的签收日期 + - name: 代收理由 + type: string + desc: 本子文档内若含多份送达回证,只抽"送达文书名称"含"行政处罚决定书"那份的代收人代收理由 + - name: 印章 + type: enum + allowed: + - 有 + - 无 + desc: 印章 输出 有/无 + vlm_extract_mode: always + - name: 收件人签名 + type: enum + allowed: + - 有 + - 无 + desc: 收件人签名或盖章→签名 输出 有/无 + vlm_extract_mode: always + - name: 收件人盖章 + type: enum + allowed: + - 有 + - 无 + desc: 收件人签名或盖章→盖章 输出 有/无 + vlm_extract_mode: always + - group: 送达人 + fields: + - name: 送达人签名 + type: enum + allowed: + - 有 + - 无 + desc: 送达人签名 输出 有/无 + vlm_extract_mode: always +rules: +- group: JZG-JD + rules: + - rule_id: JZ-JD-001 + name: 当事人基本情况或立案情况记载准确性 + desc: 若当事人信息与证据复制(提取)单中信息不一致,则扣分。 + risk: medium + score: 5 + scope: + - 案件处理审批表 + - 案件调查终结报告 + - 立案报告表 + - 证据复制(提取)单 + stages: + - id: '1' + check: match + pairs: + - source: 案件处理审批表.案由 + target: 案件调查终结报告.案由 + - source: 案件处理审批表.案件来源 + target: 案件调查终结报告.案件来源 + # 案件来源是开放词汇(投诉举报/群众举报/电话举报/来电举报/上级交办… + # 无穷枚举),不用 canonicalize 字典维护。字面不等时走 rescue L1 + # match 做语义等价判定。 + - source: 案件处理审批表.立案编号 + target: 立案报告表.立案编号 + - source: 案件处理审批表.立案日期 + target: 案件调查终结报告.立案日期 + - source: 案件处理审批表.单位名称 + target: 案件调查终结报告.单位名称 + when: "当事人类型 != '个人'" + - source: 案件调查终结报告.单位名称 + target: 证据复制(提取)单.执照名称 + when: "当事人类型 != '个人'" + - source: 案件处理审批表.单位法代 + target: 案件调查终结报告.单位法代 + when: "当事人类型 != '个人'" + - source: 案件调查终结报告.单位法代 + target: 证据复制(提取)单.执照法代 + when: "当事人类型 != '个人'" + - source: 案件处理审批表.单位地址 + target: 案件调查终结报告.单位地址 + when: "当事人类型 != '个人'" + - source: 案件调查终结报告.单位地址 + target: 证据复制(提取)单.执照住所 + when: "当事人类型 != '个人'" + - id: '2' + check: match + pairs: + - source: 案件处理审批表.案由 + target: 案件调查终结报告.案由 + - source: 案件处理审批表.案件来源 + target: 案件调查终结报告.案件来源 + # 案件来源是开放词汇(投诉举报/群众举报/电话举报/来电举报/上级交办… + # 无穷枚举),不用 canonicalize 字典维护。字面不等时走 rescue L1 + # match 做语义等价判定。 + - source: 案件处理审批表.立案日期 + target: 案件调查终结报告.立案日期 + - source: 案件处理审批表.立案编号 + target: 立案报告表.立案编号 + - source: 案件处理审批表.个人姓名 + target: 案件调查终结报告.个人姓名 + when: "当事人类型 != '单位'" + - source: 案件处理审批表.个人性别 + target: 证据复制(提取)单当事人.身份证性别 + when: "当事人类型 != '单位'" + - source: 案件调查终结报告.个人民族 + target: 证据复制(提取)单当事人.身份证民族 + when: "当事人类型 != '单位'" + - source: 案件调查终结报告.个人证件 + target: 证据复制(提取)单当事人.身份证号 + when: "当事人类型 != '单位'" + method: substring + - source: 案件处理审批表.个人住址 + target: 案件调查终结报告.个人住址 + when: "当事人类型 != '单位'" + - id: '3' + check: ai + prompt: "请根据以下卷宗信息,判断当事人基本情况及立案情况的记载是否准确一致。\n\n 【第一步:判断案件类型】\n\n 检查\"案件处理审批表\"\ + 中的当事人单位名称字段值:\n {{案件处理审批表.单位名称}}\n\n - 如果该值为 \"/\"、\"-\"、空或其他占位符 → 这是**个人案件**,执行个人案件检查\n\ + \ - 如果该值是真实的单位名称 → 这是**单位案件**,执行单位案件检查\n\n ---\n\n 【第二步-A:单位案件检查】(当事人为单位时执行)\n\ + \n 请逐一比对以下字段,判断是否一致:\n\n 1. 案由\n - 案件处理审批表:{{案件处理审批表.案由}}\n - 案件调查终结报告:{{案件调查终结报告.案由}}\n\ + \n 2. 案件来源\n - 案件处理审批表:{{案件处理审批表.案件来源}}\n - 案件调查终结报告:{{案件调查终结报告.案件来源}}\n\n\ + \ 3. 立案编号\n - 案件处理审批表:{{案件处理审批表.立案编号}}\n - 立案报告表:{{立案报告表.立案编号}}\n\n 4. 立案日期\n\ + \ - 案件处理审批表:{{案件处理审批表.立案日期}}\n - 案件调查终结报告:{{案件调查终结报告.立案日期}}\n\n 5. 单位名称(三方核对)\n\ + \ - 案件处理审批表:{{案件处理审批表.单位名称}}\n - 案件调查终结报告:{{案件调查终结报告.单位名称}}\n - 证据复制(提取)单-营业执照:{{证据复制(提取)单.执照名称}}\n\ + \n 6. 法定代表人(三方核对)\n - 案件处理审批表:{{案件处理审批表.单位法代}}\n - 案件调查终结报告:{{案件调查终结报告.单位法代}}\n\ + \ - 证据复制(提取)单-营业执照:{{证据复制(提取)单.执照法代}}\n\n 7. 单位地址(三方核对)\n - 案件处理审批表:{{案件处理审批表.单位地址}}\n\ + \ - 案件调查终结报告:{{案件调查终结报告.单位地址}}\n - 证据复制(提取)单-营业执照:{{证据复制(提取)单.执照住所}}\n\n \ + \ ---\n\n 【第二步-B:个人案件检查】(当事人为个人或个体工商户时执行)\n\n 请逐一比对以下字段,判断是否一致:\n\n 1.\ + \ 案由\n - 案件处理审批表:{{案件处理审批表.案由}}\n - 案件调查终结报告:{{案件调查终结报告.案由}}\n\n 2. 案件来源\n\ + \ - 案件处理审批表:{{案件处理审批表.案件来源}}\n - 案件调查终结报告:{{案件调查终结报告.案件来源}}\n\n 3. 立案编号\n\ + \ - 案件处理审批表:{{案件处理审批表.立案编号}}\n - 立案报告表:{{立案报告表.立案编号}}\n\n 4. 立案日期\n - 案件处理审批表:{{案件处理审批表.立案日期}}\n\ + \ - 案件调查终结报告:{{案件调查终结报告.立案日期}}\n\n 5. 姓名\n - 案件处理审批表:{{案件处理审批表.个人姓名}}\n -\ + \ 案件调查终结报告:{{案件调查终结报告.个人姓名}}\n\n 6. 性别\n - 案件处理审批表:{{案件处理审批表.个人性别}}\n - 证据复制(提取)单-居民身份证:{{证据复制(提取)单当事人.身份证性别}}\n\ + \n 7. 民族\n - 案件调查终结报告:{{案件调查终结报告.个人民族}}\n - 证据复制(提取)单-居民身份证:{{证据复制(提取)单当事人.身份证民族}}\n\ + \n 8. 证件号码(包含匹配)\n - 案件调查终结报告:{{案件调查终结报告.个人证件}}\n - 证据复制(提取)单-居民身份证:{{证据复制(提取)单当事人.身份证号}}\n\ + \ - 注意:审批表中证件字段格式可能为\"居民身份证:44xxxxxxxx\",判断时应提取纯号码部分进行比对\n\n 9. 住址\n - 案件处理审批表:{{案件处理审批表.个人住址}}\n\ + \ - 证据复制(提取)单-居民身份证:{{证据复制(提取)单当事人.身份证住址}}\n\n ---\n\n 【判断规则】\n\n - \"/\"\ + 、\"-\"、\"—\" 等符号代表该字段不适用,不是有效值,遇到此类值的比对项直接跳过\n - 只要有任意一个有效字段不一致,判定为**不通过**\n\ + \ - 所有有效字段均一致(或均为占位符可跳过),判定为**通过**\n" + logic: 1 OR 2 OR 3 + messages: + pass: 文档检查通过,符合规范要求。 + fail: 文档存在以下问题,请修改后重新提交。 + references_laws: + - 《中华人民共和国行政处罚法》第五十九条 + type: ai_rule + - rule_id: JZ-JD-002 + name: 处罚决定书证据列举 + desc: 若找不到"证据:"或者"证据:"之后无内容,则扣分。 + risk: medium + score: 10 + scope: + - 处罚决定书 + stages: + - id: '1' + check: required + field: 处罚决定书.证据列举 + messages: + pass: 处罚决定书已列出相关证据。 + fail: 罚决定书未列出相关证据,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十九条 + type: deterministic + - rule_id: JZ-JD-003 + name: 救济途径或期限告知明确性 + desc: 若未找到文本匹配内容,则扣分。 + risk: medium + score: 5 + scope: + - 处罚决定书 + stages: + - id: '1' + check: required + field: 处罚决定书.救济途径 + messages: + pass: 已告知救济途径和期限。 + fail: 救济途径或期限告知不明确或不正确,请核对。 + references_laws: + - 《中华人民共和国烟草专卖法》第四十一条 + type: deterministic + - rule_id: JZ-JD-004 + name: 行政处罚决定当事人基本情况记载准确性 + desc: 检查首段信息是否填写齐全,若存在未填内容,(字号:可为空),若不齐全,则扣分。 若当事人信息与证据中提取的信息不一致,则扣分。 + risk: medium + score: 10 + scope: + - 处罚决定书 + - 证据复制(提取)单 + stages: + - id: '1' + check: match + pairs: + - source: 处罚决定书.当事人 + target: 证据复制(提取)单.许可证企业名称 + when: "当事人类型 != '个人'" + - source: 处罚决定书.字号 + target: 证据复制(提取)单.执照名称 + when: "当事人类型 != '个人'" + - source: 处罚决定书.统一社会信用代码 + target: 证据复制(提取)单.执照统一社会信用代码 + when: "当事人类型 != '个人'" + - source: 处罚决定书.经营地址 + target: 证据复制(提取)单.许可证经营场所 + when: "当事人类型 != '个人'" + - source: 证据复制(提取)单.许可证经营场所 + target: 证据复制(提取)单.执照住所 + when: "当事人类型 != '个人'" + - id: '2' + check: match + pairs: + - source: 处罚决定书.当事人 + target: 证据复制(提取)单当事人.身份证姓名 + when: "当事人类型 != '单位'" + - source: 处罚决定书.性别 + target: 证据复制(提取)单当事人.身份证性别 + when: "当事人类型 != '单位'" + - source: 处罚决定书.民族 + target: 证据复制(提取)单当事人.身份证民族 + when: "当事人类型 != '单位'" + - source: 处罚决定书.身份证住址 + target: 证据复制(提取)单当事人.身份证住址 + when: "当事人类型 != '单位'" + - source: 处罚决定书.身份证号码 + target: 证据复制(提取)单当事人.身份证号 + when: "当事人类型 != '单位'" + - source: 处罚决定书.经营地址 + target: 证据复制(提取)单.许可证经营场所 + when: "当事人类型 != '个人'" + - source: 证据复制(提取)单.许可证经营场所 + target: 证据复制(提取)单.执照住所 + when: "当事人类型 != '个人'" + - source: 处罚决定书.字号 + target: 证据复制(提取)单.执照名称 + when: "当事人类型 != '个人'" + logic: 1 OR 2 + messages: + pass: 当事人的基本情况记载齐全且准确。 + fail: 当事人的基本情况记载不齐全或不准确,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十九条 + type: deterministic +- group: JZG-SD + rules: + - rule_id: JZ-SD-001 + name: 法定时限送达 + desc: 若处罚决定书文尾的日期与处罚决定书的送达回证中的"签收日期",之间的范围不在法定时限内,则扣分。 + risk: medium + score: 10 + scope: + - 处罚决定书 + - 送达回证 + stages: + - id: '1' + check: required + fields: + - 送达回证.签收日期 + - 处罚决定书.落款日期 + messages: + pass: 文档检查通过,符合规范要求。 + fail: 文档存在以下问题,请修改后重新提交。 + references_laws: + - 《中华人民共和国行政处罚法》第六十一条 + type: deterministic + - rule_id: JZ-SD-002 + name: 送达回证基本信息规范 + desc: 若收件人签名、签收时间、送达人签名、印章任意一项不存在,则扣分 + risk: medium + score: 10 + scope: + - 送达回证 + stages: + - id: '1' + check: required + fields: + - 送达回证.回证编号 + - 送达回证.送达文书名称 + - 送达回证.送达方式 + - 送达回证.签收日期 + messages: + pass: 办案单位印章、送达人签名、收件人签名及签收时间填写规范。 + fail: 填写不规范,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第六十一条 + type: deterministic +- group: JZG-XC + rules: + - rule_id: JZ-XC-001 + name: 现场笔录时间地点完整性 + desc: 若现场笔录中时间或地点未记载,则扣分;若记载的时间与证据提取单中的时间、地点不一致,也扣分。 + risk: medium + score: 10 + scope: + - 现场笔录 + - 证据复制(提取)单 + stages: + # 地点是文字型字段,用确定性 match 足够(fuzzy 可容忍小差异) + - id: '1' + check: match + pairs: + - source: 现场笔录.检查地点 + target: 证据复制(提取)单.现场地址 + method: fuzzy + # 时间是语义型字段 —— 现场笔录.检查时间常是时间段("16:10至17:00"), + # 证据复制(提取)单.现场时间常是时间点("16:20")。不写字符串 parser, + # 直接让 LLM 按业务语义判定(点落在段内视为一致)。 + - id: '2' + check: ai + prompt: | + 判断以下两个时间在业务上是否一致: + + - 现场笔录.检查时间:{{现场笔录.检查时间}} + - 证据复制(提取)单.现场时间:{{证据复制(提取)单.现场时间}} + + 判断原则: + - 若两者都是时间点且值相同 → 一致 + - 若一方是时间段,另一方是时间点,且**点落在段内** → 一致 + - 若两者都是时间段且有重叠 → 一致 + - 若完全无关或对不上 → 不一致 + + 只判时间业务语义,不判格式差异("2024 年 11 月 18 日"和"2024-11-18"视为同日)。 + logic: 1 AND 2 + messages: + pass: 时间地点记录准确。 + fail: 时间地点记录缺失或与实际不一致,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: deterministic + - rule_id: JZ-XC-002 + name: 被检查人基本情况记载完整性-有无 + desc: 被检查人基本情况记载 + risk: medium + score: 10 + scope: + - 现场笔录 + stages: + - id: '1' + check: required + fields: + - 现场笔录.单位名称 + - 现场笔录.单位法代 + - 现场笔录.地址 + - 现场笔录.电话 + - 现场笔录.单位许可证号 + - id: '2' + check: required + fields: + - 现场笔录.个人姓名 + - 现场笔录.个人性别 + - 现场笔录.个人证件 + - 现场笔录.地址 + - 现场笔录.电话 + - id: '3' + check: required + fields: + - 现场笔录.现场负责人 + - 现场笔录.电话 + - 现场笔录.地址 + logic: (1 OR 2) AND 3 + messages: + pass: 被检查人姓名、身份证号、地址、许可证号与证据一致,请检查其余基本信息是否完整准确。 + fail: 被检查人基本情况记录有误或缺失,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: deterministic + - rule_id: JZ-XC-003 + name: 被检查人基本情况记载完整性-一致 + desc: 检查现场笔录中被检查人信息与身份证/营业执照/许可证信息是否一致 + risk: medium + score: 10 + scope: + - 现场笔录 + - 立案报告表 + - 证据复制(提取)单 + stages: + - id: '1' + check: match + pairs: + - source: 现场笔录.单位名称 + target: 证据复制(提取)单.执照名称 + when: "当事人类型 != '个人'" + - source: 现场笔录.单位法代 + target: 证据复制(提取)单.执照法代 + when: "当事人类型 != '个人'" + - source: 现场笔录.单位许可证号 + target: 证据复制(提取)单.许可证号 + when: "当事人类型 != '个人'" + - source: 证据复制(提取)单.许可证企业名称 + target: 证据复制(提取)单.执照名称 + when: "当事人类型 != '个人'" + - source: 证据复制(提取)单.许可证负责人 + target: 证据复制(提取)单.执照法代 + when: "当事人类型 != '个人'" + - source: 立案报告表.单位名称 + target: 证据复制(提取)单.执照名称 + when: "当事人类型 != '个人'" + - source: 立案报告表.单位法代 + target: 证据复制(提取)单.执照法代 + when: "当事人类型 != '个人'" + - source: 立案报告表.单位地址 + target: 证据复制(提取)单.执照住所 + when: "当事人类型 != '个人'" + - id: '2' + check: match + pairs: + - source: 现场笔录.个人姓名 + target: 立案报告表.个人姓名 + when: "当事人类型 != '单位'" + - source: 立案报告表.个人姓名 + target: 证据复制(提取)单当事人.身份证姓名 + when: "当事人类型 != '单位'" + - source: 现场笔录.个人性别 + target: 立案报告表.个人性别 + when: "当事人类型 != '单位'" + - source: 立案报告表.个人性别 + target: 证据复制(提取)单当事人.身份证性别 + when: "当事人类型 != '单位'" + - source: 现场笔录.个人证件 + target: 证据复制(提取)单当事人.身份证号 + when: "当事人类型 != '单位'" + - source: 现场笔录.地址 + target: 立案报告表.个人住址 + when: "当事人类型 != '单位'" + - source: 立案报告表.个人住址 + target: 证据复制(提取)单当事人.身份证住址 + when: "当事人类型 != '单位'" + logic: 1 OR 2 + messages: + pass: 文档检查通过,符合规范要求。 + fail: 文档存在以下问题,请修改后重新提交。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: deterministic + - rule_id: JZ-XC-004 + name: 被检查人签署意见合规性 + desc: 若被检查人拒绝签署意见及姓名,且执法人员未说明情况,则扣分。 + risk: medium + score: 10 + scope: + - 现场笔录 + stages: + - id: '1' + check: required + fields: + - 现场笔录.意见 + - 现场笔录.意见日期 + - 现场笔录.意见签名 + - id: '2' + check: required + field: 现场笔录.意见 + messages: + pass: 被检查人已签署意见及姓名,或执法人员已说明拒绝签署的情况。 + fail: 被检查人拒绝签署但执法人员未说明情况,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: deterministic +- group: JZG-DJ + rules: + - rule_id: JZ-DJ-001 + name: 批准保存时间记载完整性 + desc: 若负责人意见并签名栏后没有日期信息,则扣分。 + risk: medium + score: 5 + scope: + - 证据先行登记保存批准书 + stages: + - id: '1' + check: required + field: 证据先行登记保存批准书.负责人日期 + messages: + pass: 已记载批准保存时间。 + fail: 批准保存时间未记载,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: deterministic + activate_if: 证据先行登记保存批准书 != None + - rule_id: JZ-DJ-002 + name: 证据先行登记保存批准书负责人意见并签名 + desc: 若行政机关负责人没有签署意见或姓名,则扣分。 + risk: medium + score: 5 + scope: + - 证据先行登记保存批准书 + stages: + - id: '1' + check: required + fields: + - 证据先行登记保存批准书.负责人签名姓名 + - 证据先行登记保存批准书.负责人意见 + - 证据先行登记保存批准书.负责人日期 + messages: + pass: 行政机关负责人已签署意见和姓名。 + fail: 行政机关负责人未签署意见或姓名,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十七条 + type: deterministic + activate_if: 证据先行登记保存批准书 != None + - rule_id: JZ-DJ-003 + name: 先行登记保存证据期限记载 + desc: 若没有文中"对先行登记保存的证据,应当在.....日内及时作出处理决定。"的描述,则扣分。 + risk: medium + score: 5 + scope: + - 证据先行登记保存批准书 + - 证据先行登记保存通知书 + stages: + - id: '1' + check: required + fields: + - 证据先行登记保存批准书.表格下方文字 + - 证据先行登记保存通知书.表格下方文字 + messages: + pass: 已注明先行登记保存证据期限和处理决定期限。 + fail: 未注明相关期限,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: deterministic + activate_if: 证据先行登记保存批准书 != None and 证据先行登记保存通知书 != None + - rule_id: JZ-DJ-004 + name: 先行登记保存批准书或通知书文件校验 + desc: 若现场笔录中的情况说明中出现物品名称及规格描述,且文件中无批准书或通知书,则扣分。 + risk: medium + score: 10 + scope: + - 证据先行登记保存批准书 + - 证据先行登记保存通知书 + stages: + - id: '1' + check: required + fields: + - 证据先行登记保存批准书.表格品规 + - 证据先行登记保存通知书.表格品规 + - id: '2' + check: ai + prompt: '请判断以下 {{证据先行登记保存批准书.表格全文}} 和 {{证据先行登记保存通知书.表格全文}} 表述和数量一致 + + ' + messages: + pass: 存在先行登记保存批准书或通知书。 + fail: 缺少先行登记保存批准书或通知书,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: ai_rule + activate_if: 证据先行登记保存批准书 != None and 证据先行登记保存通知书 != None + - rule_id: JZ-DJ-005 + name: 批准书与通知书内容一致性 + desc: 若批准书和通知书内容不一致,则直接扣分;若一致,则与抽样清单中的物品数量进行比对,如果抽样清单中同一品种有多条记录则提示。 若当事人和见证人栏均无签名,则扣分 + risk: medium + score: 5 + scope: + - 证据先行登记保存批准书 + - 证据先行登记保存通知书 + stages: + - id: '1' + check: match + pairs: + - source: 证据先行登记保存通知书.表格品规 + target: 证据先行登记保存批准书.表格品规 + messages: + pass: 批准书与通知书内容一致 + fail: 批准书与通知书内容不一致,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: deterministic + activate_if: 证据先行登记保存批准书 != None and 证据先行登记保存通知书 != None + - rule_id: JZ-DJ-006 + name: 证据先行登记保存批准/通知书承办人签名日期 + desc: 若没有证据先行登记保存批准/通知书承办人签字或盖章,则扣分。 + risk: medium + score: 5 + scope: + - 证据先行登记保存批准书 + - 证据先行登记保存通知书 + stages: + - id: '1' + check: required + fields: + - 证据先行登记保存批准书.承办人日期 + - 证据先行登记保存通知书.承办人日期 + - 证据先行登记保存批准书.承办人签名1 + - 证据先行登记保存批准书.承办人签名2 + - 证据先行登记保存通知书.承办人签名1 + - 证据先行登记保存通知书.承办人签名2 + messages: + pass: 有日期,案件承办人已签字或盖章。 + fail: 缺少印章、日期或承办人签字盖章,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: deterministic + activate_if: 证据先行登记保存批准书 != None and 证据先行登记保存通知书 != None + - rule_id: JZ-DJ-007 + name: 证据先行登记保存批准书负责人意见并签名 + desc: 若没有填写两名承办人意见及签名,负责人意见及签名,则扣分。 + risk: medium + score: 5 + scope: + - 证据先行登记保存批准书 + stages: + - id: '1' + check: required + fields: + - 证据先行登记保存批准书.负责人日期 + - 证据先行登记保存批准书.负责人签名有无 + - 证据先行登记保存批准书.负责人意见有无 + messages: + pass: 两名承办人签名,负责人意见及签名完整。 + fail: 两名承办人签名或负责人意见及签名缺失,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十七条 + type: deterministic + activate_if: 证据先行登记保存批准书 != None + - rule_id: JZ-DJ-008 + name: 保存理由和内容记载完整性 + desc: 若首部没有保存理由描述,表格中没有规格和数量信息,则扣分。 + risk: medium + score: 10 + scope: + - 立案报告表 + - 证据先行登记保存批准书 + stages: + - id: '1' + check: ai + prompt: '{{立案报告表.案由}} + + {{证据先行登记保存批准书.标题下方文本}} + + 案由应该要和标题下方文本同一个意思,案由会比较少字。帮我评查这个案由是否存在 在标题下方文本 中 + + ' + - id: '2' + check: ai + prompt: '{{立案报告表.案情品种}}中提及的具体规格品种、数量应出现在{{证据先行登记保存批准书.表格品规}}中,但案情摘要中不一定会将全部规格品种都写全,评查尺度可以适当放松 + + ' + - id: '3' + check: ai + prompt: '请根据以下信息判断案件类型,对个人(个体工商户)案件单独评查证据先行登记保存批准书内容是否完整。 + + + 当事人-单位-名称: {{立案报告表.单位名称}} + + 当事人-个人(个体工商户)-姓名: {{立案报告表.个人姓名}} + + 证据先行登记保存批准书-表格内容-品种规格、单位、数量: {{证据先行登记保存批准书.表格品规}} + + + 判断逻辑: + + 1. 如果单位-名称为空或为"/",且个人-姓名不为空,则这是个人(个体工商户)案件 + + 2. 对于个人案件:只要证据先行登记保存批准书-表格内容-品种规格、单位、数量有内容(非空);若为空 + + 3. 如果单位-名称有实际值(非空、非"/") + + ' + logic: (1 AND 2) OR 3 + messages: + pass: 已注明保存理由和内容。 + fail: 保存理由和内容未注明,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: ai_rule + activate_if: 证据先行登记保存批准书 != None + - rule_id: JZ-DJ-009 + name: 先行登记保存物品处理通知书当事人签字 + desc: 若通知书中当事人未签字或没有其他内容说明,则扣分。 + risk: medium + score: 5 + scope: + - 证据先行登记保存通知书 + stages: + - id: '1' + check: required + field: 证据先行登记保存通知书.当事人签名 + - id: '2' + check: required + field: 证据先行登记保存通知书.拒绝签名说明 + logic: 1 OR 2 + messages: + pass: 当事人已在先行登记保存物品处理通知书上签字。 + fail: 当事人未签字或i没有情况说明,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: deterministic + activate_if: 证据先行登记保存通知书 != None + - rule_id: JZ-DJ-010 + name: 证据先行登记保存批准书负责人意见并签名 + desc: 检查涉案物品返还清单接收人签名、日期和印章是否完整,并通过正则检查损耗/返还信息 + risk: medium + score: 5 + scope: + - 证据先行登记保存批准书 + stages: + - id: '1' + check: required + fields: + - 证据先行登记保存批准书.负责人意见 + - 证据先行登记保存批准书.负责人签名姓名 + messages: + pass: 文档检查通过,符合规范要求。 + fail: 文档存在以下问题,请修改后重新提交。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: deterministic + activate_if: 证据先行登记保存批准书 != None + - rule_id: JZ-DJ-011 + name: 证据先行登记保存批准/通知书盖章 + desc: 检查先行登记保存批准书和通知书是否加盖行政机关印章 + risk: medium + score: 5 + scope: + - 证据先行登记保存批准书 + - 证据先行登记保存通知书 + stages: + - id: '1' + check: required + fields: + - 证据先行登记保存批准书.盖章 + - 证据先行登记保存通知书.盖章 + messages: + pass: 有行政机关印章 + fail: 缺少印章,请核对 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: deterministic + activate_if: 证据先行登记保存批准书 != None and 证据先行登记保存通知书 != None +- group: JZG-QR + rules: + - rule_id: JZ-QR-001 + name: 陈述申辩权利告知和听取 + desc: 若表述中不包含"享有陈述权和申辩权"、"...日内"、"...视为放弃",任意一项,则扣分, + risk: medium + score: 10 + scope: + - 行政处罚事先告知书 + stages: + - id: '1' + check: required + field: 行政处罚事先告知书.权利告知 + messages: + pass: 已告知当事人陈述申辩权利。 + fail: 未告知当事人陈述申辩相关权力,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第四十四条 + type: deterministic + - rule_id: JZ-QR-002 + name: 行政处罚事先告知对象准确性 + desc: 若告知书首句中的姓名与当事人意见中的签名不一致,则扣分。 + risk: medium + score: 10 + scope: + - 行政处罚事先告知书 + stages: + - id: '1' + check: match + pairs: + - source: 行政处罚事先告知书.当事人 + target: 行政处罚事先告知书.正文前称呼 + messages: + pass: 行政处罚事先告知对象正确。 + fail: 行政处罚事先告知对象错误,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第四十四条 + type: deterministic +- group: JZG-QZ + rules: + - rule_id: JZ-QZ-001 + name: 当事人身份证明提取规范性 + desc: 若没有提取当事人身份证明,则扣分。 + risk: medium + score: 10 + scope: + - 证据复制(提取)单 + stages: + - id: '1' + check: required + fields: + - 证据复制(提取)单当事人.身份证号 + - 证据复制(提取)单当事人.身份证背面 + messages: + pass: 当事人身份证明已规范提取。 + fail: 当事人身份证明提取不规范或缺失,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: deterministic + - rule_id: JZ-QZ-002 + name: 查获物品情况记载准确性、合规性 + desc: 若批准书和通知书内容不一致,则直接扣分;若一致,则与抽样清单中的物品数量进行比对,如果抽样清单中同一品种有多条记录则提示。 若当事人和见证人栏均无签名,则扣分 + risk: medium + score: 10 + scope: + - 抽样取证物品清单 + - 涉案物品核价表 + - 证据先行登记保存批准书 + - 证据先行登记保存通知书 + stages: + - id: '1' + check: ai + prompt: '请判断{{抽样取证物品清单.品种规格}}(若有)或{{涉案物品核价表.核价明细}},以及{{证据先行登记保存批准书.表格品规}}、{{证据先行登记保存通知书.表格品规}}表述和数量一致。 + + 如果{{抽样取证物品清单.品种规格}}、{{涉案物品核价表.核价明细}}都不存在,则只需判断{{证据先行登记保存批准书.表格品规}}和{{证据先行登记保存通知书.表格品规}}的一致性 + + ' + messages: + pass: 查获物品情况、数量及当事人或见证人姓名记录准确。 + fail: 记录不准确或缺失,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: ai_rule + - rule_id: JZ-QZ-003 + name: 核价文书记录的准确性(盖章) + desc: 检查涉案物品核价表是否有涉案卷烟价格管理小组印章 + risk: medium + score: 5 + scope: + - 涉案物品核价表 + stages: + - id: '1' + check: required + field: 涉案物品核价表.核价组印章 + messages: + pass: 已正确加盖印章。 + fail: 印章加盖错误,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: deterministic + activate_if: 涉案物品核价表 != None + - rule_id: JZ-QZ-004 + name: 抽样取证物品清单完整性 + desc: 先行登记保存证据处理通知书"处理"方式选择第2项"送交...鉴定"时,卷宗内没有抽样取证物品清单,则扣分。 + risk: medium + score: 10 + scope: + - 先行登记保存证据处理通知书 + - 抽样取证物品清单 + stages: + - id: '1' + check: required + field: 先行登记保存证据处理通知书.处理方式 + - id: '2' + check: required + fields: + - 抽样取证物品清单.表格有内容 + - 抽样取证物品清单.当事人签名 + logic: (1 AND 2) OR (NOT 1) + messages: + pass: 抽样提取物证时有完整的物品清单。 + fail: 抽样提取物证时缺少物品清单,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: deterministic + activate_if: 先行登记保存证据处理通知书 != None + - rule_id: JZ-QZ-005 + name: 核价文书记录准确性 + desc: 若核价文书或记录中没有准确记载(计算核价结果错误)涉案物品情况,核价错误,则扣分。 + risk: medium + score: 5 + scope: + - 涉案物品核价表 + stages: + - id: '1' + check: ai + prompt: '{{涉案物品核价表.表格全文}} + + 请判断以表格中各品种规格的数量、单价计算的合计金额是否正确,各品种规格合计金额计算总计金额是否正确,请在计算的时候保留小数点后两位 + + ' + messages: + pass: 涉案物件核价表存在 + fail: 涉案物件核价表不存在或者信息内容有误 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: ai_rule + - rule_id: JZ-QZ-006 + name: 价格证明合规性 + desc: 若批准书与通知书内容不一致,核价表中数量与批准书或通知书中不一致,则扣分。 + risk: medium + score: 10 + scope: + - 涉案物品核价表 + - 证据先行登记保存批准书 + - 证据先行登记保存通知书 + stages: + - id: '1' + check: ai + prompt: '请判断以下三个表格物品和数量是否对应 + + {{涉案物品核价表.核价明细}} + + {{证据先行登记保存批准书.表格品规}} + + {{证据先行登记保存通知书.表格品规}} + + ' + messages: + pass: 价格证明符合要求,且有涉案物品核价依据或价格来源。 + fail: 价格证明不符合要求或缺少依据,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: ai_rule +- group: JZG-XW + rules: + - rule_id: JZ-XW-001 + name: 被询问人签署"记录属实"合规性 + desc: 若每页页尾被询问人处没有签名,则扣分;如果最后一页没有手写内容则提示。 + risk: medium + score: 10 + scope: + - 询问笔录 + stages: + - id: '1' + check: required + field: 询问笔录.被询问人核实 + messages: + pass: 被询问人已签署"记录属实"且逐页签名。 + fail: 被询问人未签署或未逐页签名,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十五条 + type: deterministic + - rule_id: JZ-XW-002 + name: 询问笔录合规性 + desc: 通过AI判断询问笔录格式是否符合规范要求 + risk: medium + score: 10 + scope: + - 询问笔录 + stages: + - id: '1' + check: ai + prompt: '请判断以下询问笔录中是否只有一名被询问人。被询问人信息:{{询问笔录当事人.被询问人姓名}} + + ' + messages: + pass: 笔录仅询问一名被询问人。 + fail: 一份笔录询问多名被询问人,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十五条 + type: ai_rule + - rule_id: JZ-XW-003 + name: 执法人员身份表明和权利告知 + desc: 若未在询问开始时表明执法人员身份,并告知当事人享有陈述申辩权和申请回避权,则扣分。 + risk: medium + score: 5 + scope: + - 询问笔录 + stages: + - id: '1' + check: required + fields: + - 询问笔录.执法人员信息 + - 询问笔录.权利告知 + messages: + pass: 执法人员已表明身份并告知相关权利。 + fail: 未表明身份或未告知权利,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十五条 + type: deterministic + - rule_id: JZ-XW-004 + name: 执法人员签名合规性 + desc: 若执法人员没有签名或只有一人签名,则扣分。 + risk: medium + score: 10 + scope: + - 询问笔录 + stages: + - id: '1' + check: required + fields: + - 询问笔录.询问人签名1 + - 询问笔录.询问人签名2 + messages: + pass: 执法人员已签名,且有两人以上签名。 + fail: 执法人员签名缺失或不足两人,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第四十二条 + type: deterministic + - rule_id: JZ-XW-005 + name: 被询问人基本情况记载全面性 + desc: 被询问人基本情况填写不全,或询问时间、地点未准确记载,则扣分。 + risk: medium + score: 5 + scope: + - 证据复制(提取)单 + - 询问笔录 + stages: + - id: '1' + check: match + pairs: + - source: 询问笔录.询问地点 + target: 证据复制(提取)单.复制地点 + - source: 询问笔录当事人.被询问人姓名 + target: 证据复制(提取)单当事人.身份证姓名 + when: "当事人类型 != '单位'" + - source: 询问笔录当事人.被询问人性别 + target: 证据复制(提取)单当事人.身份证性别 + when: "当事人类型 != '单位'" + - source: 询问笔录当事人.被询问人民族 + target: 证据复制(提取)单当事人.身份证民族 + when: "当事人类型 != '单位'" + - source: 询问笔录当事人.被询问人证件 + target: 证据复制(提取)单当事人.身份证号 + when: "当事人类型 != '单位'" + - source: 询问笔录当事人.被询问人住址 + target: 证据复制(提取)单当事人.身份证住址 + when: "当事人类型 != '单位'" + - source: 询问笔录.询问时间 + target: 证据复制(提取)单.复制时间 + method: fuzzy + - source: 询问笔录当事人.被询问人经营地址 + target: 证据复制(提取)单.许可证经营场所 + when: "当事人类型 != '个人'" + messages: + pass: 被询问人基本情况、询问时间地点记录完整准确。 + fail: 记录不完整或不准确,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十五条 + type: deterministic + - rule_id: JZ-XW-006 + name: 被询问人拒绝签署处理合规性 + desc: 检查被询问人拒绝签名时是否有情况说明记录 + risk: medium + score: 10 + scope: + - 询问笔录 + stages: + - id: '1' + check: required + field: 询问笔录.被询问人签名 + - id: '2' + check: required + field: 询问笔录.拒绝签名说明 + logic: 1 OR 2 + messages: + pass: 被询问人已签署或已记载拒绝情况。 + fail: 被询问人未签署且未记录情况说明,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十五条 + type: deterministic +- group: JZG-LA + rules: + - rule_id: JZ-LA-001 + name: 当事人基本情况记载完整、准确 + desc: 若当事人姓名、有效证件号码和地址未记载或与身份证中信息不一致,则扣分。 + risk: medium + score: 10 + scope: + - 立案报告表 + - 证据复制(提取)单 + stages: + - id: '1' + check: required + fields: + - 立案报告表.单位名称 + - 立案报告表.单位法代 + - 立案报告表.单位电话 + - 立案报告表.单位地址 + - id: '2' + check: required + fields: + - 立案报告表.个人姓名 + - 立案报告表.个人性别 + - 立案报告表.个人年龄 + - 立案报告表.个人民族 + - 立案报告表.个人证件 + - 立案报告表.个人电话 + - 立案报告表.个人住址 + - id: '3' + check: match + pairs: + - source: 立案报告表.个人姓名 + target: 证据复制(提取)单当事人.身份证姓名 + when: "当事人类型 != '单位'" + - source: 立案报告表.个人性别 + target: 证据复制(提取)单当事人.身份证性别 + when: "当事人类型 != '单位'" + - source: 立案报告表.个人民族 + target: 证据复制(提取)单当事人.身份证民族 + when: "当事人类型 != '单位'" + - source: 立案报告表.个人住址 + target: 证据复制(提取)单当事人.身份证住址 + when: "当事人类型 != '单位'" + - source: 立案报告表.个人证件 + target: 证据复制(提取)单当事人.身份证号 + when: "当事人类型 != '单位'" + - id: '4' + check: match + pairs: + - source: 立案报告表.单位名称 + target: 证据复制(提取)单.执照名称 + when: "当事人类型 != '个人'" + - source: 立案报告表.单位法代 + target: 证据复制(提取)单.执照法代 + when: "当事人类型 != '个人'" + - source: 立案报告表.单位地址 + target: 证据复制(提取)单.执照住所 + when: "当事人类型 != '个人'" + - id: '5' + check: match + pairs: + - source: 立案报告表.个人姓名 + target: 证据复制(提取)单.执照法代 + when: "当事人类型 != '个人'" + - source: 立案报告表.个人住址 + target: 证据复制(提取)单.执照住所 + when: "当事人类型 != '个人'" + - id: '6' + check: ai + prompt: '请根据以下信息判断案件类型(个人案件或单位案件),并评查当事人基本情况是否记载完整。 + + + 当事人-单位-名称: {{立案报告表.单位名称}} + + 当事人-单位-法定代表人(负责人): {{立案报告表.单位法代}} + + 当事人-个人(个体工商户)-姓名: {{立案报告表.个人姓名}} + + 当事人-个人(个体工商户)-性别: {{立案报告表.个人性别}} + + 当事人-个人(个体工商户)-年龄: {{立案报告表.个人年龄}} + + 当事人-个人(个体工商户)-民族: {{立案报告表.个人民族}} + + 当事人-个人(个体工商户)-证件类型及号码: {{立案报告表.个人证件}} + + 当事人-个人(个体工商户)-联系电话: {{立案报告表.个人电话}} + + 当事人-个人(个体工商户)-住址: {{立案报告表.个人住址}} + + + 判断逻辑: + + 1. 如果单位-名称为空或为"/",且个人-姓名不为空,则这是个人(个体工商户)案件 + + 2. 对于个人案件:检查个人字段(姓名、性别、年龄、民族、证件类型及号码、联系电话、住址)是否都不为空—— + + 3. 如果单位-名称有实际值(非空、非"/") + + ' + logic: (1 AND 4) OR 6 + messages: + pass: 当事人基本情况记录完整,与身份证信息一致。 + fail: 当事人基本情况记录有误或缺失,请核对。 + references_laws: + - 《中华人民共和国烟草专卖法》第三十八条 + type: ai_rule + - rule_id: JZ-LA-002 + name: 案由、发案时间和发案地点记载准确性-有无 + desc: 若案由、发案时间和发案地点未记载或错误记载,则扣分。 + risk: medium + score: 10 + scope: + - 立案报告表 + stages: + - id: '1' + check: required + field: 立案报告表.案由 + messages: + pass: 案由、发案时间和发案地点记录准确。 + fail: 案由、发案时间和发案地点记录有误或缺失,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十四条 + type: deterministic + - rule_id: JZ-LA-003 + name: 案件来源有无一致性校验 + desc: 若三处文档中的案件来源信息不一致或者存在未填写的情况,则扣分。 + risk: medium + score: 5 + scope: + - 案件处理审批表 + - 案件调查终结报告 + - 立案报告表 + stages: + - id: '1' + check: required + fields: + - 立案报告表.案件来源 + - 案件处理审批表.案件来源 + - 案件调查终结报告.案件来源 + - id: '2' + check: match + pairs: + - source: 立案报告表.案件来源 + target: 案件处理审批表.案件来源 + - source: 案件处理审批表.案件来源 + target: 案件调查终结报告.案件来源 + # 案件来源是开放词汇(投诉举报/群众举报/电话举报/来电举报/上级交办… + # 无穷枚举),不用 canonicalize 字典维护。字面不等时走 rescue L1 + # match 做语义等价判定。 + messages: + pass: 案件来源完整 + fail: 没有记载案件来源或案件来源与其他文书不一致,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十四条 + type: deterministic + - rule_id: JZ-LA-004 + name: 案由、发案时间和发案地点记载准确性-一致 + desc: 检查立案报告表案发时间/地点与现场笔录检查时间/地点是否一致 + risk: medium + score: 10 + scope: + - 现场笔录 + - 立案报告表 + stages: + - id: '1' + check: required + fields: + - 立案报告表.案发时间 + - 立案报告表.案发地点 + - 现场笔录.检查时间 + - 现场笔录.检查地点 + - id: '2' + check: match + pairs: + - source: 立案报告表.案发时间 + target: 现场笔录.检查时间 + method: substring + - id: '3' + check: match + pairs: + - source: 现场笔录.检查地点 + target: 立案报告表.案发地点 + messages: + pass: 案由、发案时间和发案地点记录准确。 + fail: 案由、发案时间和发案地点记录有误或缺失,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: deterministic + - rule_id: JZ-LA-005 + name: 承办人和承办部门意见 + desc: 承办人栏无描述、无签名、承办部门处无描述、无签名,出现任一一项则扣分。 + risk: medium + score: 5 + scope: + - 立案报告表 + stages: + - id: '1' + check: required + fields: + - 立案报告表.承办部门意见 + - 立案报告表.承办部门日期 + - 立案报告表.承办人意见 + - 立案报告表.承办人日期 + - 立案报告表.承办部门签名 + - 立案报告表.承办人签名2 + - 立案报告表.承办人签名1 + messages: + pass: 承办人和承办部门意见及签名完整。 + fail: 承办人和承办部门意见及签名存在缺失,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十四条 + type: deterministic + - rule_id: JZ-LA-006 + name: 行政机关负责人明确意见、签字和日期 + desc: 若"负责人意见"栏中存在"不同意"或"不同意和意见描述",留空则扣分。;负责人意见栏无描述、无签名、无日期,出现任一一项则扣分。 + risk: medium + score: 10 + scope: + - 立案报告表 + stages: + - id: '1' + check: required + fields: + - 立案报告表.负责人意见 + - 立案报告表.负责人签名 + messages: + pass: 行政机关负责人意见、签字和日期完整。 + fail: 行政机关负责人意见、签字和日期缺失,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十七条 + type: deterministic + - rule_id: JZ-LA-007 + name: 立案文书完整性检查(签名) + desc: 检查立案报告表负责人意见处是否有签名 + risk: medium + score: 10 + scope: + - 立案报告表 + stages: + - id: '1' + check: required + field: 立案报告表.负责人签名 + messages: + pass: 文档检查通过,符合规范要求。 + fail: 文档存在以下问题,请修改后重新提交。 + references_laws: + - 《中华人民共和国行政处罚法》第五十七条 + type: deterministic + - rule_id: JZ-LA-008 + name: 案件情况清晰 + desc: 通过AI检查立案报告表案由和案情摘要表述是否清晰;若案件情况描述中,需出现案件时间、货物名称、(案由描述+条款引用)中所有信息,未出现任一一项则扣分。 + risk: low + score: 1 + scope: + - 立案报告表 + stages: + - id: '1' + check: ai + prompt: | + 检查 案情摘要 是否覆盖以下 4 项要素(任一缺失才扣分): + 1. 案件时间(检查/发案时间) + 2. 涉案货物名称或品种 + 3. 案由描述(违法行为的事实陈述) + 4. 相关条款或法律依据的引用 + + 案由:{{立案报告表.案由}} + 案情摘要:{{立案报告表.案情摘要}} + + 判定规则: + - 4 项要素齐全 → pass + - 有缺项 → fail + - **不要**对文字风格、段落重复、句式冗余等格式问题扣分,只看内容是否齐全。 + messages: + pass: 案件情况描述清晰。 + fail: 案件情况记录不清晰或缺失,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十四条 + type: ai_rule +- group: JZG-ZJ + rules: + - rule_id: JZ-ZJ-001 + name: 调查终结报告文件校验 + desc: 若没有调查终结报告,则扣分 + risk: medium + score: 10 + scope: + - 案件调查终结报告 + stages: + - id: '1' + check: required + field: 案件调查终结报告.案由 + messages: + pass: 存在完整的调查终结报告。 + fail: 缺少调查终结报告,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十七条 + type: deterministic + - rule_id: JZ-ZJ-002 + name: 案由、立案时间和当事人基本情况记载 + desc: 若当事人信息与提取出的信息不一致,则扣分。 + risk: medium + score: 5 + scope: + - 案件调查终结报告 + - 证据复制(提取)单 + stages: + - id: '1' + check: required + fields: + - 案件调查终结报告.案件来源 + - 案件调查终结报告.案由 + - 案件调查终结报告.立案日期 + - 案件调查终结报告.单位名称 + - 案件调查终结报告.单位法代 + - 案件调查终结报告.单位电话 + - 案件调查终结报告.单位地址 + - id: '2' + check: required + fields: + - 案件调查终结报告.案件来源 + - 案件调查终结报告.案由 + - 案件调查终结报告.立案日期 + - 案件调查终结报告.个人姓名 + - 案件调查终结报告.个人性别 + - 案件调查终结报告.个人年龄 + - 案件调查终结报告.个人民族 + - 案件调查终结报告.个人电话 + - 案件调查终结报告.个人证件 + - 案件调查终结报告.个人住址 + - id: '3' + check: match + pairs: + - source: 案件调查终结报告.单位名称 + target: 证据复制(提取)单.执照名称 + when: "当事人类型 != '个人'" + - source: 案件调查终结报告.单位法代 + target: 证据复制(提取)单.执照法代 + when: "当事人类型 != '个人'" + - source: 案件调查终结报告.单位地址 + target: 证据复制(提取)单.执照住所 + when: "当事人类型 != '个人'" + - id: '4' + check: match + pairs: + - source: 案件调查终结报告.个人姓名 + target: 证据复制(提取)单当事人.身份证姓名 + when: "当事人类型 != '单位'" + - source: 案件调查终结报告.个人性别 + target: 证据复制(提取)单当事人.身份证性别 + when: "当事人类型 != '单位'" + - source: 案件调查终结报告.个人民族 + target: 证据复制(提取)单当事人.身份证民族 + when: "当事人类型 != '单位'" + - source: 案件调查终结报告.个人住址 + target: 证据复制(提取)单当事人.身份证住址 + when: "当事人类型 != '单位'" + - source: 案件调查终结报告.个人证件 + target: 证据复制(提取)单当事人.身份证号 + when: "当事人类型 != '单位'" + logic: (1 AND 3) OR (2 AND 4) + messages: + pass: 当事人基本情况记载准确。请检查案后及时间是否正确。 + fail: 记载不准确或缺失,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十七条 + type: deterministic + - rule_id: JZ-ZJ-003 + name: 当事人基本情况记载-一致 + desc: 检查调查终结报告中当事人基本信息与身份证信息是否一致 + risk: medium + score: 1 + scope: + - 案件调查终结报告 + - 证据复制(提取)单 + stages: + - id: '1' + check: match + pairs: + - source: 案件调查终结报告.个人姓名 + target: 证据复制(提取)单当事人.身份证姓名 + - source: 案件调查终结报告.个人性别 + target: 证据复制(提取)单当事人.身份证性别 + - source: 案件调查终结报告.个人民族 + target: 证据复制(提取)单当事人.身份证民族 + - source: 案件调查终结报告.个人住址 + target: 证据复制(提取)单当事人.身份证住址 + - source: 案件调查终结报告.个人证件 + target: 证据复制(提取)单当事人.身份证号 + messages: + pass: 文档检查通过,符合规范要求。 + fail: 文档存在以下问题,请修改后重新提交。 + references_laws: + - 《中华人民共和国行政处罚法》第五十七条 + type: deterministic + - rule_id: JZ-ZJ-004 + name: 案件调查终结报告承办人及承办部门负责人签字日期 + desc: 若没有承办人及承办人负责人签字、或者没有签字日期,则扣分。 + risk: medium + score: 5 + scope: + - 案件调查终结报告 + stages: + - id: '1' + check: required + fields: + - 案件调查终结报告.处理意见日期 + - 案件调查终结报告.处理意见承办人签名1 + - 案件调查终结报告.处理意见承办人签名2 + messages: + pass: 承办人及承办部门负责人已签字并签署日期。 + fail: 缺少签字或日期,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十七条 + type: deterministic +- group: JZG-CL + rules: + - rule_id: JZ-CL-001 + name: 法制部门或法制员意见明确性 + desc: 若法制部门意见栏无文字描述内容,则扣分。 + risk: medium + score: 10 + scope: + - 案件处理审批表 + stages: + - id: '1' + check: required + fields: + - 案件处理审批表.法制部门意见 + - 案件处理审批表.法制部门日期 + - 案件处理审批表.法制部门审核人签名 + - 案件处理审批表.法制部门负责人签名 + messages: + pass: 法制部门或法制员意见明确。 + fail: 法制部门或法制员意见缺失或不明确,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十八条 + type: deterministic + - rule_id: JZ-CL-002 + name: 案件处理审批表承办人意见和签名 + desc: 若承办人意见栏中无文字内容或无签名日期,则扣分。 + risk: medium + score: 5 + scope: + - 案件处理审批表 + stages: + - id: '1' + check: required + fields: + - 案件处理审批表.承办人意见 + - 案件处理审批表.承办人日期 + - 案件处理审批表.承办部门意见 + - 案件处理审批表.承办部门日期 + - 案件处理审批表.承办部门签名 + - 案件处理审批表.承办人签名1 + - 案件处理审批表.承办人签名2 + messages: + pass: 承办人意见和签名完整。 + fail: 缺少承办人意见或签名,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十八条 + type: deterministic + - rule_id: JZ-CL-003 + name: 案件处理审批表负责人审批意见明确性 + desc: 检查案件处理审批表负责人审批意见内容和日期是否完整 + risk: medium + score: 10 + scope: + - 案件处理审批表 + stages: + - id: '1' + check: required + fields: + - 案件处理审批表.负责人日期 + - 案件处理审批表.负责人意见 + - 案件处理审批表.负责人签名 + messages: + pass: 行政机关负责人审批意见明确,签名和审批时间规范。 + fail: 审批意见不明确或签名审批时间不规范,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十七条 + type: deterministic +- group: JZG-ZG + rules: + - rule_id: JZ-ZG-001 + name: 行政处罚事先告知书送达 + desc: 若送达方式为"直接送达",则收件人签名或盖章栏无信息,则扣分。 若送达方式为"邮寄送达",则校验证据复制(提取)中是否有邮件回执,若不存在,则扣分。 + risk: medium + score: 10 + scope: + - 证据复制(提取)单 + - 送达回证 + stages: + - id: '1' + check: contains + field: 送达回证.送达方式 + value: 直接送达 + - id: '2' + check: contains + field: 送达回证.送达方式 + value: 邮寄送达 + - id: '3' + check: required + field: 证据复制(提取)单.邮件回执 + logic: 1 OR (2 AND 3) + messages: + pass: 事先告知书已送达当事人。 + fail: 事先告知书可能未送达当事人,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第六十一条 + type: deterministic +- group: JZG-ZX + rules: + - rule_id: JZ-ZX-001 + name: 罚款、没收违法所得处罚执行规范性 + desc: 若不存在《缴款凭证》(含《广东省非税收入一般缴款书(电子)》及其收款证明等任何形式的缴款凭证),则扣分。若缴款书中金额与处罚决定书中金额总计不一致,则扣分。 + risk: medium + score: 10 + scope: + - 处罚决定书 + - 缴款凭证 + stages: + - id: '1' + check: required + fields: + - 缴款凭证.金额 + - 缴款凭证.收入项目 + - 处罚决定书.罚款项目 + - 处罚决定书.罚款基数 + - 处罚决定书.罚款比例 + - 处罚决定书.罚款总额 + - id: '2' + check: ai + prompt: '请分析{{处罚决定书.罚款项目}}对应{{处罚决定书.罚款基数}}乘{{处罚决定书.罚款比例}},计算并校对与{{处罚决定书.罚款总额}}一致,同时{{处罚决定书.罚款总额}}与{{缴款凭证.金额}}需一致 + + ' + messages: + pass: 罚款、没收违法所得处罚已开具缴款书,有银行缴费收款证明,且与处罚决定书一致。 + fail: 未开具缴款书或无银行缴费证明,或与处罚决定书不一致,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第六十六条、第六十七条 + type: ai_rule + activate_if: 缴款凭证 != None + - rule_id: JZ-ZX-002 + name: 发还当事人物品与先行登记保存物品-一致 + desc: 若两份文件表格中,数量不一致,则涉案物品返还清单中备注一列需要有内容,没有内容则扣分。 + risk: medium + score: 10 + scope: + - 涉案物品返还清单 + - 证据先行登记保存批准书 + stages: + - id: '1' + check: ai + prompt: '{{证据先行登记保存批准书.表格品规}}和{{涉案物品返还清单.返还明细}}表格中的物品和数量应当一致,若 涉案物品返还清单表格中的具体的品种规格和数量行列数据不一致,则通过涉案物品返还清单的备注的内容进一步判断是否一致(即数量+损耗数量) + + ' + messages: + pass: 发还物品与先行登记保存物品一致,或不一致时已说明原因。 + fail: 发还物品与先行登记保存物品不一致且未说明原因,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: ai_rule + - rule_id: JZ-ZX-003 + name: 损耗费用返还合规性 + desc: 若签名或盖章不存在,或日期未填写,则扣分。 + risk: medium + score: 10 + scope: + - 卷宗封面 + - 涉案物品返还清单 + stages: + - id: '1' + check: contains + field: 卷宗封面.处理结果 + value: 销毁 + - id: '2' + check: required + field: 卷宗封面.处理结果 + - id: '3' + check: required + fields: + - 涉案物品返还清单.日期 + - 涉案物品返还清单.补偿信息 + - 涉案物品返还清单.返还确认 + - 涉案物品返还清单.接收人签名 + - id: '4' + check: required + fields: + - 涉案物品返还清单.日期 + - 涉案物品返还清单.接收单位印章 + - 涉案物品返还清单.补偿信息 + - 涉案物品返还清单.返还确认 + logic: (1 AND 2) OR ((NOT 1) AND 2 AND (3 OR 4)) + messages: + pass: 已全部返还留样卷烟或鉴别检验损耗费用。 + fail: 未全部返还,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十六条 + type: deterministic + activate_if: 涉案物品返还清单 != None or (卷宗封面 != None and 卷宗封面.处理结果 != None) + - rule_id: JZ-ZX-004 + name: 缴款凭证填写规范性 + desc: 若处罚中有没收而文件中不存在没收收据,则扣分。 + risk: medium + score: 5 + scope: + - 处罚决定书 + - 缴款凭证 + stages: + - id: '1' + check: required + fields: + - 处罚决定书.罚款说明 + - 缴款凭证.备注 + messages: + pass: 存在缴款凭证,请进一步确认填写是否规范。 + fail: 未找到缴款凭证,请核对文书是否齐全 + references_laws: + - 《中华人民共和国行政处罚法》第六十七条 + type: deterministic + activate_if: 缴款凭证 != None +- group: JZG-JA + rules: + - rule_id: JZ-JA-001 + name: 当事人名称、违法事实和处罚内容记载准确性 + desc: 若两份文书中的当事人名称不一致,则扣分。 + risk: medium + score: 10 + scope: + - 处罚决定书 + - 结案报告表 + stages: + - id: '1' + check: match + pairs: + - source: 结案报告表.当事人 + target: 处罚决定书.当事人 + messages: + pass: 当事人名称、处罚内容记载一致,请进一步检查违法事实是否一致。 + fail: 当事人记载不准确,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十九条 + type: deterministic + - rule_id: JZ-JA-002 + name: 行政处罚决定的执行结果记载 + desc: 若执行情况栏后不存在描述内容,则扣分。 + risk: medium + score: 10 + scope: + - 结案报告表 + stages: + - id: '1' + check: required + field: 结案报告表.执行情况 + messages: + pass: 行政处罚决定的执行结果存在对应记载内容。 + fail: 执行结果记载不准确,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第七十一条 + type: deterministic + - rule_id: JZ-JA-003 + name: 结案意见、签名及其时间填写规范性 + desc: 若承办人、承办机构负责人和办案单位负责人的意见、签名及其时间任意一项未找到,则扣分。 + risk: medium + score: 10 + scope: + - 结案报告表 + stages: + - id: '1' + check: required + fields: + - 结案报告表.承办人结案理由 + - 结案报告表.承办人结案日期 + - 结案报告表.承办部门意见 + - 结案报告表.承办部门日期 + - 结案报告表.负责人意见 + - 结案报告表.负责人日期 + - 结案报告表.负责人签名 + - 结案报告表.承办人结案签名1 + - 结案报告表.承办人结案签名2 + - 结案报告表.承办部门签名 + messages: + pass: 意见、签名及其时间填写规范。 + fail: 填写不规范,请核对并更正。 + references_laws: + - 《中华人民共和国行政处罚法》第五十四条 + type: deterministic + - rule_id: JZ-JA-004 + name: 结案后按期立卷归档 + desc: 通过AI检查结案后是否在10日内立卷归档 + risk: medium + score: 10 + scope: + - 卷内备考表 + - 结案报告表 + stages: + - id: '1' + check: ai + prompt: '请你判断{{卷内备考表.立卷时间}}与{{结案报告表.负责人日期}}是否相差小于10天 + + ' + messages: + pass: 结案后已按期立卷归档。 + fail: 结案后未按期立卷归档,请核对。 + references_laws: + - 《烟草专卖行政处罚程序规定》 + type: ai_rule diff --git a/387f06411f279cd1d2ea98aacc5a6c1f.png b/387f06411f279cd1d2ea98aacc5a6c1f.png new file mode 100644 index 0000000..3bad85a Binary files /dev/null and b/387f06411f279cd1d2ea98aacc5a6c1f.png differ diff --git a/app.toml b/app.toml index 877bfa3..9fb56b4 100644 --- a/app.toml +++ b/app.toml @@ -80,3 +80,14 @@ PAGE_QUALITY_QUEUE_NORMAL = "leaudit.page_quality.normal" RUN_LOCK_SECONDS = 1800 TASK_SOFT_TIME_LIMIT = 3300 TASK_TIME_LIMIT = 3600 + +[QICHACHA] +APP_KEY = "738678a1fc4942f3b459d76f64fd4fc4" +SECRET_KEY = "F0FB2336E79770EFDC1F03530ED27C57" +BASE_URL = "https://api.qichacha.com" +ENTERPRISE_PATH = "/ECIV4/GetBasicDetailsByName" +DISHONESTY_PATH = "/ShixinCheck/GetList" +TIMEOUT = 30 +MAX_RETRIES = 3 +RETRY_DELAY = 1 +CACHE_DAYS = 30 diff --git a/docs/prototypes/review-side-drawer-preview.html b/docs/prototypes/review-side-drawer-preview.html new file mode 100644 index 0000000..52eb888 --- /dev/null +++ b/docs/prototypes/review-side-drawer-preview.html @@ -0,0 +1,971 @@ + + + + + + 评查详情纵向抽屉原型 + + + +
+
+
+ 合同评查详情 - 采购合同示例.docx + 合同编号:HT-2026-0523 | DOCX | 18页 | 当前布局为预览原型 +
+
+ + +
+
+ + + +
+
+ +
+ + +
+
+ 文档预览 + 缩放 100% | 当前页 2 / 18 +
+
+
+

采购合同

+

甲方:某某烟草公司

+

乙方:某某供应商有限公司

+

经评查发现,乙方统一社会信用代码未在合同主体信息中完整展示,建议补充完整后再提交确认。

+

双方根据相关法律法规,经友好协商,就采购事项达成本合同。

+

付款方式:合同签订后支付 30%,验收通过后支付 60%,质保期满后支付 10%。

+

违约责任:任一方违反本合同约定,应承担相应违约责任。

+
+
+
+ + +
+
+ +
+ + +
+ + + + diff --git a/docs/superpowers/plans/2026-05-22-rag-dsl-bridge-merge.md b/docs/superpowers/plans/2026-05-22-rag-dsl-bridge-merge.md new file mode 100644 index 0000000..1fe6660 --- /dev/null +++ b/docs/superpowers/plans/2026-05-22-rag-dsl-bridge-merge.md @@ -0,0 +1,407 @@ +# RAG DSL Bridge Merge Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Safely bring `gitea/jande-feature-dsl` RAG-backed rule DSL execution into current `wren-dev` without overwriting tenant, permission, or page-quality work already present. + +**Architecture:** Add a shared `RagRetriever` service that both RAG chat and the leaudit evaluation pipeline can call. Wire the pipeline to pass a retriever into `leaudit.evaluate_extraction`, where the installed/local `leaudit` package already supports `stage.rag.query_template`. Keep current tenant-aware RAG chat code as the source of truth and only replace duplicate retrieval helpers with calls into the shared retriever. + +**Tech Stack:** Python 3.12, FastAPI, SQLAlchemy async sessions, Chroma, httpx, pytest, leaudit DSL/evaluation engine. + +--- + +## File Structure + +- Create `fastapi_modules/fastapi_leaudit/rag_engine/retriever.py`: shared RAG retrieval implementation copied from `gitea/jande-feature-dsl`, with vector search, keyword fallback, source filtering, document hydration, and source formatting. +- Modify `fastapi_modules/fastapi_leaudit/leaudit_bridge/pipeline.py`: inject `RagRetriever` into `LauditPipeline` and pass it to `evaluate_extraction`. +- Modify `fastapi_modules/fastapi_leaudit/services/impl/ragChatServiceImpl.py`: preserve current tenant-aware chat API and delegate retrieval internals to `RagRetriever`. +- Modify `rules/行政处罚/rules.yaml`: append only the new `JZ-JD-005` RAG-backed rule from `gitea/jande-feature-dsl`; do not rewrite existing rules. +- Create `tests/test_rag_retriever.py`: validates vector retrieval source filtering and keyword fallback. +- Create `tests/test_leaudit_rag_bridge.py`: validates pipeline passes the injected retriever to leaudit evaluation. + +## Execution Constraints + +- Do not run `git merge gitea/jande-feature-dsl` in the working tree. +- Do not touch current untracked files unless explicitly requested. +- Do not overwrite `fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py`; it has unrelated page-quality work in progress. +- Stage and commit only after explicit user approval. +- If `ragChatServiceImpl.py` behavior changes beyond retrieval helper delegation, stop and re-review before continuing. + +### Task 1: Copy Shared Retriever And Tests + +**Files:** +- Create: `fastapi_modules/fastapi_leaudit/rag_engine/retriever.py` +- Create: `tests/test_rag_retriever.py` +- Create: `tests/test_leaudit_rag_bridge.py` + +- [ ] **Step 1: Restore files from source branch** + +Run: + +```bash +git checkout gitea/jande-feature-dsl -- fastapi_modules/fastapi_leaudit/rag_engine/retriever.py tests/test_rag_retriever.py tests/test_leaudit_rag_bridge.py +``` + +Expected: three files appear in working tree; no existing tenant code is modified. + +- [ ] **Step 2: Verify copied files are isolated** + +Run: + +```bash +git status --short fastapi_modules/fastapi_leaudit/rag_engine/retriever.py tests/test_rag_retriever.py tests/test_leaudit_rag_bridge.py +``` + +Expected: + +```text +A fastapi_modules/fastapi_leaudit/rag_engine/retriever.py +A tests/test_leaudit_rag_bridge.py +A tests/test_rag_retriever.py +``` + +### Task 2: Wire Retriever Into Pipeline + +**Files:** +- Modify: `fastapi_modules/fastapi_leaudit/leaudit_bridge/pipeline.py` +- Test: `tests/test_leaudit_rag_bridge.py` + +- [ ] **Step 1: Inspect current pipeline imports and constructor** + +Run: + +```bash +sed -n '1,120p' fastapi_modules/fastapi_leaudit/leaudit_bridge/pipeline.py +``` + +Expected: confirm current imports include `StorageAdapter` and constructor currently has `ocr_client`, `llm_client`, `storage_adapter`. + +- [ ] **Step 2: Add retriever import** + +Add this import next to the existing platform imports: + +```python +from fastapi_modules.fastapi_leaudit.rag_engine.retriever import RagRetriever +``` + +- [ ] **Step 3: Extend constructor without changing existing arguments** + +Change constructor signature to include: + +```python + rag_retriever: RagRetriever | None = None, +``` + +Then initialize: + +```python + self.rag_retriever = rag_retriever or RagRetriever() +``` + +- [ ] **Step 4: Pass retriever to evaluation** + +In the `evaluate_extraction(...)` call, add: + +```python + retriever=self.rag_retriever, +``` + +Expected: this is a pure dependency-injection change; OCR, extraction, storage, and tenant behavior are unchanged. + +### Task 3: Preserve Tenant-Aware RAG Chat And Delegate Retrieval + +**Files:** +- Modify: `fastapi_modules/fastapi_leaudit/services/impl/ragChatServiceImpl.py` + +- [ ] **Step 1: Inspect current import area and constructor** + +Run: + +```bash +sed -n '1,110p' fastapi_modules/fastapi_leaudit/services/impl/ragChatServiceImpl.py +``` + +Expected: current file imports `build_openai_embeddings_url`, `get_chroma`, and `TenantResolver`; constructor initializes `self.TenantResolver`. + +- [ ] **Step 2: Update imports minimally** + +Remove unused direct retrieval imports after delegation: + +```python + build_openai_embeddings_url, +``` + +Remove: + +```python +from fastapi_modules.fastapi_leaudit.rag_engine.chroma_client import get_chroma +``` + +Add: + +```python +from fastapi_modules.fastapi_leaudit.rag_engine.retriever import RagRetriever +``` + +Keep: + +```python +from fastapi_modules.fastapi_leaudit.services.impl.tenantResolver import TenantResolver +``` + +- [ ] **Step 3: Preserve constructor and add retriever** + +Change constructor to: + +```python + def __init__(self, retriever: RagRetriever | None = None) -> None: + self.TenantResolver = TenantResolver() + self.retriever = retriever or RagRetriever() +``` + +Expected: existing tenant resolver remains active. + +- [ ] **Step 4: Replace `_retrieve_context` implementation only** + +Replace the body of `_retrieve_context` with: + +```python + result = await self.retriever.retrieve(query=query, dataset_id=dataset_id) + return result.chunks, result.dataset_name +``` + +Do not change method signature. + +- [ ] **Step 5: Delegate `_embed_texts`** + +Replace the body of `_embed_texts` with: + +```python + return await self.retriever._embed_texts(texts, model_name) +``` + +- [ ] **Step 6: Delegate keyword fallback helper** + +Replace the body of `_keyword_retrieve_context` with: + +```python + chunks = await self.retriever._keyword_retrieve_context( + dataset_id=dataset_id, + collection_name=collection_name, + dataset_name=dataset_name, + query=query, + top_k=top_k, + score_threshold=score_threshold, + source_names=None, + ) + return chunks[:top_k] +``` + +- [ ] **Step 7: Delegate keyword utility methods** + +Replace helper bodies: + +```python + def _build_keyword_terms(self, query: str) -> list[str]: + return self.retriever._build_keyword_terms(query) + + def _normalize_keyword_query(self, query: str) -> str: + return self.retriever._normalize_keyword_query(query) + + def _score_keyword_chunk(self, *, query: str, terms: list[str], content: str, document_name: str) -> float: + return self.retriever._score_keyword_chunk( + query=query, + terms=terms, + content=content, + document_name=document_name, + ) +``` + +- [ ] **Step 8: Delegate source building and hydration** + +Replace `_build_sources` body: + +```python + build_sources = getattr(self.retriever, "build_sources", None) + if callable(build_sources): + return build_sources(context_chunks, dataset_name) + return RagRetriever(hydrate_documents=False).build_sources(context_chunks, dataset_name) +``` + +Replace `_hydrate_document_hits` body: + +```python + return await self.retriever._hydrate_document_hits(dataset_id, chunks) +``` + +Expected: public chat behavior, tenant filtering, app resolution, session ownership, and permission feedback remain current `wren-dev` behavior. + +### Task 4: Append Administrative Penalty RAG Rule + +**Files:** +- Modify: `rules/行政处罚/rules.yaml` + +- [ ] **Step 1: Confirm new rule is not already present** + +Run: + +```bash +grep -n "JZ-JD-005" rules/行政处罚/rules.yaml +``` + +Expected: no output before insertion. + +- [ ] **Step 2: Append only the source-branch rule** + +Extract from `gitea/jande-feature-dsl` and insert the `JZ-JD-005` block after the existing `JZ-JD-004` rule in `rules/行政处罚/rules.yaml`. + +The inserted rule must include: + +```yaml + - rule_id: JZ-JD-005 + name: 案由及裁量标准适用准确性 + desc: 结合处罚决定书认定依据、处罚依据、罚款项目和罚款金额,检索案由与裁量标准,判断处罚种类和罚款幅度是否适用准确。 + risk: medium + score: 10 + scope: + - 处罚决定书 + rag: + collection: general_legal_kb + top_k: 5 + source_names: + - 广东省烟草专卖行政处罚裁量执行标准-rag.md + - 案由_行政处罚与反走私管理治理办法.md + query_template: | + 认定依据:{{处罚决定书.认定依据}} + 处罚依据:{{处罚决定书.处罚依据}} + 罚款项目:{{处罚决定书.罚款项目}} + 罚款基数:{{处罚决定书.罚款基数}} + 罚款比例:{{处罚决定书.罚款比例}} + 罚款总额:{{处罚决定书.罚款总额}} + 问题:检索对应案由、裁量档次、处罚种类和罚款幅度 + inject_as: rag_context + resources_as: rag_resources + stages: + - id: '1' + check: required + fields: + - 处罚决定书.认定依据 + - 处罚决定书.处罚依据 + - 处罚决定书.罚款项目 + - 处罚决定书.罚款基数 + - 处罚决定书.罚款比例 + - 处罚决定书.罚款总额 + - id: '2' + check: ai + prompt: | + 请结合检索到的法律知识和卷宗处罚决定书字段,判断案由、裁量档次、处罚种类和罚款幅度是否适用准确。 + + 【检索依据】 + {{rag_context}} + + 【处罚决定书字段】 + 认定依据:{{处罚决定书.认定依据}} + 处罚依据:{{处罚决定书.处罚依据}} + 罚款项目:{{处罚决定书.罚款项目}} + 罚款基数:{{处罚决定书.罚款基数}} + 罚款比例:{{处罚决定书.罚款比例}} + 罚款总额:{{处罚决定书.罚款总额}} + + 【判断要求】 + 1. 判断违法事实对应案由是否准确; + 2. 判断处罚依据是否能支撑对应处罚种类; + 3. 判断罚款基数、比例、总额是否落在裁量标准允许幅度内; + 4. 若检索依据不足以确认,应给出 warn,不要编造依据。 + logic: 1 AND 2 + messages: + pass: 案由、裁量档次、处罚种类和罚款幅度适用准确。 + fail: 案由、裁量档次、处罚种类或罚款幅度可能适用不准确,请核对。 + references_laws: + - 《中华人民共和国行政处罚法》第五十九条 + type: ai_rule +``` + +Expected: YAML diff only contains the new rule block. + +### Task 5: Verify Compile And Targeted Tests + +**Files:** +- Verify: `fastapi_modules/fastapi_leaudit/rag_engine/retriever.py` +- Verify: `fastapi_modules/fastapi_leaudit/leaudit_bridge/pipeline.py` +- Verify: `fastapi_modules/fastapi_leaudit/services/impl/ragChatServiceImpl.py` +- Verify: `tests/test_rag_retriever.py` +- Verify: `tests/test_leaudit_rag_bridge.py` + +- [ ] **Step 1: Run Python compile check** + +Run: + +```bash +.venv/bin/python -m py_compile fastapi_modules/fastapi_leaudit/rag_engine/retriever.py fastapi_modules/fastapi_leaudit/leaudit_bridge/pipeline.py fastapi_modules/fastapi_leaudit/services/impl/ragChatServiceImpl.py +``` + +Expected: exit code 0, no syntax errors. + +- [ ] **Step 2: Run targeted tests** + +Run: + +```bash +.venv/bin/pytest tests/test_rag_retriever.py tests/test_leaudit_rag_bridge.py -q +``` + +Expected: all tests pass. + +- [ ] **Step 3: Run current relevant RAG chat tests if present** + +Run: + +```bash +find tests -maxdepth 2 -type f -iname '*rag*chat*' -o -iname '*rag*permission*' +``` + +If files are found, run them with `.venv/bin/pytest -q`. Expected: pass or pre-existing unrelated failure documented. + +### Task 6: Final Diff Review + +**Files:** +- Review all touched files. + +- [ ] **Step 1: Show changed files** + +Run: + +```bash +git status --short +``` + +Expected: touched files include only the planned files plus pre-existing unrelated files. + +- [ ] **Step 2: Review diff summary** + +Run: + +```bash +git diff --stat +``` + +Expected: planned files dominate; no accidental frontend, database, or page-quality code changes except pre-existing `documentServiceImpl.py`. + +- [ ] **Step 3: Confirm no conflict markers** + +Run: + +```bash +grep -R "<<<<<<<\\|=======\\|>>>>>>>" -n fastapi_modules tests rules || true +``` + +Expected: no output. + +- [ ] **Step 4: Report status instead of committing** + +Do not commit automatically. Report: + +- Files changed. +- Tests run and result. +- Whether rules YAML needs OSS/database re-import to become active. +- Any known risk, especially dependency on installed `leaudit` package supporting `retriever`. diff --git a/docs/superpowers/plans/2026-05-22-route-permission-guard.md b/docs/superpowers/plans/2026-05-22-route-permission-guard.md new file mode 100644 index 0000000..a0d5756 --- /dev/null +++ b/docs/superpowers/plans/2026-05-22-route-permission-guard.md @@ -0,0 +1,171 @@ +# Route Permission Guard Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Prevent users from opening hidden/unauthorized frontend pages by manually entering URLs. + +**Architecture:** Keep backend API permission checks unchanged, and add a server-side Next.js page-route guard inside the authenticated `(audit)` layout. The guard uses backend user route authorization, normalizes feature subpages to their controlled route, and redirects unauthorized page requests before rendering content. + +**Tech Stack:** Next.js 15 App Router, TypeScript, Node test runner, existing RBAC `/api/rbac/user/routes` data. + +--- + +### Task 1: Add Route Access Unit Tests + +**Files:** +- Create: `legal-platform-frontend/lib/auth/route-access.ts` +- Test: `legal-platform-frontend/tests/govdoc-audit/route-access.test.mts` +- Modify: `legal-platform-frontend/lib/utils/route-alias.shared.js` + +- [ ] **Step 1: Write failing tests for direct URL authorization** + +Create tests that assert route guard behavior: + +```ts +import assert from "node:assert/strict"; +import test from "node:test"; + +import { isRoutePathAllowed, flattenMenuPaths } from "../../lib/auth/route-access.ts"; +import { normalizeRoutePathForPermission } from "../../lib/utils/route-alias.shared.js"; + +const allowedPaths = flattenMenuPaths([ + { id: "home", title: "系统概览", path: "/home", icon: "", order: 1 }, + { id: "rules", title: "规则管理", path: "/rules", icon: "", order: 2 }, + { id: "contract", title: "合同管理", path: "/contract-template", icon: "", order: 3, children: [ + { id: "contract-list", title: "模板列表", path: "/contract-template/list", icon: "", order: 1 }, + ] }, +]); + +test("route guard allows exact authorized route", () => { + assert.equal(isRoutePathAllowed("/rules", allowedPaths), true); +}); + +test("route guard allows feature detail page through alias", () => { + assert.equal(isRoutePathAllowed("/rules-test/detail?packId=3&ruleId=MM-ENT-001", allowedPaths), true); +}); + +test("route guard rejects direct URL when route is hidden from role", () => { + assert.equal(isRoutePathAllowed("/tenants", allowedPaths), false); +}); + +test("route guard maps current govdoc pages to govdoc root permission", () => { + assert.equal(normalizeRoutePathForPermission("/govdoc/audits"), "/govdoc"); + assert.equal(normalizeRoutePathForPermission("/govdoc/detail/A-108bce03"), "/govdoc"); +}); +``` + +- [ ] **Step 2: Run tests and verify failure** + +Run: + +```bash +cd legal-platform-frontend +node --experimental-strip-types --test tests/govdoc-audit/route-access.test.mts +``` + +Expected: fail because `route-access.ts` does not exist yet and `/govdoc/*` is not normalized. + +### Task 2: Implement Pure Route Access Helper + +**Files:** +- Create: `legal-platform-frontend/lib/auth/route-access.ts` +- Modify: `legal-platform-frontend/lib/utils/route-alias.shared.js` + +- [ ] **Step 1: Implement helper** + +Add functions: +- `flattenMenuPaths(menuItems)` to extract authorized paths from route tree. +- `normalizeRequestPath(pathname)` to strip query/hash/trailing slash and apply aliases. +- `isRoutePathAllowed(pathname, allowedPaths)` to allow exact authorized routes, authorized route subtrees, and `/home`. + +- [ ] **Step 2: Add `/govdoc/*` route alias** + +Map current internal document pages to `/govdoc`, matching the existing legacy `/govdoc-audit/*` behavior. + +- [ ] **Step 3: Run focused tests** + +Run: + +```bash +cd legal-platform-frontend +node --experimental-strip-types --test tests/govdoc-audit/route-access.test.mts tests/govdoc-audit/home-routing.test.mts +``` + +Expected: all tests pass. + +### Task 3: Wire Server-Side Guard Into Authenticated Layout + +**Files:** +- Modify: `legal-platform-frontend/app/(audit)/layout.tsx` + +- [ ] **Step 1: Read current pathname** + +Use `headers().get("x-pathname")`, which is already set by `middleware.ts`. + +- [ ] **Step 2: Fetch user authorized routes** + +Call `getUserRoutesByRole(userRole, frontendJWT, true)` from server layout. + +- [ ] **Step 3: Redirect unauthorized pages** + +If routes load successfully and `isRoutePathAllowed(pathname, flattenMenuPaths(routes))` is false, redirect to: + +```ts +/home?error=insufficient_permissions +``` + +Do not block `/home`. + +- [ ] **Step 4: Fail closed when routes cannot load** + +If route loading fails because the session is expired, redirect to login. For non-auth failures, redirect to `/home?error=permission_check_failed` except when already on `/home`. + +### Task 4: Verify Build and Regression + +**Files:** +- No new production files unless tests expose type issues. + +- [ ] **Step 1: Run focused tests** + +```bash +cd legal-platform-frontend +node --experimental-strip-types --test tests/govdoc-audit/route-access.test.mts tests/govdoc-audit/home-routing.test.mts +``` + +- [ ] **Step 2: Run full existing frontend node tests** + +```bash +cd legal-platform-frontend +node --experimental-strip-types --test tests/govdoc-audit/*.test.mts +``` + +- [ ] **Step 3: Run lint** + +```bash +cd legal-platform-frontend +npm run lint -- --quiet +``` + +- [ ] **Step 4: Run production build** + +```bash +cd legal-platform-frontend +npm run build +``` + +### Task 5: Manual Acceptance Checklist + +- [ ] A role without `/tenants` cannot open `/tenants` by URL. +- [ ] A role without `/rules` cannot open `/rules-test/list` or `/rules-test/detail?...` by URL. +- [ ] A role with `/rules` can still open `/rules-test/list` and `/rules-test/detail?...`. +- [ ] `/home` remains reachable for logged-in users. +- [ ] Sidebar menu hiding remains unchanged. +- [ ] Backend API 403 behavior remains independent. + +--- + +### Self-Review + +- Spec coverage: Direct URL access is blocked at the server layout before page render. +- Placeholder scan: No TBD/TODO remains in implementation steps. +- Type consistency: Helper consumes existing `MenuItem` shape from `lib/auth/user-routes.ts`. diff --git a/docs/superpowers/plans/2026-05-23-business-entry-ux-optimization.md b/docs/superpowers/plans/2026-05-23-business-entry-ux-optimization.md new file mode 100644 index 0000000..e2e99f8 --- /dev/null +++ b/docs/superpowers/plans/2026-05-23-business-entry-ux-optimization.md @@ -0,0 +1,652 @@ +# 业务入口与业务类型交互优化实施计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 把当前“入口模块管理 + 文档类型管理 + 规则分组 + 上传”的用户理解成本降下来,让用户按“业务入口、业务大类、业务类型、规则”完成配置,而不是理解 `entry_module_id/type_id/group_id`。 + +**Architecture:** 不改后端核心数据模型,不自动创建业务大类或业务类型。前端做语义重构和交互包装:入口模块页只选择已有业务范围;业务类型管理页负责维护大类/类型;上传页保留两级选择但改成业务语言;规则分组页继续承接规则绑定。 + +**Tech Stack:** Next.js App Router、React、现有 `Card/Button/FormSelect/Table/Toast` 组件、现有 CSS 变量、FastAPI 现有入口模块/文档类型/规则分组接口、Playwright UI 验收。 + +--- + +## 一、设计结论 + +### 1.1 用户语义 + +统一使用下面这套用户可理解的语言: + +```text +业务入口 = 首页工作台,对应 leaudit_entry_modules +业务大类 = 上传第一步选择,对应一级分组/当前文档类型大类 +业务类型 = 上传第二步选择,对应二级分组/运行子类型 +规则配置 = 给业务类型绑定规则集 +``` + +前端不再重点暴露这些技术词: + +```text +入口模块绑定文档类型 +一级分组 +二级分组 +document_type_id +group_id +entry_module_id +``` + +技术词可以保留在调试信息、接口字段、开发文档里,但不能作为普通后台用户的主要操作语言。 + +### 1.2 业务边界 + +入口模块新建/编辑页只允许选择已有业务范围: + +```text +不能在入口模块页自动创建业务大类 +不能在入口模块页自动创建业务类型 +不能在入口模块页自动创建规则绑定 +``` + +如果用户找不到业务类型,页面只做引导: + +```text +未找到需要的业务类型?请先到“业务类型管理”维护。 +``` + +### 1.3 最终操作链路 + +```text +1. 业务类型管理:维护业务大类和业务类型 +2. 业务入口管理:创建入口,选择菜单、租户、业务范围 +3. 规则配置:给业务类型绑定规则集 +4. 上传页:选择业务大类、业务类型,上传文件 +5. 文档列表/评查:按入口、租户、大类、类型过滤和命中规则 +``` + +## 二、UI 风格约束 + +所有新增 UI 必须和当前系统保持一致: + +```text +继续使用现有 Card、Button、FormSelect、Table、Toast +继续使用现有 ant-btn / ant-btn-primary 按钮风格 +继续使用绿色主色 #00684a +继续使用 var(--color-primary-text)、var(--color-primary-text-muted)、var(--color-primary-border-soft)、var(--color-primary-surface) +继续使用当前后台页面 24px 外边距、Card 容器、表格、圆角、阴影风格 +继续沿用当前入口模块、文档类型、规则分组页面的表单布局 +``` + +禁止: + +```text +不要引入新的 UI 框架 +不要重做视觉体系 +不要大面积渐变、特殊字体、花哨动效 +不要把入口模块管理页做成和系统其他管理页不一致的风格 +不要改变现有按钮颜色体系 +``` + +## 三、页面信息架构调整 + +### 3.1 菜单命名 + +建议调整左侧菜单文案: + +```text +文档类型 -> 业务类型 +入口模块 -> 业务入口 +规则分组 -> 规则配置 +``` + +如果担心一次改名影响用户习惯,可以第一阶段使用过渡文案: + +```text +业务类型(原文档类型) +业务入口(原入口模块) +规则配置 +``` + +### 3.2 页面职责 + +```text +业务入口管理:配置工作台、菜单、租户、业务范围 +业务类型管理:维护业务大类和业务类型,不绑定规则集 +规则配置:给业务类型绑定规则集 +上传文档:选择业务大类和业务类型后上传 +``` + +## 四、入口模块管理 UI 方案 + +### 4.1 列表页优化 + +文件: + +```text +legal-platform-frontend/app/(audit)/entry-modules/EntryModulesClient.tsx +legal-platform-frontend/styles/pages/entry-modules.css +``` + +列表页保留现有表格风格,但字段改成业务可读: + +```text +入口名称 +工作台类型 +适用租户 +业务范围 +功能菜单 +状态 +操作 +``` + +业务范围列显示摘要: + +```text +合同 3 个类型 / 公文 2 个类型 +``` + +如果接口暂时没有业务范围摘要,第一阶段先显示: + +```text +已配置业务范围 +未配置业务范围 +``` + +操作按钮: + +```text +编辑入口 +配置业务范围 +预览入口 +删除/停用 +``` + +权限仍走现有 RBAC: + +```text +entry_module:list:read +entry_module:create:write +entry_module:update:write +entry_module:delete:delete +entry_module:image:write +``` + +### 4.2 新建/编辑页改成 4 步向导 + +文件: + +```text +legal-platform-frontend/app/(audit)/entry-modules/new/EntryModuleNewClient.tsx +legal-platform-frontend/styles/pages/entry-modules.css +``` + +页面顶部增加步骤条: + +```text +1 基础信息 -> 2 功能菜单 -> 3 适用租户 -> 4 业务范围 +``` + +步骤条只控制页面分区滚动/显示,不做复杂路由拆分。保存仍然是一个表单整体提交。 + +#### Step 1 基础信息 + +字段: + +```text +入口名称 +入口描述 +Logo +跳转入口 +``` + +跳转入口不要直接展示裸路径作为主选项,改成业务文案: + +```text +通用文档评查(/documents) +内部公文评查(/govdoc/audits) +交叉评查(/cross-checking) +``` + +保存时仍提交原 `route_path`。 + +#### Step 2 功能菜单 + +把菜单模板做成当前系统风格的轻量卡片: + +```text +通用文档评查 +合同工作台 +公文工作台 +交叉评查 +自定义工作台 +``` + +选中模板后自动带出默认功能。 + +功能勾选默认折叠为“高级功能设置”,避免用户一进来看到一大堆 checkbox。 + +高级区域展开后显示现有功能勾选: + +```text +首页 +文件上传 +文档列表 +规则管理 +规则分组 +模板搜索 +模板列表 +公文列表 +公文上传 +交叉评查 +创建任务 +评查任务列表 +使用统计 +``` + +#### Step 3 适用租户 + +保持 checkbox,但分组显示: + +```text +公共 + PUBLIC + +地区租户 + 云浮 + 揭阳 + 梅州 +``` + +提示文案: + +```text +未勾选的租户首页不会看到该业务入口。 +``` + +#### Step 4 业务范围 + +标题: + +```text +这个入口可以处理哪些业务? +``` + +交互: + +```text +[ ] 合同 + [ ] 建设工程合同 + [ ] 买卖合同 + [ ] 租赁合同 + +[ ] 公文 + [ ] 请示 + [ ] 通知 + [ ] 会议纪要 +``` + +规则: + +```text +勾选业务大类 = 勾选其下全部业务类型 +取消某个业务类型 = 当前入口不包含这个类型 +只允许选择已有业务大类和业务类型 +不自动创建业务大类和业务类型 +``` + +右侧摘要: + +```text +已选择 2 个业务大类,5 个业务类型 +上传页将只显示这些业务范围 +文档列表将只显示这些业务范围 +规则配置将只显示这些业务范围 +``` + +空状态: + +```text +暂无可选业务类型 +请先到“业务类型管理”维护业务大类和业务类型 +[去业务类型管理] +``` + +### 4.3 业务范围接口策略 + +第一阶段优先复用现有接口: + +```text +GET /api/v3/document-type-roots?entry_module_id=... +GET /api/evaluation-point-groups?entry_module_id=... +GET /api/evaluation-point-groups/by-document-types +``` + +如果现有接口难以一次返回完整树,再新增轻量聚合接口: + +```text +GET /api/entry-modules/{id}/business-scope-options +PUT /api/entry-modules/{id}/business-scope +``` + +返回结构建议: + +```json +{ + "roots": [ + { + "id": 1, + "name": "合同", + "code": "root.contract", + "selected": true, + "children": [ + { + "groupId": 101, + "documentTypeId": 11, + "name": "建设工程合同", + "code": "contract.construction", + "selected": true + } + ] + } + ] +} +``` + +保存结构建议: + +```json +{ + "selectedGroupIds": [101, 102, 201] +} +``` + +注意: + +```text +这里保存的是入口可用业务范围,不创建新业务类型。 +``` + +## 五、业务类型管理 UI 方案 + +### 5.1 页面改名和说明 + +文件: + +```text +legal-platform-frontend/app/(audit)/document-types/DocumentTypesIndexClient.tsx +legal-platform-frontend/app/(audit)/document-types/new/DocumentTypesNewClient.tsx +legal-platform-frontend/styles/pages/document-types_index.css +legal-platform-frontend/styles/pages/document-types_new.css +``` + +页面标题: + +```text +业务类型管理 +``` + +顶部说明: + +```text +这里维护系统可用的业务大类和业务类型。 +业务大类用于上传时第一步选择。 +业务类型用于上传时第二步选择,并用于绑定规则集。 +``` + +### 5.2 列表页展示 + +当前表格保留,但列名改成: + +```text +编码 +业务大类 +所属业务入口 +业务类型数量 +已绑定规则集 +状态 +操作 +``` + +“一级文档类型”“一级分组”“二级分组”这些词从主表格文案里去掉。 + +### 5.3 新建/编辑页文案 + +当前页先只维护业务大类,不让用户误会它会自动创建运行类型。 + +标题: + +```text +新建业务大类 +编辑业务大类 +``` + +说明: + +```text +业务大类是上传时第一步选择,例如合同、公文、案卷。具体业务类型请在规则配置中维护。 +``` + +如果后续要把业务类型维护也放到这里,需要单独设计二级列表,不和本轮混在一起。 + +## 六、上传页 UI 方案 + +文件: + +```text +legal-platform-frontend/app/(audit)/files/upload/FilesUploadClient.tsx +legal-platform-frontend/styles/pages/files_upload.css +``` + +上传页保留两级选择,但统一文案: + +```text +业务大类 +业务类型 +``` + +选择逻辑: + +```text +先按当前入口模块过滤业务大类 +选择业务大类后,只显示当前入口允许的业务类型 +``` + +默认规则: + +```text +当前入口只有一个业务大类 -> 默认选中并展示提示 +当前业务大类只有一个业务类型 -> 默认选中并展示提示 +``` + +提示: + +```text +当前业务入口:合同评查 +上传文件将归属到所选业务大类和业务类型。 +``` + +提交字段不变: + +```text +entryModuleId +typeId +groupId +tenantCode +``` + +## 七、规则配置页 UI 方案 + +文件: + +```text +legal-platform-frontend/app/(audit)/rule-groups/RuleGroupsClient.tsx +legal-platform-frontend/styles/pages/rule-groups_index.css +``` + +文案调整: + +```text +规则分组 -> 规则配置 +一级分组 -> 业务大类 +二级分组 -> 业务类型 +绑定规则集 -> 配置评查规则 +``` + +操作规则不变: + +```text +业务大类不能绑定规则集 +业务类型才可以绑定规则集 +``` + +按钮文案: + +```text +新增一级分组 -> 新增业务大类 +新增二级分组 -> 新增业务类型 +绑定规则集 -> 配置规则 +``` + +## 八、实施任务清单 + +### 8.1 准备和基线 + +- [x] 执行 `./leaudit.sh status`,确认后端、前端、Worker、Beat 运行。 +- [ ] 使用 Playwright 登录 `000/admin06111` 截取当前入口模块、文档类型、上传、规则分组页面现状。 +- [ ] 记录当前主要入口 URL:`/entry-modules`、`/entry-modules/new`、`/document-types`、`/files/upload`、`/rule-groups`。 + +### 8.2 入口模块列表页优化 + +- [x] 修改 `EntryModulesClient.tsx` 页面标题和文案:`入口模块管理` 改为 `业务入口管理`。 +- [x] 修改列表列名,隐藏或弱化技术字段。 +- [x] 增加功能菜单摘要展示,沿用现有 tag/badge 风格。 +- [x] 增加业务范围摘要占位,接口未完成前显示 `待配置` 或 `已配置`。 +- [ ] 跑 Playwright 验证列表页没有布局溢出,按钮权限仍正确。 + +### 8.3 入口模块新建/编辑页 4 步向导 + +- [x] 在 `EntryModuleNewClient.tsx` 增加步骤条状态。 +- [x] 把现有基础字段移动到 `基础信息` 分区。 +- [x] 把 `route_path` 下拉改成业务文案 + 路径说明。 +- [x] 把菜单模板改成模板卡片,选中后仍更新 `menu_profile/features`。 +- [x] 把功能勾选移动到 `高级功能设置` 折叠区。 +- [x] 把租户勾选拆成 `公共` 和 `地区租户` 两组。 +- [x] 增加 `业务范围` 分区,先展示只读/占位状态和跳转 `业务类型管理` 按钮。 +- [x] 保持提交 payload 字段不变:`name/description/route_path/menu_profile/features/tenants`。 +- [x] 跑 TypeScript 检查。 +- [ ] 跑 Playwright 验证新建、编辑、权限只读、Logo 上传入口不破坏。 + +### 8.4 业务范围选择器接入 + +- [x] 新增前端组件 `BusinessScopeSelector`,放在入口模块页面附近,复用现有 checkbox、Card、empty state 风格。 +- [x] 优先用现有文档类型/规则分组接口组装业务大类和业务类型树。 +- [ ] 如果现有接口不足,补后端聚合接口 `business-scope-options`。 +- [x] 选择器支持勾选大类自动勾选子类型。 +- [x] 选择器支持右侧摘要:大类数、业务类型数。 +- [x] 保存时只保存已有业务类型范围,不创建新大类/类型。 +- [ ] 跑 Playwright 验证入口编辑页业务范围选择、保存、回显。 + +### 8.5 业务类型管理文案优化 + +- [x] 修改 `DocumentTypesIndexClient.tsx` 标题为 `业务大类管理`。 +- [x] 修改表格列名:业务大类、所属业务入口、业务类型数量、已绑定规则集。 +- [x] 修改空状态文案。 +- [x] 修改 `DocumentTypesNewClient.tsx` 标题为 `新建业务大类/编辑业务大类`。 +- [x] 修改说明文案,强调这里维护上传第一步的业务大类。 +- [x] 修改 `document-types/page.tsx` 和 `document-types/new/page.tsx` metadata,避免浏览器标题继续显示文档类型。 +- [x] 修改面包屑 `文档类型管理/新建文档类型` 为 `业务大类管理/新建业务大类`。 +- [x] 修改侧栏 fallback 菜单 `文档类型` 为 `业务大类管理`。 +- [x] 修改上传失败诊断文案中的 `文档类型管理/当前文档类型` 为 `业务大类管理/当前业务大类`。 +- [x] 保持现有创建/编辑接口字段不变。 +- [ ] 跑 Playwright 验证新建和编辑页字段仍能保存。 + +### 8.6 上传页交互文案优化 + +- [x] 修改上传页选择项文案:`文档类型` 改为 `业务大类`。 +- [x] 修改子类型选择项文案:`子类型/二级分组` 改为 `业务类型`。 +- [x] 当前入口只有一个业务大类时自动选中并展示提示。 +- [x] 当前业务大类只有一个业务类型时自动选中并展示提示。 +- [x] 保持提交字段 `entryModuleId/typeId/groupId/tenantCode` 不变。 +- [ ] 跑 Playwright 真实上传表单测试,确认字段落库正确。 + +### 8.7 规则配置页文案优化 + +- [x] 修改页面标题:`规则分组` 改为 `规则配置`。 +- [x] 修改树节点文案:一级分组显示为业务大类,二级分组显示为业务类型。 +- [x] 修改按钮文案:新增业务大类、新增业务类型、配置规则。 +- [x] 一级节点不显示配置规则入口。 +- [x] 二级节点显示配置规则入口。 +- [x] 跑 Playwright 验证规则配置页业务文案、业务大类/业务类型行展示和二级配置入口可见。 + +### 8.8 权限和多租户回归 + +- [ ] 验证没有 `entry_module:list:read` 不显示业务入口管理菜单。 +- [ ] 验证没有 `entry_module:create:write` 不显示新建业务入口按钮。 +- [ ] 验证没有 `entry_module:update:write` 编辑页不可保存业务入口。 +- [ ] 验证没有 `entry_module:image:write` 不可上传入口 Logo。 +- [ ] 验证云浮、揭阳、梅州租户只看到自己或 `PUBLIC` 入口。 + +### 8.9 最终验收 + +- [x] `legal-platform-frontend` 下运行 `npx tsc --noEmit --pretty false`。 +- [x] 运行目标 eslint,要求 `0 error`。 +- [x] 使用 Playwright 跑入口模块编辑页:业务范围步骤、业务范围摘要、业务大类列表/空状态回显。 +- [ ] 使用 Playwright 跑上传页:业务大类、业务类型选择和真实提交。 +- [x] 使用 Playwright 跑规则配置页:业务大类/业务类型树、一级不显示配置入口、二级显示配置入口。 +- [ ] 更新 `docs/superpowers/plans/2026-05-23-entry-module-menu-profile-multitenant-refactor.md` 执行记录。 + +### 8.10 执行记录 2026-05-23 + +- [x] 新增业务入口 UI helper:`legal-platform-frontend/lib/business-entry/business-entry-ui.ts`,统一维护工作台入口文案、菜单模板文案、功能菜单摘要。 +- [x] 新增单测:`legal-platform-frontend/tests/govdoc-audit/business-entry-ui.test.mts`,覆盖路由业务文案、模板文案、功能摘要。 +- [x] 使用 TDD 跑红灯:helper 缺失时测试失败,随后实现 helper 并通过。 +- [x] 优化业务入口列表页:标题、说明、列名、工作台入口、工作台类型、业务范围占位、功能菜单摘要改为业务语义。 +- [x] 列表页 Logo 预览从 `` 改为 `next/image`,目标 eslint 无 warning。 +- [x] 优化业务入口新建/编辑页:增加 `基础信息/功能菜单/适用租户/业务范围` 四步结构。 +- [x] 工作台入口下拉改为 `通用文档评查/内部公文评查/交叉评查`,保存仍使用原始 `route_path`。 +- [x] 菜单模板改为卡片选择,功能勾选移动到 `高级功能设置` 折叠区域。 +- [x] 租户选择拆成 `公共` 和 `地区租户` 两组。 +- [x] 业务范围先做占位和跳转 `业务类型管理`,不自动创建、不保存业务范围,避免破坏现有接口。 +- [x] 验证:`node --test --experimental-strip-types tests/govdoc-audit/business-entry-ui.test.mts` 结果 `5 pass / 0 fail`。 +- [x] 验证:目标 eslint 结果 `0 error / 0 warning`。 +- [x] 验证:`npx tsc --noEmit --pretty false` 通过。 +- [x] 新增业务分类 UI helper:`legal-platform-frontend/lib/business-entry/business-taxonomy-ui.ts`,统一维护 `业务大类/业务类型` 标签和上传必填提示。 +- [x] 新增单测:`legal-platform-frontend/tests/govdoc-audit/business-taxonomy-ui.test.mts`,覆盖业务大类/业务类型文案。 +- [x] 优化业务类型管理列表页:标题、说明、列名、空状态统一为业务语义。 +- [x] 优化业务大类新建/编辑页:把 `一级文档类型/入口模块/二级分组/评查点分组` 主文案替换为 `业务大类/业务入口/业务类型/规则配置`。 +- [x] 优化上传页:选择卡片改为 `选择业务范围`,两级选择改为 `业务大类/业务类型`,错误提示和说明同步改为业务语义。 +- [x] 验证:`node --test --experimental-strip-types tests/govdoc-audit/business-taxonomy-ui.test.mts tests/govdoc-audit/business-entry-ui.test.mts` 结果 `7 pass / 0 fail`。 +- [x] 验证:业务类型管理和上传页目标 eslint 结果 `0 error / 0 warning`。 +- [x] 验证:`npx tsc --noEmit --pretty false` 通过。 +- [x] 优化规则配置页:页面标题、指标卡、说明条、筛选项、业务大类/业务类型树、配置弹窗、删除确认和 toast 统一改成业务语义。 +- [x] 规则配置页保持接口和 payload 不变,仅替换用户可见文案;业务大类仍不显示配置规则入口,业务类型显示 `配置规则`。 +- [x] 修复规则配置页目标 eslint 的 hook dependency warning:把规则预览加载改为稳定 `useCallback` + ref 读取状态。 +- [x] 验证:`npx eslint app/\(audit\)/rule-groups/RuleGroupsClient.tsx --max-warnings=0` 结果 `0 error / 0 warning`。 +- [x] 验证:`npx tsc --noEmit --pretty false` 通过。 +- [x] 新增业务范围 helper:`legal-platform-frontend/lib/business-entry/business-scope-ui.ts`,覆盖选择摘要和保存差异计算。 +- [x] 新增业务范围 API 封装:复用 `GET /api/v3/document-type-roots`、`GET /api/rule-groups/tree`、`PUT /api/v3/document-type-roots/{id}`。 +- [x] 入口模块新建/编辑页接入业务范围选择器:用户勾选业务大类,下属业务类型只读展示;保存只更新业务大类归属,不自动创建业务大类或业务类型。 +- [x] 验证:`node --test --experimental-strip-types tests/business-entry/business-scope-ui.test.mts tests/govdoc-audit/business-taxonomy-ui.test.mts tests/govdoc-audit/business-entry-ui.test.mts` 结果 `9 pass / 0 fail`。 +- [x] 验证:`npx eslint app/\(audit\)/entry-modules/new/EntryModuleNewClient.tsx lib/business-entry/business-scope-ui.ts --max-warnings=0` 结果 `0 error / 0 warning`。 +- [x] 验证:`npx eslint lib/api/legacy/entry-modules/business-scope.ts --no-warn-ignored --max-warnings=0` 结果 `0 error / 0 warning`。 +- [x] 验证:`npx tsc --noEmit --pretty false` 通过。 +- [x] 修正遗漏的文档类型管理外露文案:页面 metadata、面包屑、侧栏 fallback、上传失败诊断统一改成 `业务大类管理/新建业务大类/业务大类`。 +- [x] 新增面包屑回归测试:`business category management breadcrumb uses business wording`。 +- [x] 验证:`node --test --experimental-strip-types tests/govdoc-audit/home-routing.test.mts tests/govdoc-audit/business-taxonomy-ui.test.mts` 结果 `12 pass / 0 fail`。 +- [x] 验证:目标 eslint 结果 `0 error / 0 warning`。 +- [x] 验证:`npx tsc --noEmit --pretty false` 通过。 +- [x] 定位并修复 `/rule-groups` 服务端路由守卫误拦截:后端 RBAC 权限树当前返回 `/rules` 且聚合了 `evaluation_group:*` 权限,但未返回 `/rule-groups` 路由;布局只按 route path 放行导致跳转 `/home?error=insufficient_permissions`。 +- [x] 新增 `isRouteAllowedByBusinessPermission`:仅当访问 `/rule-groups` 且用户拥有 `evaluation_group:*` 权限时放行,避免扩大其他路由访问范围。 +- [x] 新增路由守卫单测:`route guard allows rule configuration when user has evaluation group permissions`,先红灯后实现,最终通过。 +- [x] Playwright 真实账号 `000/admin06111` 验收:`node /tmp/leaudit-business-entry-ux-smoke.js` 通过,结果 `scopeItems=38 / rootRows=37 / childRows=58 / errorCount=0`。 +- [x] 验证:`node --test --experimental-strip-types tests/business-entry/business-scope-ui.test.mts tests/govdoc-audit/business-taxonomy-ui.test.mts tests/govdoc-audit/business-entry-ui.test.mts tests/govdoc-audit/route-access.test.mts` 结果 `16 pass / 0 fail`。 +- [x] 验证:`npx eslint app/\(audit\)/layout.tsx lib/auth/route-access.ts tests/govdoc-audit/route-access.test.mts app/\(audit\)/entry-modules/new/EntryModuleNewClient.tsx app/\(audit\)/rule-groups/RuleGroupsClient.tsx lib/business-entry/business-scope-ui.ts --max-warnings=0` 结果 `0 error / 0 warning`。 +- [x] 验证:`npx tsc --noEmit --pretty false` 通过。 +- [x] 环境说明:本轮 `./leaudit.sh start/restart` 在当前工具会话里出现启动后进程立即消失的现象;为完成 Playwright 验收,临时用同等命令在 PTY 会话中启动后端 `.venv/bin/python run.py` 和前端 `npm run dev:dev`。 + +## 九、风险控制 + +- [ ] 第一阶段优先改文案和页面组织,不改数据库。 +- [ ] 入口模块页不自动创建业务大类/业务类型,避免隐式脏数据。 +- [ ] 上传页保留两级选择,避免用户传错类型。 +- [ ] 所有菜单生成仍经过 RBAC 权限过滤。 +- [ ] 所有入口范围仍经过租户过滤。 +- [ ] Playwright 真实验收前先完成 UI 交互优化,避免测试脚本固化错误交互。 diff --git a/docs/superpowers/plans/2026-05-23-entry-module-menu-profile-multitenant-refactor.md b/docs/superpowers/plans/2026-05-23-entry-module-menu-profile-multitenant-refactor.md new file mode 100644 index 0000000..e9626b7 --- /dev/null +++ b/docs/superpowers/plans/2026-05-23-entry-module-menu-profile-multitenant-refactor.md @@ -0,0 +1,2224 @@ +# 入口模块菜单配置与多租户重构实施计划 + +> **给后续执行开发的人:** 按本文任务逐步实施,不要跳着改。本文只做入口模块、菜单、文档归属、多租户范围的重构计划,不直接改动运行时规则评查逻辑。 + +**目标:** 让“入口模块”真正成为用户能理解的业务工作台,菜单显示、文档过滤、上传范围、规则分组、租户可见性都从入口模块配置出发,不再依赖“名称里是否包含合同/公文”这种硬编码判断。 + +**总体架构:** 保留 `leaudit_entry_modules` 作为首页入口模块主表,保留现有 `leaudit_entry_module_tenants` 作为租户可见性关系表。在入口模块上增加“菜单模板字段”`menu_profile` 和“功能清单字段”`features`,用它们决定该入口模块显示哪些功能菜单。角色权限系统继续负责页面和接口权限,入口模块只负责“业务工作台编排”和“业务范围”。 + +**涉及技术:** 后端接口、数据库原生查询、数据库迁移脚本、前端页面、前端侧边栏、现有角色权限系统、现有租户识别逻辑。 + +--- + +## 一、当前根因 + +现在系统的问题不是用户没配置好,而是代码在“猜业务类型”。 + +当前错误判断包括: + +- 合同菜单靠“入口模块名称里有没有合同两个字”判断。 +- 公文菜单靠“入口模块名称里有没有公文两个字”或固定路径判断。 +- 公文列表页面强行显示成“内部公文”。 +- 公文列表页面虽然读取了浏览器地址里的 `entryModuleId`,但没有传给后端列表接口。 +- 文档列表和规则页面部分支持 `entryModuleId`,但侧边栏菜单不认它。 +- 租户可见性已经有 `leaudit_entry_module_tenants`,但菜单功能没有和入口模块、租户上下文形成闭环。 + +目标链路应该变成: + +```text +当前用户租户 -> 可见入口模块 -> 用户选择入口模块 +入口模块 -> 菜单模板字段/功能清单字段 -> 生成左侧菜单 +入口模块 -> 文档类型 -> 上传/列表/规则分组范围 +文档 -> entry_module_id + type_id + group_id + tenant_code -> 后续评查/统计/列表过滤 +``` + +## 二、用户应该怎么理解 + +管理员配置入口模块时,只需要按业务语言操作: + +1. 新建入口模块,例如 `入口模块-测试`。 +2. 选择菜单模板:合同工作台、公文工作台、交叉评查工作台、通用文档评查、自定义。 +3. 勾选功能:文档列表、文件上传、规则配置、规则分组、模板搜索、模板列表、公文列表、公文上传等。 +4. 分配租户:云浮、揭阳、梅州、公共资源域等。 +5. 绑定一级文档类型。 +6. 在规则分组里配置二级运行子类型和规则绑定。 + +用户不应该被迫把入口模块命名为“合同xxx”或“公文xxx”才能显示对应功能。 + +## 三、核心数据模型 + +### 3.1 保留现有表 + +继续使用: + +- `leaudit_entry_modules`:入口模块主表,表示首页业务入口。 +- `leaudit_entry_module_tenants`:入口模块和租户的可见关系。 +- `leaudit_document_types`:文档类型,通过 `entry_module_id` 归属入口模块。 +- `leaudit_evaluation_point_groups`:规则分组树。 +- `leaudit_rule_group_bindings`:运行时规则绑定唯一事实源。 +- `leaudit_documents`:文档记录,后续需要补 `entry_module_id`。 + +### 3.2 入口模块新增字段 + +给 `leaudit_entry_modules` 增加: + +```sql +ALTER TABLE leaudit_entry_modules +ADD COLUMN IF NOT EXISTS menu_profile VARCHAR(64) NOT NULL DEFAULT 'document_review', +ADD COLUMN IF NOT EXISTS features JSONB NOT NULL DEFAULT '[]'::jsonb; + +CREATE INDEX IF NOT EXISTS idx_leaudit_entry_modules_menu_profile +ON leaudit_entry_modules(menu_profile) +WHERE deleted_at IS NULL; +``` + +字段解释: + +- `menu_profile`:菜单模板字段,比如合同工作台、公文工作台、通用文档评查。 +- `features`:功能清单字段,表示该入口模块启用哪些功能菜单。 + +### 3.3 文档表新增字段 + +给 `leaudit_documents` 增加: + +```sql +ALTER TABLE leaudit_documents +ADD COLUMN IF NOT EXISTS entry_module_id BIGINT NULL REFERENCES leaudit_entry_modules(id); + +CREATE INDEX IF NOT EXISTS idx_leaudit_documents_entry_module_id +ON leaudit_documents(entry_module_id); +``` + +作用: + +- 文档创建后明确知道自己来自哪个入口模块。 +- 文档列表、公文列表、统计、后续质量校验都可以按入口模块过滤。 + +## 四、菜单模板与功能编码 + +### 4.1 菜单模板 + +固定使用这些编码: + +```text +document_review 通用文档评查 +contract 合同工作台 +govdoc 内部公文工作台 +cross_checking 交叉评查工作台 +custom 自定义工作台 +``` + +### 4.2 功能编码 + +固定使用这些编码: + +```text +home 首页/概览 +documents 文档列表 +upload 文件上传 +rules 规则配置 +rule_groups 规则分组 +contract_template_search 合同模板搜索 +contract_template_list 合同模板列表 +govdoc_audits 公文列表 +govdoc_upload 公文上传 +cross_checking 交叉评查 +cross_checking_upload 创建交叉评查任务 +cross_checking_list 交叉评查任务列表 +usage_stats 使用统计 +``` + +### 4.3 默认功能 + +如果老数据没有配置功能清单字段,就按菜单模板字段自动补默认值: + +```json +{ + "document_review": ["home", "documents", "upload", "rules", "rule_groups"], + "contract": ["home", "documents", "upload", "rules", "contract_template_search", "contract_template_list"], + "govdoc": ["home", "govdoc_audits", "govdoc_upload", "rule_groups"], + "cross_checking": ["cross_checking", "cross_checking_upload", "cross_checking_list"], + "custom": ["home", "documents"] +} +``` + +## 五、多租户规则 + +这里必须沿用你当前项目已有模型,不新造租户体系。 + +### 5.1 入口模块可见性 + +入口模块是否对某个租户可见,继续由 `leaudit_entry_module_tenants` 控制。 + +判断规则: + +```text +用户 tenant_code 命中 leaudit_entry_module_tenants.tenant_code +并且 leaudit_entry_module_tenants.is_enabled = true +并且 leaudit_entry_modules.is_enabled = true +并且入口模块没有 deleted_at +``` + +特殊情况: + +- `PUBLIC` 可以作为公共入口模块。 +- 超级管理员或全局管理员可以跨租户查看。 +- 旧的地区字段暂时保留兼容,但不再作为新逻辑主模型。 + +### 5.2 功能菜单是否需要租户级差异 + +第一阶段不做“同一个入口模块不同租户显示不同功能”。 + +第一阶段规则: + +```text +入口模块功能清单对所有可见租户一致 +租户只控制入口模块是否可见 +``` + +如果后续真有需求,例如同一个入口模块云浮显示模板搜索、梅州不显示模板搜索,再扩展: + +```sql +ALTER TABLE leaudit_entry_module_tenants +ADD COLUMN IF NOT EXISTS features_override JSONB NULL, +ADD COLUMN IF NOT EXISTS menu_profile_override VARCHAR(64) NULL; +``` + +最终生效逻辑: + +```text +租户关系表 features_override 有值 -> 用租户覆盖配置 +否则入口模块 features 有值 -> 用入口模块配置 +否则按 menu_profile 默认功能生成 +``` + +第一阶段先不要上这个复杂度。 + +### 5.3 入口模块配置权限 + +入口模块是系统级业务工作台配置,但不要在代码里硬编码“只有某个角色名能配置”。正确口径是:谁被分配了入口模块管理权限,谁才能配置入口模块。 + +也就是说: + +```text +不要写死 role_key = super_admin +不要写死 role_key = provincial_admin +不要写死市区管理员一定不能操作 +``` + +后端只认权限点: + +```text +entry_module:list:read 查看入口模块列表 +entry_module:detail:read 查看入口模块详情 +entry_module:create:write 创建入口模块 +entry_module:update:write 编辑入口模块 +entry_module:delete:delete 删除入口模块 +entry_module:image:write 上传或替换入口模块图标 +``` + +推荐默认分配策略: + +```text +系统超级管理员角色:默认分配入口模块全部管理权限 +省级管理员角色:默认不分配创建/编辑/删除入口模块权限,是否开放由实际授权决定 +市区管理员角色:默认不分配创建/编辑/删除入口模块权限,是否开放由实际授权决定 +``` + +所以最终判断不是“这个人是不是超级管理员”,而是: + +```text +能不能创建入口模块 -> 看有没有 entry_module:create:write +能不能编辑入口模块 -> 看有没有 entry_module:update:write +能不能删除入口模块 -> 看有没有 entry_module:delete:delete +能不能上传图标 -> 看有没有 entry_module:image:write +``` + +三层边界必须分清: + +```text +RBAC 权限点:控制谁能配置入口模块 +入口模块租户关系:控制哪个租户能看到/使用入口模块 +入口模块 features + 页面/API 权限:控制进入入口后能看到和使用哪些功能 +``` + +举例: + +```text +某个市区管理员没有 entry_module:create:write +即使他属于云浮租户,也不能新建入口模块 + +某个市区管理员有 entry_module:create:write +他就可以进入入口模块管理页执行创建动作 +但创建出来的入口能被哪些租户看到,仍然必须写入 leaudit_entry_module_tenants +``` + +## 六、一级分组、二级分组、规则绑定设计 + +这一块是本次重构最关键的业务边界。入口模块只解决“用户从哪个工作台进来”,规则分组解决“这个工作台下有哪些业务类型”,规则绑定解决“这个业务类型运行哪套规则”。 + +### 6.1 最终业务口径 + +固定使用两层分组,不继续发散: + +```text +入口模块 + -> 一级分组:业务大类 + -> 二级分组:具体运行类型 + -> 规则绑定:绑定规则集 +``` + +举例: + +```text +入口模块:合同评查 +一级分组:合同 +二级分组:建设工程合同、买卖合同、租赁合同 +规则绑定:建设工程合同 -> 建设工程合同规则集 +``` + +再举例: + +```text +入口模块:内部公文 +一级分组:内部公文 +二级分组:请示、通知、会议纪要 +规则绑定:请示 -> 请示规则集 +``` + +### 6.2 一级分组职责 + +一级分组只表达业务大类,不直接参与运行时规则命中。 + +数据库仍然复用: + +```text +leaudit_evaluation_point_groups +``` + +一级分组判断: + +```text +pid = 0 +``` + +一级分组字段规则: + +```text +entry_module_id 必填,表示这个一级分组属于哪个入口模块 +document_type_id 为空,不直接绑定具体文档类型 +name 使用业务大类名称,例如 合同、内部公文、行政卷宗 +``` + +也就是说,入口模块页面里的“规则分组”应该先看到这个入口模块下的一级分组,而不是全系统所有分组。 + +### 6.3 二级分组职责 + +二级分组才是实际运行类型,也就是用户上传、列表过滤、评查执行真正要落的 `groupId`。 + +二级分组判断: + +```text +pid != 0 +``` + +二级分组字段规则: + +```text +pid 指向一级分组 id +document_type_id 必填,表示这个二级分组对应哪个具体文档类型 +entry_module_id 可以冗余保存,也可以从一级分组继承,但查询时必须能按入口模块过滤 +``` + +前端显示时,用户理解成: + +```text +一级分组 = 大类 +二级分组 = 具体要评查的文档类型/运行类型 +``` + +后端理解成: + +```text +文档最终必须落到二级分组 group_id +``` + +### 6.4 规则绑定职责 + +规则绑定只允许挂二级分组,不允许挂一级分组。 + +继续使用: + +```text +leaudit_rule_group_bindings +``` + +绑定关系: + +```text +leaudit_rule_group_bindings.group_id = 二级分组 id +leaudit_rule_group_bindings.rule_set_id = 规则集 id +``` + +禁止口径: + +```text +不要把规则集挂在一级分组 +不要再按入口模块名称猜规则集 +不要再绕回旧的文档类型规则绑定表作为主链路 +``` + +运行时唯一主链路: + +```text +document.group_id + -> leaudit_rule_group_bindings.group_id + -> rule_set_id + -> 当前可执行规则版本 +``` + +### 6.5 多租户下规则怎么生效 + +分组树本身不建议第一阶段按租户复制一份。否则云浮、梅州、揭阳每个租户一套树,后面维护会爆炸。 + +第一阶段建议: + +```text +入口模块租户关系:控制哪个租户看得到哪个入口模块 +一级/二级分组:表达入口模块下的业务结构 +规则绑定 tenant_code/scope_type:控制不同租户实际运行哪套规则集 +``` + +也就是说: + +```text +同一个二级分组,可以存在多条规则绑定 +``` + +例如: + +```text +二级分组:建设工程合同 +云浮租户 -> 云浮建设工程合同规则集 +梅州租户 -> 梅州建设工程合同规则集 +省级兜底 -> 通用建设工程合同规则集 +``` + +运行时选择顺序: + +```text +优先当前租户绑定 +没有当前租户绑定,再走省级/公共兜底绑定 +仍然没有,就提示该二级分组未配置有效规则 +``` + +### 6.6 上传、列表、评查的字段落点 + +上传时必须落这些字段: + +```text +entry_module_id:从哪个入口进来 +type_id:具体文档类型 +group_id:二级分组 id +tenant_code:当前租户 +``` + +如果上传时只传了文档类型,没有传二级分组,后端只能在“该文档类型只有一个可用二级分组”时兜底推断。只要有多个二级分组,就必须让用户明确选择,否则规则命中会不稳定。 + +列表过滤顺序: + +```text +先按 tenant_code 做租户边界 +再按 entry_module_id 做入口模块范围 +再按 group_id/type_id 做具体业务过滤 +``` + +评查执行顺序: + +```text +读取文档 group_id +确认 group_id 是二级分组 +按 group_id + tenant_code 找有效规则绑定 +取 rule_set 当前可执行版本 +执行评查 +``` + +### 6.7 后台页面怎么改 + +入口模块管理页: + +```text +只负责入口模块本身、菜单功能、租户可见性 +不在这里直接绑规则集 +``` + +文档类型管理页: + +```text +负责维护具体文档类型属于哪个入口模块 +可以辅助展示它被哪个二级分组使用 +``` + +规则分组页: + +```text +进入某入口模块后,只显示该入口模块下的一级分组 +一级分组下面维护二级分组 +二级分组上配置规则绑定 +``` + +规则绑定页或弹窗: + +```text +只能从二级分组进入 +可以配置当前租户绑定、省级兜底绑定、公共绑定 +保存到 leaudit_rule_group_bindings +``` + +### 6.8 必须加的校验 + +后端保存规则分组时必须校验: + +```text +一级分组 pid = 0 时,必须有 entry_module_id +一级分组不允许绑定 rule_set +二级分组 pid != 0 时,必须有 document_type_id +二级分组所属一级分组必须和当前入口模块一致 +二级分组才允许写 leaudit_rule_group_bindings +上传文档时传入的 group_id 必须是二级分组 +上传文档时 group_id 对应的入口模块必须等于 entry_module_id +``` + +建议补充数据库约束或唯一索引: + +```sql +CREATE UNIQUE INDEX IF NOT EXISTS uq_leaudit_ep_groups_parent_doc_type_active +ON leaudit_evaluation_point_groups(pid, document_type_id) +WHERE deleted_at IS NULL + AND COALESCE(pid, 0) <> 0 + AND document_type_id IS NOT NULL; +``` + +作用是避免同一个一级分组下面重复挂同一个文档类型,导致上传时无法稳定推断二级分组。 + +### 6.9 旧数据兼容策略 + +当前系统里可能还有旧结构: + +```text +一级分组 = 具体文档类型 +二级分组 = 通用 +``` + +这个是过渡结构,不要继续加深。 + +兼容策略: + +```text +短期:后端查询同时兼容旧结构,避免线上功能直接断 +中期:迁移成 一级=业务大类、二级=具体文档类型 +长期:规则绑定只认二级分组,旧结构下线 +``` + +迁移示例: + +```text +旧: +一级:建设工程合同 +二级:通用 +规则绑定:通用 -> 规则集 + +新: +一级:合同 +二级:建设工程合同 +规则绑定:建设工程合同 -> 原规则集 +``` + +这一段迁移可以参考已有脚本: + +```text +scripts/创建sql/migrate_rule_groups_to_business_roots.sql +scripts/创建sql/precheck_rule_group_migration.sql +``` + +## 七、后端改造范围 + +### 7.1 后端请求对象 + +文件: + +```text +fastapi_modules/fastapi_leaudit/domian/Dto/entryModuleDto.py +``` + +给 `EntryModuleCreateDTO` 和 `EntryModuleUpdateDTO` 增加: + +```python +menu_profile: str | None = Field(None, description="菜单模板:document_review/contract/govdoc/cross_checking/custom") +features: list[str] | None = Field(None, description="启用功能编码列表") +``` + +### 7.2 后端返回对象 + +文件: + +```text +fastapi_modules/fastapi_leaudit/domian/vo/entryModuleAdminVo.py +fastapi_modules/fastapi_leaudit/domian/vo/homeVo.py +``` + +入口模块管理返回对象增加: + +```python +menu_profile: str = Field("document_review", description="菜单模板") +features: list[str] = Field(default_factory=list, description="启用功能编码列表") +business_scope: EntryModuleBusinessScopeVO = Field(default_factory=EntryModuleBusinessScopeVO, description="业务范围摘要") +``` + +首页入口返回对象增加: + +```python +menuProfile: str = Field("document_review", description="菜单模板") +features: list[str] = Field(default_factory=list, description="启用功能编码列表") +tenantCode: str | None = Field(None, description="当前命中的租户编码") +``` + +### 7.3 入口模块管理服务 + +文件: + +```text +fastapi_modules/fastapi_leaudit/services/impl/entryModuleAdminServiceImpl.py +``` + +要做: + +1. 确保菜单模板字段和功能清单字段存在。 +2. 创建入口模块时保存菜单模板和功能清单。 +3. 更新入口模块时保存菜单模板和功能清单。 +4. 查询列表和详情时返回这两个字段。 +5. 查询列表和详情时返回业务范围摘要 `business_scope`,用于前端展示已绑定业务大类和业务类型数量。 +6. 校验菜单模板和功能清单只能使用允许的编码。 +7. 继续沿用现有入口模块权限点做接口控制,不要额外硬编码角色名。 +8. 如果当前用户没有对应权限点,后端直接返回 403,不能只靠前端隐藏按钮。 + +允许值: + +```python +_ALLOWED_MENU_PROFILES = {"document_review", "contract", "govdoc", "cross_checking", "custom"} +_ALLOWED_FEATURES = { + "home", + "documents", + "upload", + "rules", + "rule_groups", + "contract_template_search", + "contract_template_list", + "govdoc_audits", + "govdoc_upload", + "cross_checking", + "cross_checking_upload", + "cross_checking_list", + "usage_stats", +} +``` + +### 7.4 首页入口服务 + +文件: + +```text +fastapi_modules/fastapi_leaudit/services/impl/homeServiceImpl.py +``` + +要做: + +1. 保持现有 `leaudit_entry_module_tenants` 过滤逻辑不变。 +2. 查询入口模块时带出菜单模板和功能清单。 +3. 返回给首页,前端点击入口时存起来。 +4. 返回当前命中的租户编码,方便前端继续传递上下文。 + +### 7.5 文档服务 + +文件: + +```text +fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py +fastapi_modules/fastapi_leaudit/controllers/documentController.py +``` + +要做: + +1. 上传接口接收 `entryModuleId`。 +2. 创建文档时写入 `entry_module_id`。 +3. 如果前端没传,则按: + +```text +二级分组 entry_module_id -> 一级分组 entry_module_id -> 文档类型 entry_module_id +``` + +兜底解析。 + +4. 文档列表过滤时使用: + +```sql +COALESCE(d.entry_module_id, eg.entry_module_id, eg_parent.entry_module_id, dt.entry_module_id) = :entry_module_id +``` + +### 7.6 公文服务 + +文件: + +```text +fastapi_modules/fastapi_leaudit/controllers/govdocController.py +fastapi_modules/fastapi_leaudit/services/impl/govdocServiceImpl.py +``` + +要做: + +1. `/api/govdoc/documents` 增加 `entry_module_id` 查询参数。 +2. `GovdocServiceImpl.ListDocuments` 增加 `EntryModuleId` 参数。 +3. 查询时按: + +```sql +COALESCE(d.entry_module_id, dt.entry_module_id) = :entry_module_id +``` + +过滤。 + +4. 继续保留现有: + +```sql +COALESCE(d.engine_type, 'leaudit') = 'govdoc' +``` + +也就是说: + +```text +公文列表 = 文档引擎类型是公文 + 当前入口模块范围 +``` + +### 7.7 规则分组服务 + +文件: + +```text +fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py +fastapi_modules/fastapi_leaudit/services/impl/ruleConfigServiceImpl.py +``` + +要做: + +1. 规则组列表按 `entry_module_id` 过滤。 +2. 过滤表达式统一用: + +```sql +COALESCE(g.entry_module_id, parent.entry_module_id, dt.entry_module_id) +``` + +3. 入口模块只决定“用户看到哪些规则组”,不参与运行时选规则。 +4. 运行时选规则继续保持: + +```text +document.group_id -> leaudit_rule_group_bindings -> rule_set/rule_version +``` + +## 八、前端改造范围 + +### 8.0 前端 UI 风格约束 + +本次前端改造必须和当前系统 UI 设计、样式、配色保持统一,不允许为了新功能另起一套视觉风格。 + +统一要求: + +```text +继续使用当前系统已有的 Card、Button、Table、FilterPanel、FilterSelect、SearchFilter、Pagination、Modal 等组件 +继续使用当前页面已有的 CSS 变量和主题色 +继续沿用现有后台管理页的布局、间距、圆角、阴影、表格操作列样式 +继续沿用现有 toast、loading、empty state、403 提示样式 +新增表单项要和当前入口模块编辑页的表单布局一致 +新增功能勾选区要像现有权限/配置类页面一样清晰,不做花哨卡片风格 +``` + +禁止: + +```text +不要新增一套独立主题色 +不要引入新的 UI 框架 +不要写和当前后台风格不一致的大面积渐变、特殊字体、复杂动效 +不要把入口模块管理页改成和系统其他管理页完全不同的视觉 +不要为了功能勾选单独设计一套复杂组件 +``` + +具体页面要求: + +```text +入口模块列表页:沿用当前表格、筛选、操作按钮风格 +入口模块编辑页:沿用当前表单分组风格,只增加菜单模板和功能勾选 +侧边栏:沿用当前菜单高亮、图标、折叠、权限过滤表现 +规则分组页:沿用当前树/列表/弹窗样式,不重做视觉 +上传页:只补入口模块上下文和二级分组选择,不重做上传区视觉 +公文列表页:只改标题和过滤参数,不重做列表视觉 +``` + +如果必须新增样式,优先放在现有对应页面样式文件里,并复用当前 CSS 变量,例如: + +```text +var(--color-primary-text) +var(--color-primary-text-muted) +var(--color-border) +var(--color-surface) +``` + +### 8.1 入口模块前端接口类型 + +文件: + +```text +legal-platform-frontend/lib/api/legacy/entry-modules/entry-modules.ts +legal-platform-frontend/lib/api/legacy/entry-modules/request-body.ts +``` + +增加类型: + +```ts +export type EntryModuleMenuProfile = "document_review" | "contract" | "govdoc" | "cross_checking" | "custom"; +export type EntryModuleFeature = + | "home" + | "documents" + | "upload" + | "rules" + | "rule_groups" + | "contract_template_search" + | "contract_template_list" + | "govdoc_audits" + | "govdoc_upload" + | "cross_checking" + | "cross_checking_upload" + | "cross_checking_list" + | "usage_stats"; +``` + +入口模块类型增加: + +```ts +menu_profile?: EntryModuleMenuProfile | string; +features?: EntryModuleFeature[] | string[]; +business_scope?: { + category_count: number; + business_type_count: number; + categories: string[]; +} | null; +``` + +请求体增加: + +```ts +menu_profile?: string | null; +features?: string[]; +``` + +### 8.2 入口模块编辑页 + +文件: + +```text +legal-platform-frontend/app/(audit)/entry-modules/new/EntryModuleNewClient.tsx +``` + +页面增加两个配置区: + +1. 菜单模板选择: + +```text +通用文档评查 +合同工作台 +内部公文工作台 +自定义工作台 +``` + +2. 功能勾选: + +```text +文档列表 +文件上传 +规则配置 +规则分组 +模板搜索 +模板列表 +公文列表 +公文上传 +交叉评查 +创建交叉评查任务 +交叉评查任务列表 +使用统计 +``` + +用户选择菜单模板后,自动带出默认功能,用户可以再调整。 + +权限显示规则: + +```text +没有 entry_module:create:write:隐藏新建入口模块按钮 +没有 entry_module:update:write:隐藏或禁用保存修改按钮 +没有 entry_module:delete:delete:隐藏删除按钮 +没有 entry_module:image:write:隐藏或禁用上传图标能力 +``` + +注意:前端隐藏只是体验优化,最终以后端权限点校验为准。 + +### 8.3 首页点击入口 + +文件: + +```text +legal-platform-frontend/app/page.tsx +``` + +点击入口模块时,除了原来的 `selectedModuleId/selectedModuleName`,还要保存: + +```ts +{ + id, + name, + targetPath, + menuProfile, + features, + tenantCode, + documentTypeIds, + iconPath +} +``` + +建议统一封装到新文件: + +```text +legal-platform-frontend/lib/auth/entry-module-context.ts +``` + +同时 URL 必须带: + +```text +entryModuleId=xxx +documentTypeIds=1,2,3 +``` + +不能只依赖 sessionStorage,否则刷新和复制链接会丢上下文。 + +### 8.4 侧边栏菜单 + +文件: + +```text +legal-platform-frontend/components/layout/Sidebar.tsx +legal-platform-frontend/lib/auth/entry-module-menu.ts +``` + +新增 `entry-module-menu.ts`,把功能编码转成菜单: + +```ts +documents -> 文档列表 /documents +upload -> 文件上传 /files/upload +rules -> 规则配置 /rules-test/list +rule_groups -> 规则分组 /rule-groups +contract_template_search -> 模板搜索 /contract-template/search +contract_template_list -> 模板列表 /contract-template/list +govdoc_audits -> 公文列表 /govdoc/audits +govdoc_upload -> 公文上传 /govdoc/upload +cross_checking -> 交叉评查 /cross-checking +cross_checking_upload -> 创建任务 /cross-checking/upload +cross_checking_list -> 评查任务列表 /cross-checking/list +``` + +侧边栏必须删除这些判断: + +```ts +selectedModuleName.includes("合同") +selectedModuleName.includes("公文") +shouldUseGovdocAuditMenu(effectiveSelectedModuleName) +``` + +改成: + +```text +读取当前入口模块功能清单 +功能清单决定显示哪些菜单 +角色权限系统再过滤用户没权限的菜单 +``` + +重点: + +```text +功能清单不能绕过角色权限系统 +``` + +如果入口模块启用了“模板列表”,但用户角色没有模板列表页面权限,仍然不能显示或访问。 + +入口模块管理菜单本身也要按权限点显示: + +```text +有 entry_module:list:read 才显示入口模块管理菜单 +没有 entry_module:list:read 即使知道路径也不能正常加载数据 +``` + +### 8.5 公文列表页面 + +文件: + +```text +legal-platform-frontend/components/govdoc-audit/audits.tsx +legal-platform-frontend/lib/api/govdoc-audit/api.ts +``` + +要做: + +1. 从 URL 或入口模块上下文读取 `entryModuleId`。 +2. 调用 `api.listAudits()` 时传入 `entryModuleId`。 +3. `api.listAudits()` 转成后端参数: + +```text +entry_module_id=xxx +``` + +4. 页面标题/侧边栏显示当前入口模块名,不再写死“内部公文”。 + +### 8.6 上传页面 + +文件: + +```text +legal-platform-frontend/app/(audit)/files/upload/FilesUploadClient.tsx +``` + +要做: + +1. 上传时读取当前入口模块上下文。 +2. 上传表单数据增加: + +```text +entryModuleId +``` + +3. 继续保留: + +```text +typeId +groupId +tenantCode +``` + +上传后的文档必须具备完整归属: + +```text +entry_module_id + type_id + group_id + tenant_code +``` + +## 九、SQL 迁移脚本 + +建议新增: + +```text +scripts/创建sql/entry_module_menu_profile_migration.sql +``` + +内容: + +```sql +ALTER TABLE leaudit_entry_modules +ADD COLUMN IF NOT EXISTS menu_profile VARCHAR(64) NOT NULL DEFAULT 'document_review', +ADD COLUMN IF NOT EXISTS features JSONB NOT NULL DEFAULT '[]'::jsonb; + +ALTER TABLE leaudit_documents +ADD COLUMN IF NOT EXISTS entry_module_id BIGINT NULL REFERENCES leaudit_entry_modules(id); + +CREATE INDEX IF NOT EXISTS idx_leaudit_entry_modules_menu_profile +ON leaudit_entry_modules(menu_profile) +WHERE deleted_at IS NULL; + +CREATE INDEX IF NOT EXISTS idx_leaudit_documents_entry_module_id +ON leaudit_documents(entry_module_id); + +CREATE UNIQUE INDEX IF NOT EXISTS uq_leaudit_ep_groups_parent_doc_type_active +ON leaudit_evaluation_point_groups(pid, document_type_id) +WHERE deleted_at IS NULL + AND COALESCE(pid, 0) <> 0 + AND document_type_id IS NOT NULL; +``` + +旧数据回填: + +```sql +UPDATE leaudit_entry_modules +SET + menu_profile = CASE + WHEN path IN ('/govdoc/audits', '/govdoc', '/govdoc-audit') THEN 'govdoc' + WHEN path IN ('/contract-template', '/contract-template/list', '/contract-template/search') THEN 'contract' + ELSE COALESCE(NULLIF(menu_profile, ''), 'document_review') + END, + features = CASE + WHEN path IN ('/govdoc/audits', '/govdoc', '/govdoc-audit') + THEN '["home","govdoc_audits","govdoc_upload","rule_groups"]'::jsonb + WHEN path IN ('/contract-template', '/contract-template/list', '/contract-template/search') + THEN '["home","documents","upload","rules","contract_template_search","contract_template_list"]'::jsonb + WHEN features = '[]'::jsonb + THEN '["home","documents","upload","rules","rule_groups"]'::jsonb + ELSE features + END +WHERE deleted_at IS NULL; +``` + +文档归属回填: + +```sql +UPDATE leaudit_documents d +SET entry_module_id = COALESCE(g.entry_module_id, parent.entry_module_id, dt.entry_module_id) +FROM leaudit_document_types dt +LEFT JOIN leaudit_evaluation_point_groups g ON g.id = d.group_id +LEFT JOIN leaudit_evaluation_point_groups parent ON parent.id = g.pid +WHERE d.type_id = dt.id + AND d.entry_module_id IS NULL; +``` + +验证脚本: + +```sql +SELECT id, name, path, menu_profile, features +FROM leaudit_entry_modules +WHERE deleted_at IS NULL +ORDER BY sort_order, id; + +SELECT COUNT(*) AS documents_without_entry_module +FROM leaudit_documents +WHERE deleted_at IS NULL + AND entry_module_id IS NULL; +``` + +## 十、实施顺序 + +### 第一阶段:先修用户能看到的问题 + +目标: + +```text +入口模块名字不含合同/公文,也能按配置显示正确菜单 +``` + +任务: + +1. 后端入口模块请求对象和返回对象增加菜单模板、功能清单。 +2. 入口模块管理页支持菜单模板和功能勾选。 +3. 首页返回入口模块时带菜单模板、功能清单。 +4. 首页点击入口时保存完整上下文。 +5. 侧边栏改成按功能清单生成菜单。 +6. 删除侧边栏按名称包含“合同/公文”的判断。 + +### 第二阶段:列表和上传真正按入口过滤 + +目标: + +```text +进入哪个入口模块,就只看这个入口模块下的文档和公文 +``` + +任务: + +1. 文档上传保存 `entry_module_id`。 +2. 普通文档列表按 `entry_module_id` 过滤。 +3. 公文列表按 `entry_module_id` 过滤。 +4. 公文列表前端传 `entryModuleId`。 +5. 页面刷新后仍能从 URL 恢复入口上下文。 + +### 第三阶段:规则分组范围统一 + +目标: + +```text +规则分组页面只显示当前入口模块相关规则组 +``` + +任务: + +1. 规则分组列表支持 `entry_module_id`。 +2. 前端规则分组页面从 URL/session 读取当前入口。 +3. 规则组创建时继承当前入口模块。 +4. 二级分组绑定具体文档类型。 +5. 规则绑定仍然只挂二级分组。 +6. 上传和评查接口校验 `group_id` 必须是二级分组。 +7. 规则绑定保存接口拒绝一级分组。 + +### 第四阶段:旧逻辑下线 + +目标: + +```text +不再靠名字和路径猜业务类型 +``` + +任务: + +1. 删除 `includes("合同")` 决定合同菜单的逻辑。 +2. 删除 `includes("公文")` 决定公文菜单的逻辑。 +3. 删除 `/govdoc` 强制显示“内部公文”的逻辑。 +4. 保留路径字段只作为跳转地址。 +5. 旧地区字段保留兼容,但新写入只走租户关系。 + +## 十一、验收标准 + +### 11.1 合同入口验收 + +配置: + +```text +入口模块名称:入口模块-测试 +menu_profile:contract +features:contract_template_search, contract_template_list +租户:云浮 +``` + +预期: + +```text +云浮用户首页能看到该入口 +点击后左侧显示模板搜索、模板列表 +入口名字不包含“合同”也正常显示 +其他未绑定租户看不到该入口 +没有角色权限的用户仍然不能访问模板页面 +``` + +### 11.2 公文入口验收 + +配置: + +```text +入口模块名称:入口模块-测试 +route_path:/govdoc/audits +menu_profile:govdoc +features:govdoc_audits +租户:梅州 +``` + +预期: + +```text +梅州用户首页能看到该入口 +点击进入 /govdoc/audits +页面显示入口模块-测试,而不是固定显示内部公文 +公文列表请求带 entry_module_id +列表只显示该入口模块范围内的公文 +``` + +### 11.3 文档列表验收 + +配置: + +```text +入口 A 绑定文档类型 A +入口 B 绑定文档类型 B +同一个租户下分别上传文档 +``` + +预期: + +```text +从入口 A 进入只看到 A 的文档 +从入口 B 进入只看到 B 的文档 +刷新页面后过滤条件不丢 +``` + +### 11.3.1 交叉评查入口验收 + +配置: + +```text +入口模块名称:入口模块-测试交叉评查 +route_path:/cross-checking +menu_profile:cross_checking +features:cross_checking, cross_checking_upload, cross_checking_list +租户:目标租户或 PUBLIC +``` + +预期: + +```text +目标租户首页能看到该入口 +点击进入 /cross-checking +侧边栏显示交叉评查、创建任务、评查任务列表 +没有交叉评查路由权限的用户即使入口模块启用交叉评查功能,也不能看到或访问交叉评查菜单 +``` + +### 11.4 多租户验收 + +配置: + +```text +入口模块 X 只绑定云浮 +入口模块 Y 只绑定梅州 +入口模块 Z 绑定 PUBLIC +``` + +预期: + +```text +云浮用户看到 X 和 Z +梅州用户看到 Y 和 Z +普通用户看不到其他租户入口 +超级管理员可以跨租户管理 +角色权限仍然控制页面和接口能不能访问 +``` + +### 11.5 规则分组和规则绑定验收 + +配置: + +```text +入口模块:合同评查 +一级分组:合同 +二级分组:建设工程合同 +规则绑定:建设工程合同 -> 建设工程合同规则集 +租户:云浮 +``` + +预期: + +```text +从合同评查入口进入,只看到合同一级分组 +建设工程合同显示在合同一级分组下面 +规则绑定只能在建设工程合同这个二级分组上配置 +一级分组合同不能直接绑定规则集 +上传建设工程合同后,文档 group_id 等于建设工程合同二级分组 id +评查时按 group_id + tenant_code 命中云浮对应规则集 +``` + +### 11.6 入口模块配置权限验收 + +配置: + +```text +角色 A 分配 entry_module:list:read +角色 A 不分配 entry_module:create:write / entry_module:update:write / entry_module:delete:delete + +角色 B 分配 entry_module:list:read / entry_module:create:write / entry_module:update:write +角色 B 不分配 entry_module:delete:delete +``` + +预期: + +```text +角色 A 可以查看入口模块列表 +角色 A 看不到新建、编辑保存、删除入口模块能力 +角色 A 直接调用创建/编辑/删除接口返回 403 + +角色 B 可以查看入口模块列表 +角色 B 可以创建和编辑入口模块 +角色 B 不能删除入口模块 +角色 B 直接调用删除接口返回 403 +``` + +关键验收点: + +```text +系统不通过 role_key 硬编码判断谁能配置入口模块 +入口模块配置权限完全由 RBAC 权限点分配决定 +入口模块租户关系只控制可见和使用,不控制谁能配置 +``` + +## 十二、非目标 + +这次重构不做: + +- 不删除角色权限系统。 +- 不删除 `leaudit_rule_type_bindings`。 +- 不改变“文档二级分组 -> 规则绑定 -> 规则集版本”的运行时主链路。 +- 不把入口模块变成权限系统。 +- 不第一阶段支持“同一入口不同租户不同菜单”。 +- 不硬编码某个角色名才能配置入口模块,入口模块管理能力由权限点分配决定。 + +运行时规则仍然保持: + +```text +document.group_id -> leaudit_rule_group_bindings -> rule_set/rule_version +``` + +入口模块只负责: + +```text +用户从哪里进来 +左侧显示哪些菜单 +文档/规则/上传属于哪个业务范围 +``` + +## 十三、风险控制 + +1. `menu_profile/features` 设置默认值,保证老入口模块不崩。 +2. 旧地区字段暂时保留兼容,避免历史数据立即失效。 +3. 功能清单生成菜单后还要经过角色权限过滤,不能绕过权限。 +4. 浏览器地址参数必须带 `entryModuleId`,不能只靠浏览器临时缓存。 +5. 文档上传必须保存 `entry_module_id/type_id/group_id/tenant_code`,后续列表和统计才稳定。 +6. 第一阶段先解决菜单和入口体验,规则运行链路不乱动。 +7. 一级分组只做业务大类,二级分组才做运行类型,规则绑定只能挂二级分组。 +8. 入口模块管理不能靠前端隐藏按钮保证安全,后端必须按权限点返回 403。 + +## 十四、Superpowers 执行任务清单 + +本节是给后续开发直接执行的任务清单。执行时必须从上到下推进,每完成一个小任务就验证一次,不要一次性大改完再排错。 + +### 14.1 执行原则 + +- [x] 先做数据库和后端返回字段,再做前端展示。 +- [x] 先解决入口模块菜单显示错误,再处理列表和上传过滤。 +- [x] 不改运行时评查主链路,只补入口模块、分组、文档归属。 +- [x] 不硬编码角色名判断入口模块管理权限,只使用 RBAC 权限点。 +- [x] 每一步都保留旧数据兼容,避免老入口模块、老文档、旧规则分组直接失效。 + +### 14.2 数据库任务 + +- [x] 新增迁移脚本 `scripts/创建sql/entry_module_menu_profile_migration.sql`。 +- [x] 给 `leaudit_entry_modules` 增加 `menu_profile` 字段。 +- [x] 给 `leaudit_entry_modules` 增加 `features` 字段。 +- [x] 给 `leaudit_documents` 增加 `entry_module_id` 字段。 +- [x] 给 `leaudit_documents.entry_module_id` 增加索引。 +- [x] 给 `leaudit_entry_modules.menu_profile` 增加索引。 +- [x] 给 `leaudit_evaluation_point_groups(pid, document_type_id)` 增加二级分组唯一索引。 +- [x] 回填老入口模块的菜单模板和功能清单。 +- [x] 尝试按二级分组、一级分组、文档类型回填历史文档的 `entry_module_id`。 +- [x] 写验证 SQL,检查入口模块字段、文档归属、重复二级分组。 + +### 14.3 后端入口模块任务 + +- [x] 修改 `fastapi_modules/fastapi_leaudit/domian/Dto/entryModuleDto.py`,创建和更新请求增加 `menu_profile/features`。 +- [x] 修改 `fastapi_modules/fastapi_leaudit/domian/vo/entryModuleAdminVo.py`,入口模块管理返回增加 `menu_profile/features`。 +- [x] 修改 `entryModuleAdminVo.py` 和 `entryModuleAdminServiceImpl.py`,入口模块列表和详情返回 `business_scope` 业务范围摘要。 +- [x] 修改 `fastapi_modules/fastapi_leaudit/domian/vo/homeVo.py`,首页入口返回增加 `menuProfile/features/tenantCode`。 +- [x] 修改 `fastapi_modules/fastapi_leaudit/services/impl/entryModuleAdminServiceImpl.py`,创建入口模块时保存菜单模板和功能清单。 +- [x] 修改 `entryModuleAdminServiceImpl.py`,更新入口模块时保存菜单模板和功能清单。 +- [x] 修改 `entryModuleAdminServiceImpl.py`,列表和详情查询返回菜单模板和功能清单。 +- [x] 修改 `entryModuleAdminServiceImpl.py`,列表和详情查询返回已绑定业务大类数量、业务类型数量和大类名称列表。 +- [x] 在 `entryModuleAdminServiceImpl.py` 增加菜单模板、功能编码白名单校验。 +- [x] 保留 `entryModuleController.py` 现有权限点校验,不新增角色名硬编码。 +- [x] 确认没有权限点时,创建、编辑、删除、上传图标接口都返回 403。 + +### 14.4 后端首页和入口上下文任务 + +- [x] 修改 `fastapi_modules/fastapi_leaudit/services/impl/homeServiceImpl.py`,首页入口查询带出 `menu_profile/features`。 +- [x] 首页入口仍按 `leaudit_entry_module_tenants` 过滤当前租户可见入口。 +- [x] 首页入口返回当前命中的租户编码。 +- [x] 首页入口返回文档类型范围,方便前端拼接上下文。 +- [x] 保持 `PUBLIC` 入口模块兼容。 +- [x] 保持老地区字段兼容,但新链路以租户关系为准。 + +### 14.5 后端文档和公文任务 + +- [x] 修改文档上传接口,接收 `entryModuleId`。 +- [x] 修改 `fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py`,创建文档时写入 `entry_module_id`。 +- [x] 上传时校验 `group_id` 是二级分组。 +- [x] 上传时校验 `group_id` 所属入口模块与 `entryModuleId` 一致。 +- [x] 当前端没传 `group_id` 时,只允许在唯一可用二级分组场景下兜底推断。 +- [x] 普通文档列表支持按 `entry_module_id` 过滤。 +- [x] 修改 `fastapi_modules/fastapi_leaudit/controllers/govdocController.py`,公文列表接收 `entry_module_id`。 +- [x] 修改 `fastapi_modules/fastapi_leaudit/services/impl/govdocServiceImpl.py`,公文列表按 `entry_module_id` 过滤。 +- [x] 公文列表继续保留公文引擎类型过滤。 + +### 14.6 后端规则分组和规则绑定任务 + +- [x] 修改 `fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py`,规则分组列表支持按入口模块过滤。 +- [ ] 一级分组创建时必须有 `entry_module_id`。 +- [x] 一级分组不允许绑定规则集。 +- [x] 二级分组创建时必须有 `document_type_id`。 +- [x] 二级分组必须挂在当前入口模块下的一级分组下面。 +- [x] 规则绑定保存接口只允许传二级分组 `group_id`。 +- [x] 规则绑定保存接口拒绝一级分组。 +- [x] 规则绑定仍保存到 `leaudit_rule_group_bindings`。 +- [x] 评查运行时继续按 `document.group_id -> leaudit_rule_group_bindings -> rule_set` 命中规则。 +- [x] 不把 `leaudit_rule_type_bindings` 重新作为新主链路。 + +### 14.7 前端入口模块管理任务 + +- [x] 所有新增 UI 必须沿用当前系统组件、CSS 变量、表格、表单、弹窗、按钮风格,不另起视觉体系。 +- [x] 修改 `legal-platform-frontend/lib/api/legacy/entry-modules/entry-modules.ts`,增加菜单模板和功能清单类型。 +- [x] 修改 `legal-platform-frontend/lib/api/legacy/entry-modules/entry-modules.ts`,入口模块类型增加 `business_scope` 摘要字段。 +- [x] 修改 `legal-platform-frontend/lib/api/legacy/entry-modules/request-body.ts`,请求体支持菜单模板和功能清单。 +- [x] 修改 `legal-platform-frontend/app/(audit)/entry-modules/EntryModulesClient.tsx`,列表展示菜单模板和功能摘要。 +- [x] 修改 `EntryModulesClient.tsx`,业务范围列按 `business_scope` 展示真实大类和业务类型数量,不再写死“待配置”。 +- [x] 修改 `legal-platform-frontend/app/(audit)/entry-modules/new/EntryModuleNewClient.tsx`,增加菜单模板选择。 +- [x] 修改 `EntryModuleNewClient.tsx`,增加功能勾选区域。 +- [x] 选择菜单模板时自动带出默认功能。 +- [x] 新建按钮按 `entry_module:create:write` 显示。 +- [x] 保存按钮按 `entry_module:update:write` 显示或禁用。 +- [x] 删除按钮按 `entry_module:delete:delete` 显示。 +- [x] 图标上传按 `entry_module:image:write` 显示或禁用。 +- [x] 前端只做体验控制,后端权限点校验必须保留。 + +### 14.8 前端首页和侧边栏任务 + +- [x] 修改 `legal-platform-frontend/app/page.tsx`,点击入口模块时保存完整入口上下文。 +- [x] 新增 `legal-platform-frontend/lib/auth/entry-module-context.ts`,统一读写入口模块上下文。 +- [x] 入口跳转 URL 带 `entryModuleId`。 +- [x] 入口跳转 URL 带 `documentTypeIds`。 +- [x] 修改 `legal-platform-frontend/lib/auth/entry-module-menu.ts`,把功能编码转换为菜单项。 +- [x] 入口模块支持 `cross_checking` 菜单模板和交叉评查功能编码。 +- [x] 修改 `legal-platform-frontend/components/layout/Sidebar.tsx`,侧边栏按入口模块功能清单生成菜单。 +- [x] 删除 `selectedModuleName.includes("合同")`。 +- [x] 删除 `selectedModuleName.includes("公文")`。 +- [x] 删除按 `/govdoc` 路径强行判断公文菜单的逻辑。 +- [x] 侧边栏生成菜单后继续经过用户页面权限过滤。 +- [x] 有 `entry_module:list:read` 才显示入口模块管理菜单。 + +### 14.9 前端公文、上传、规则分组任务 + +- [x] 公文、上传、规则分组页面只补业务上下文和过滤逻辑,不重做页面视觉。 +- [x] 修改 `legal-platform-frontend/components/govdoc-audit/audits.tsx`,从 URL 或入口上下文读取 `entryModuleId`。 +- [x] 修改 `legal-platform-frontend/lib/api/govdoc-audit/api.ts`,请求参数带 `entry_module_id`。 +- [x] 公文页面标题使用当前入口模块名称,不再写死“内部公文”。 +- [x] 修改 `legal-platform-frontend/app/(audit)/files/upload/FilesUploadClient.tsx`,上传表单带 `entryModuleId`。 +- [x] 上传页选择二级分组后传 `groupId`。 +- [x] 上传页在存在多个二级分组时必须让用户明确选择。 +- [x] 修改 `legal-platform-frontend/app/(audit)/rule-groups/RuleGroupsClient.tsx`,规则分组页读取当前入口模块。 +- [x] 规则分组页只展示当前入口模块下的一级分组。 +- [x] 规则绑定入口只出现在二级分组上。 + +### 14.10 测试和验收任务 + +- [x] 验证入口模块名称不包含“合同”,但配置合同功能后侧边栏仍显示模板搜索和模板列表。 +- [x] 验证入口模块名称不包含“公文”,但配置公文功能后仍进入公文列表。 +- [x] 验证没有模板页面权限时,即使入口模块启用模板功能,侧边栏也不显示模板菜单。 +- [x] 验证云浮租户只能看到分配给云浮或 `PUBLIC` 的入口模块。 +- [x] 验证梅州租户只能看到分配给梅州或 `PUBLIC` 的入口模块。 +- [x] 验证没有 `entry_module:create:write` 的用户不能创建入口模块。 +- [x] 验证没有 `entry_module:update:write` 的用户不能编辑入口模块。 +- [x] 验证没有 `entry_module:delete:delete` 的用户不能删除入口模块。 +- [x] 验证普通文档列表按入口模块隔离。 +- [x] 验证公文列表按入口模块隔离。 +- [x] 验证上传文档写入 `entry_module_id/type_id/group_id/tenant_code`。 +- [x] 验证规则绑定不能挂一级分组。 +- [x] 验证规则绑定能挂二级分组。 +- [x] 验证入口模块列表业务范围列展示真实绑定摘要,不再全部显示“待配置”。 +- [x] 验证评查按文档二级分组命中正确规则集。 + +### 14.10.1 执行记录 2026-05-23 + +- [x] 使用 Playwright 真实账号 `000/admin06111` 登录验证首页入口和侧边栏。 +- [x] 验证点击 `合同评查` 后进入 `/documents/list?entryModuleId=1&documentTypeIds=...`。 +- [x] 验证侧边栏顺序为:首页、文件上传、文档列表、规则管理。 +- [x] 验证文档列表菜单不重复。 +- [x] 验证上传文档按钮可见。 +- [x] 使用 Playwright 跑优先项 `2/3/4/5`:`node /tmp/leaudit-playwright-priority-2-5-v3.js`,结果 `25 pass / 0 fail`。 +- [x] 修复二级分组绑定规则集 500:`evaluationPointGroupServiceImpl._get_binding_row` SQL 中 `{access_filter}` 未注入。 +- [x] 回归验证:二级分组绑定规则集返回 200。 +- [x] 回归验证:`pytest -q tests/test_rule_group_binding_scope.py` 结果 `4 passed`。 +- [x] 回归验证:`legal-platform-frontend` 下 `npx tsc --noEmit --pretty false` 通过。 +- [x] 回归验证:目标 eslint 结果 `0 error / 6 warnings`,warning 为既有 ``、未使用变量类提示。 +- [x] 使用 `./leaudit.sh status` 确认后端、前端、Worker、Beat 均运行。 +- [x] 修复公文入口侧边栏仍使用旧“系统概览/文件管理”菜单的问题,统一为入口模块菜单:首页、公文列表、公文上传、规则分组。 +- [x] 修复公文入口侧边栏混入普通“文件上传”的问题,避免和“公文上传”重复或语义冲突。 +- [x] 使用 Playwright 真实账号 `000/admin06111` 跑页面入口 smoke:`node /tmp/leaudit-playwright-page-entry-suite.js`,结果 `31 pass / 0 fail`。 +- [x] 页面入口 smoke 覆盖:合同入口名称不含“合同”仍显示模板搜索/模板列表;公文入口名称不含“公文”仍进入 `/govdoc/audits`;自定义入口只显示首页/文件上传/文档列表;三类入口刷新后不丢上下文。 +- [x] 使用 Playwright 跑优先项 `2/3/4/5`:`node /tmp/leaudit-playwright-priority-2-5-v3.js`,结果 `25 pass / 0 fail`。 +- [x] 优先项覆盖:上传写入 `entryModuleId/typeId/groupId/tenantCode`、入口 A/B 文档列表隔离、规则分组按入口过滤、一级分组拒绝绑定、二级分组可绑定、无入口管理权限接口返回 403。 +- [x] 补充交叉评查入口模块配置能力:入口模块路由下拉支持 `/cross-checking`,菜单模板支持 `cross_checking`,功能清单支持 `cross_checking/cross_checking_upload/cross_checking_list`。 +- [x] 修复首页交叉评查入口仍走旧“写死交叉评查卡片”的问题,改为按真实入口模块渲染,入口名称、入口 ID、功能清单全部来自 `leaudit_entry_modules`。 +- [x] 修复 `/cross-checking` 入口点击不带入口上下文的问题,跳转统一进入 `/cross-checking/list?entryModuleId=...`。 +- [x] 修复交叉评查入口侧边栏混入普通“文件上传”的问题,只显示 `创建任务/评查任务列表` 等交叉评查功能菜单。 +- [x] 使用 Playwright 真实账号 `000/admin06111` 验证交叉评查入口模块:`node /tmp/leaudit-playwright-cross-entry.js`,结果 `8 pass / 0 fail`。 +- [x] 新增数据库只读巡检脚本:`scripts/创建sql/verify_entry_module_menu_profile.sql`,覆盖字段、索引、非法菜单配置、文档入口归属、重复二级分组、未绑定入口模块一级分组。 +- [x] 补充公文列表标题逻辑:有入口模块上下文时显示当前入口模块名称,例如 `入口模块-测试文档列表`;无上下文时才兜底 `内部公文文档列表`。 +- [x] 补充入口模块管理菜单权限过滤:`/entry-modules` 设置子菜单必须带 `entry_module:list:read` 才显示。 +- [x] 回归验证:`node --test --experimental-strip-types tests/govdoc-audit/govdoc-entry-title.test.mts` 结果 `2 pass / 0 fail`。 +- [x] 回归验证:`node --test --experimental-strip-types tests/govdoc-audit/settings-menu-permission.test.mts` 结果 `1 pass / 0 fail`。 +- [x] 修复 `Sidebar` 中无入口上下文时按 `/govdoc` 路径强行套公文菜单的问题;公文菜单只由入口模块 `menuProfile/features` 决定。 +- [x] 使用 Playwright 真实账号 `000/admin06111` 跑剩余项验收:`node test-results/leaudit-playwright-entry-module-remaining-acceptance.js`,结果 `11 pass / 0 fail`。 +- [x] 剩余项验收覆盖:名字不含“公文”的 govdoc 入口可进入;公文列表标题使用当前入口名称;侧栏只显示 `公文列表/公文上传/规则分组`;无 `entry_module:image:write` 的用户上传入口模块图标返回 403。 + +### 14.10.2 下一步验收和开发安排 + +- [x] Playwright 页面级验收入口模块管理页:新建入口、编辑入口、功能勾选保存、功能回显、租户回显。 +- [x] Playwright 页面级验收上传页:文件上传菜单排第二,真实表单选择文档类型和二级分组后提交。 +- [x] Playwright 页面级验收规则分组页:入口 A/B 切换后页面树隔离,一级分组不出现绑定入口,二级分组绑定弹窗可用。 +- [x] Playwright 页面级验收公文入口:入口名称不含“公文”但配置 `govdoc_audits/govdoc_upload` 后,公文列表和上传可进入。 +- [x] Playwright 页面级验收公文详情页:确认不出现平台 Sidebar + 公文工作区 Sidebar 双侧栏。 +- [x] 多租户真实账号验收:云浮、揭阳、梅州分别只看到已分配或 `PUBLIC` 入口模块。 +- [x] 多租户权限验收:没有入口模块管理权限的租户用户不能在 UI 和接口层创建、编辑、删除入口模块。 +- [x] 评查命中验收:上传带二级分组的文档后,启动评查,确认命中该二级分组绑定规则集。 + +### 14.10.3 当前最新状态 2026-05-23 + +当前入口模块主链路已经完成: + +- [x] 入口模块不再靠名称包含“合同/公文”决定菜单。 +- [x] 入口模块支持 `document_review/contract/govdoc/cross_checking/custom` 菜单模板。 +- [x] 入口模块支持 `features` 控制首页、上传、文档列表、规则、规则分组、模板、公文、交叉评查菜单。 +- [x] 首页点击入口后会保存完整入口上下文,并在 URL 带 `entryModuleId/documentTypeIds`。 +- [x] 侧边栏菜单由入口模块 `menuProfile/features` 生成,再经过用户页面权限过滤。 +- [x] 普通文档上传和列表已经接入 `entry_module_id/type_id/group_id/tenant_code`。 +- [x] 公文列表接口已经支持 `entry_module_id`,页面标题已经改为当前入口模块名称。 +- [x] 规则分组列表已经支持入口模块过滤,规则绑定只允许挂二级分组。 +- [x] 入口模块管理权限不硬编码角色名,创建、编辑、删除、上传图标走 RBAC 权限点。 +- [x] 数据库迁移脚本和只读验证 SQL 已补齐。 + +当前仍然需要验收或收口: + +- [x] 模板页面权限过滤:入口模块启用模板功能,但用户没有模板页面权限时,侧边栏不能显示模板菜单。 +- [x] 多租户真实账号验收:云浮、揭阳、梅州只能看到自己租户或 `PUBLIC` 入口。 +- [x] 公文列表数据隔离:同一租户下两个公文入口分别进入时,列表数据不能串入口。 +- [x] 公文详情页侧栏:确认不出现平台 Sidebar 和公文工作区 Sidebar 双侧栏。 +- [x] 入口模块管理页真实表单验收:新建、编辑、功能勾选、租户回显必须跑 Playwright。 +- [x] 上传页真实表单验收:从入口进入后,选择文档类型和二级分组提交,后端落库字段正确。 +- [x] 规则分组页真实页面验收:入口 A/B 切换后树隔离,一级分组无绑定入口,二级分组绑定弹窗可用。 +- [x] 评查命中验收:上传带二级分组的文档后启动评查,确认按该二级分组命中规则集。 + +当前暂缓硬改: + +- [ ] `一级分组创建时必须有 entry_module_id` 仍保留兼容口径,避免旧全局分组或历史数据立即断链。下一轮只先补告警和数据巡检,不直接硬拦。 + +### 14.10.4 执行记录 2026-05-24 + +- [x] 清理 Playwright 和手工验收产生的测试入口模块、测试业务大类、测试规则分组。 +- [x] 测试数据清理采用软删除,未硬删历史文档,避免破坏审计记录和外键引用。 +- [x] 已软删除测试入口模块 31 个:`37,39-68`。 +- [x] 已软删除测试业务大类 24 个:`24-47`。 +- [x] 已软删除测试规则分组 71 个:`52-122`。 +- [x] 已软删除入口租户关联 34 条、规则分组绑定 3 条。 +- [x] 反查验证:未删除测试入口模块 0 个、未删除测试业务大类 0 个、未删除测试规则分组 0 个、测试入口有效租户关联残留 0 条、测试分组有效规则绑定残留 0 条。 +- [x] 修复入口模块列表“业务范围”列全部显示“待配置”的问题。 +- [x] 根因确认:数据库中合同评查、案卷智能评查、内部公文已有业务范围绑定,但 `EntryModulesClient.tsx` 把业务范围列写死为“待配置”,后端入口模块列表也没有返回业务范围摘要。 +- [x] 后端 `EntryModuleVO` 增加 `business_scope`,入口模块列表和详情返回业务大类数量、业务类型数量、大类名称列表。 +- [x] `entryModuleAdminServiceImpl.py` 的 `business_scope` 查询兼容两种历史结构:通过 `leaudit_document_types.entry_module_id` 绑定业务大类,以及通过一级规则分组 `entry_module_id` 统计二级业务类型。 +- [x] 前端 `EntryModule` 类型增加 `business_scope`。 +- [x] 前端新增 `summarizeBusinessEntryScope()`,入口模块列表按真实业务范围展示,例如“建设工程合同、买卖合同、借款合同 等 10 类 / 覆盖 10 个业务类型”。 +- [x] 真实数据库只读验证:合同评查 `10` 个业务大类、`10` 个业务类型;案卷智能评查 `10` 个业务大类、`10` 个业务类型;内部公文 `1` 个业务大类、`1` 个业务类型;智慧法务助手和交叉评查当前未配置业务范围,显示“待配置”符合数据事实。 +- [x] 回归验证:`node --test --experimental-strip-types tests/govdoc-audit/business-entry-ui.test.mts` 结果 `7 pass / 0 fail`。 +- [x] 回归验证:`legal-platform-frontend` 下 `npx tsc --noEmit --pretty false` 通过。 +- [x] 回归验证:`.venv/bin/python -m py_compile fastapi_modules/fastapi_leaudit/domian/vo/entryModuleAdminVo.py fastapi_modules/fastapi_leaudit/services/impl/entryModuleAdminServiceImpl.py` 通过。 +- [x] 回归验证:目标 eslint 通过,`0 error / 0 warning`。 +- [x] RBAC 数据调整:删除 `省级管理员 / provincial_admin` 角色。 +- [x] RBAC 删除级联清理:`user_role` 1 条、`role_permissions` 94 条、`role_route` 29 条。 +- [x] 将 `000` 对应用户调整为系统超级管理员:当前库中该用户为 `sso_users.id=5, sub=000, username=admin`。 +- [x] RBAC 反查验证:`provincial_admin` 剩余 0 个;`000` 用户当前只绑定 `super_admin / 系统超级管理员 / data_scope=ALL`。 +- [x] 使用 Playwright 真实账号 `000/admin06111` 验收入口模块编辑页和上传页:`LEAUDIT_FRONTEND_URL=http://127.0.0.1:5193 node /tmp/leaudit-playwright-entry-upload-management-v1.js`,结果 `16 pass / 0 fail`。 +- [x] 入口模块编辑页验收覆盖:4 步表单、菜单模板回显、功能勾选回显、PUBLIC 租户回显、业务范围回显。 +- [x] 上传页验收覆盖:从入口上下文进入后按入口加载业务大类和业务类型,显示所属业务入口,上传接口和文档详情接口返回正确 `entryModuleId/typeId/groupId/tenantCode`。 +- [x] 修复上传页单一业务类型时查不到二级分组的问题:`FilesUploadClient.tsx` 不再只在 `childDocumentTypeIds.length > 1` 时批量查询二级分组。 +- [x] 后端文档上传、列表、详情 VO 返回 `entryModuleId`,方便前端和测试确认文档入口归属。 +- [x] 修复公文详情页双侧栏风险:`/govdoc/detail/...` 和 `/govdoc-audit/detail/...` 不再渲染平台 Sidebar,只保留公文工作区布局。 +- [x] 回归验证:`.venv/bin/python -m py_compile fastapi_modules/fastapi_leaudit/domian/vo/documentVo.py fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py` 通过。 +- [x] 回归验证:`legal-platform-frontend` 下 `npx tsc --noEmit --pretty false` 通过。 +- [x] 回归验证:目标 eslint 通过,`0 error / 1 warning`,warning 为 `files-upload.ts` 被 eslint ignore,非本次阻塞。 +- [x] 本轮 Playwright 临时数据已清理:`PW入口编辑上传-pwui%`、`pw.root.pwui%`、`pw.child.pwui%`、`pw.type.pwui%`、`pw-ui-upload-pwui%` 有效残留均为 0。 +- [x] 环境记录:`./leaudit.sh` 本轮出现 pidfile/后台进程状态不稳定,真实验收改用 TTY 手动启动后端 `run.py` 和前端 `npm run dev:dev`,Playwright 统一访问 `http://127.0.0.1:5193`。 +- [x] 使用 Playwright 真实账号 `000/admin06111` 验收规则分组页:`LEAUDIT_FRONTEND_URL=http://127.0.0.1:5193 node /tmp/leaudit-playwright-rule-groups-scope-v1.js`,结果 `21 pass / 0 fail`。 +- [x] 规则分组页验收覆盖:入口 A/B 树隔离、请求带 `entry_module_id`、一级分组不显示“配置规则”、后端拒绝一级分组绑定规则集、二级分组绑定弹窗可打开并保存。 +- [x] 规则分组页验收发现一个非产品 BUG:临时脚本第一次按旧弹窗类名 `.modal-content/[role=dialog]` 断言失败;当前页面实际使用 `.rg-modal`,修正脚本后通过。 +- [x] 规则分组 Playwright 临时数据已清理:`PW规则分组入口%`、`pw.rg.type.%`、`pw.rg.%`、`note like '%pwrg%'` 有效残留均为 0。 +- [x] 使用 Playwright 真实账号 `000/admin06111` 验收公文入口深度链路:`LEAUDIT_FRONTEND_URL=http://127.0.0.1:5193 node /tmp/leaudit-playwright-govdoc-entry-scope-v1.js`,结果 `17 pass / 0 fail`。 +- [x] 公文入口深度验收覆盖:创建 A/B 公文入口、创建 A/B 公文业务类型、A/B 公文列表请求带 `entry_module_id`、A/B 列表数据不串、标题使用当前入口模块名称、公文上传链接携带 `entryModuleId/documentTypeIds`、公文上传落库携带 `entry_module_id/type_id`、公文详情页不渲染平台 Sidebar。 +- [x] 修正公文 Playwright 验收脚本:不再用 `request.postData()` 判断 multipart 表单,因为该方式对文件上传体不稳定;改为上传后解析新 `documentId` 并查询数据库验证 `leaudit_documents.entry_module_id/type_id`。 +- [x] 公文 Playwright 临时数据已清理:`PW公文入口%`、`pw.govdoc.type.%`、`pw-govdoc-%` 有效文档残留均为 0。 +- [x] 发现并记录一个非阻塞清理差异:公文删除接口会软删 `leaudit_documents`,但 `leaudit_document_files` 中历史测试文件记录仍保持 `deleted_at is null`;当前列表以文档主表删除态过滤,不影响入口隔离验收,后续如要严格清理可单独优化删除服务。 +- [x] 使用 Playwright 真实账号验收多租户入口可见性和普通权限:`LEAUDIT_FRONTEND_URL=http://127.0.0.1:5193 node /tmp/leaudit-playwright-multitenant-entry-scope-v1.js`,结果 `37 pass / 0 fail`。 +- [x] 多租户验收账号:`yf001/yfyc06111`、`jy001/jyyc06111`、`mz001/mzyc06111`、`001/gdyc06111`。 +- [x] 多租户验收覆盖:云浮只看 `合同评查/案卷智能评查/内部公文`;揭阳只看 `合同评查/案卷智能评查/内部公文/交叉评查`;梅州地区管理员看 `合同评查/案卷智能评查/内部公文/智慧法务助手/交叉评查`;普通用户 `001` 看不到 `智慧法务助手`。 +- [x] 权限验收覆盖:普通用户 `001` 首页不显示系统设置按钮,直调 `/api/v3/entry-modules` 返回 403。 +- [x] 使用 Playwright APIRequestContext + 数据库只读查询验收评查命中链路:真实账号 `mz001/mzyc06111` 上传 `legal-platform-frontend/public/testWork/(最终版)智慧法务平台建设采购项目合同(1).docx`,传入 `typeId=1/groupId=2/entryModuleId=1/tenant_code=MZ/autoRun=true`。 +- [x] 评查命中验收结果:上传生成 `documentId=232/runId=110`,`leaudit_documents` 写入 `type_id=1/group_id=2/entry_module_id=1/tenant_code=MZ/current_run_id=110`。 +- [x] 规则集命中数据库证据:`leaudit_audit_runs.id=110` 写入 `rule_set_id=126/rule_version_id=500/group_id_snapshot=2/rule_binding_id_snapshot=101/tenant_code=MZ/scope_type_snapshot=TENANT`,符合 `leaudit_rule_group_bindings.group_id=2` 下梅州租户绑定。 +- [x] 使用 Playwright 真实账号 `001/gdyc06111` 验收模板页面权限过滤:进入 `合同评查` 后 URL 为 `/documents/list?entryModuleId=1&documentTypeIds=...`,侧栏显示 `文件上传/文档列表`,不显示 `合同管理/模板搜索/模板列表`。 +- [x] 模板权限验收补充证据:`001` 为 `common` 角色,无 `contract_template:*` 和 `entry_module:*` 权限;`/api/auth/session-data` 返回的合同入口 `features` 已被裁剪为 `home/documents/upload/rules/rule_groups`,`permissionMap` 不含 `/contract-template` 路由。 +- [x] RBAC 权限数据收口:已按“入口模块只能系统超级管理员维护”口径移除 `admin / 地区管理员` 的 `entry_module:create/update/delete/image/list/detail` 权限和 `/entry-modules` 菜单。 +- [x] 新增可重复执行 SQL:`scripts/创建sql/rbac_entry_module_super_admin_only.sql`,用于保证 `super_admin` 保留入口模块权限,同时移除 `admin/provincial_admin` 的入口模块权限和菜单。 +- [x] 修复首页系统设置按钮跳转:不再固定跳 `/entry-modules`,而是跳当前用户有权限的第一个设置子菜单;没有任何设置子菜单时不显示系统设置按钮。 +- [x] RBAC 收口真实验收:`mz001/mzyc06111` 登录后无 `entry_module:*` 权限,直调 `/api/v3/entry-modules` 返回 403,`settingsChildren` 不含 `/entry-modules`。 +- [x] RBAC 收口真实验收:`000/admin06111` 登录后仍有入口模块 6 个权限,直调 `/api/v3/entry-modules` 返回 200,`settingsChildren` 包含 `/entry-modules`。 + +### 14.10.5 当前最新状态 2026-05-24 + +当前已完成并验证: + +- [x] 入口模块列表业务范围列已经接真实后端摘要,不再统一显示“待配置”。 +- [x] 合同评查、案卷智能评查、内部公文的业务范围摘要和当前数据库绑定一致。 +- [x] 智慧法务助手、交叉评查当前没有绑定业务大类,所以继续显示“待配置”是正确状态。 +- [x] 测试入口模块和测试业务大类已经清理,不会继续污染入口模块管理页和业务大类管理页。 +- [x] `000/admin06111` 对应账号已变成系统超级管理员,后续 Playwright 验收使用该账号时具备系统级管理权限。 +- [x] `provincial_admin` 已删除,后续计划和验收不要再依赖省级管理员这个角色。 +- [x] 入口模块管理页和上传页已经完成真实 Playwright 验收,上传链路能落 `entry_module_id/type_id/group_id/tenant_code`。 +- [x] 规则分组页已经完成真实 Playwright 验收,入口 A/B 不串树,一级不绑定规则,二级绑定弹窗可用。 +- [x] 公文详情页双侧栏代码风险已修复,并已用真实公文详情页面 Playwright 确认。 +- [x] 多租户真实账号验收已完成,云浮、揭阳、梅州入口可见性符合当前入口模块租户分配和 RBAC 菜单权限。 +- [x] 模板页面权限过滤已完成真实 Playwright 验收,普通用户没有模板页面权限时,即使进入合同入口也不显示模板菜单。 +- [x] 评查命中验收已完成,普通文档上传后按 `document.group_id -> leaudit_rule_group_bindings -> rule_set/current_version_id` 命中租户级规则集。 + +后续仍需继续验收: + +- [x] RBAC 权限数据收口:已从 `admin / 地区管理员` 移除入口模块管理权限,当前只有系统超级管理员可以维护入口模块。 + +### 14.10.6 下一轮执行顺序 + +前置调整: + +```text +真实前端 Playwright 全量验收前,先执行入口模块和文档类型的 UI/交互优化计划: +docs/superpowers/plans/2026-05-23-business-entry-ux-optimization.md +``` + +原因: + +```text +当前“入口模块管理 + 文档类型管理 + 规则分组 + 上传”的技术语义学习成本过高。 +如果先写真实前端测试,会把当前复杂交互固化下来。 +应先把页面统一成“业务入口、业务大类、业务类型、规则配置”的用户语义,再做真实 Playwright 验收。 +``` + +按下面顺序继续,不要先碰评查引擎: + +1. P0:入口模块管理页 Playwright 验收。 + + 验收内容: + + ```text + 新建入口 -> 选择菜单模板 -> 勾选功能 -> 分配租户 -> 保存 -> 列表回显 -> 编辑回显 + ``` + + 输出要求: + + ```text + 已完成。继续维护时如果页面字段或回显错,先修前端表单和接口字段映射,不改运行时评查逻辑。 + ``` + +2. P1:上传页真实表单 Playwright 验收。 + + 验收内容: + + ```text + 从入口模块进入上传页 -> 文件上传菜单排第二 -> 选择文档类型 -> 选择二级分组 -> 提交 + ``` + + 输出要求: + + ```text + 已完成。验收确认数据库或接口返回里存在 entry_module_id/type_id/group_id/tenant_code。 + ``` + +3. P2:规则分组页 Playwright 验收。 + + 验收内容: + + ```text + 入口 A 只看到 A 的一级分组 + 入口 B 只看到 B 的一级分组 + 一级分组不出现绑定规则入口 + 二级分组可以打开绑定弹窗并保存 + ``` + + 输出要求: + + ```text + 已完成。验收确认前端透传 `entryModuleId`,后端列表过滤生效,一级/二级规则绑定边界正确。 + ``` + +4. P3:公文入口深度验收。 + + 验收内容: + + ```text + 公文列表按 entry_module_id 隔离 + 公文上传能从入口模块进入 + 公文详情页不出现双侧栏 + ``` + + 输出要求: + + ```text + 如果列表串数据,优先查 govdoc API 请求参数和 GovdocServiceImpl 查询过滤。 + ``` + +5. P4:多租户真实账号验收。 + + 验收内容: + + ```text + 云浮用户只看到云浮或 PUBLIC 入口 + 揭阳用户只看到揭阳或 PUBLIC 入口 + 梅州用户只看到梅州或 PUBLIC 入口 + 没有入口模块管理权限的租户用户 UI 不显示管理能力,接口返回 403 + ``` + + 输出要求: + + ```text + 如果缺少真实账号,先用现有 RBAC/租户数据造最小测试账号,并记录账号和租户。 + ``` + +6. P5:评查命中验收。 + + 验收内容: + + ```text + 上传带二级分组的文档 -> 绑定该二级分组规则集 -> 启动评查 -> 确认命中该规则集 + ``` + + 输出要求: + + ```text + 这里只验证 group_id -> leaudit_rule_group_bindings -> rule_set 主链路,不重构评查引擎。 + ``` + +### 14.10.7 下一轮可并行安排 + +可以拆成 3 个并行工作流。当前 3 个工作流均已完成,本节保留为后续复盘参考: + +- [x] 工作流 A:入口模块管理页 + 上传页 Playwright 验收,主要看表单和字段落库。 +- [x] 工作流 B:规则分组页 + 公文入口深度验收,主要看入口上下文过滤和侧栏。 +- [x] 工作流 C:多租户真实账号 + 模板权限过滤 + 评查命中验收,主要看权限、租户和运行时规则命中。 + +并行约束: + +- [x] 三个工作流都必须先执行 `./leaudit.sh status`,确认后端、前端、Worker、Beat 运行。 +- [x] UI 验收必须使用 Playwright,账号优先使用 `000/admin06111`,多租户验收再补具体租户账号。 +- [x] 每个工作流完成后都要回写本计划文档的执行记录。 +- [x] 发现 BUG 先补最小复现脚本,再修代码,最后跑对应 Playwright 或 pytest 回归。 + +### 14.11 建议提交顺序 + +1. 数据库迁移脚本。 +2. 后端 DTO/VO 和入口模块服务。 +3. 首页入口返回和前端入口上下文。 +4. 侧边栏菜单生成逻辑。 +5. 入口模块管理页菜单模板和功能勾选。 +6. 文档上传和列表入口模块归属。 +7. 公文列表入口模块过滤。 +8. 规则分组和规则绑定校验。 +9. 删除名称包含“合同/公文”的旧判断。 +10. 跑完整验收清单。 + +### 14.12 暂不执行的任务 + +- [ ] 暂不做同一个入口模块不同租户不同功能清单。 +- [ ] 暂不删除旧地区字段。 +- [ ] 暂不删除旧规则类型绑定表。 +- [ ] 暂不重写评查运行引擎。 +- [ ] 暂不把入口模块变成权限系统。 + +### 14.13 下一阶段收口计划:entry_module_id 强一致性 + +#### 14.13.1 先把概念说清楚 + +`entry_module_id` 不是给用户看的新概念,它在前端应该叫“业务入口”或“工作台”。 + +它的作用只有一个:让系统知道一条数据属于哪个入口模块。 + +如果没有 `entry_module_id`,系统只能靠下面这些不稳定方式猜: + +```text +入口名字里有没有“合同” +当前 URL 是不是 /govdoc +文档类型刚好属于哪个入口 +规则分组刚好挂在哪个父节点下面 +``` + +这些猜法会导致同一个租户下多个入口互相串数据,例如: + +```text +合同入口能看到案卷文档 +公文入口侧边栏混入普通文件上传 +规则分组 A/B 入口互相出现 +上传后评查命中错规则集 +``` + +所以新链路必须明确落字段: + +```text +入口模块 leaudit_entry_modules.id + -> 业务大类/一级分组 leaudit_evaluation_point_groups.entry_module_id + -> 业务类型/二级分组 group_id + -> 文档 leaudit_documents.entry_module_id + group_id + -> 评查按 group_id 命中规则绑定 +``` + +用户理解口径: + +```text +我从哪个业务入口进来,上传、列表、规则配置就只看这个入口下的数据。 +``` + +开发理解口径: + +```text +entry_module_id 是入口隔离字段。 +tenant_code 管租户边界。 +group_id 管规则命中。 +type_id 管文档分类。 +features/menu_profile 管菜单显示。 +``` + +#### 14.13.2 当前不直接强拦的原因 + +当前代码里“一级业务大类必须有 `entry_module_id`”还没有完全强拦,是刻意保留兼容。 + +原因: + +```text +老数据里可能还有全局一级分组 +老文档可能只能从 type_id 或 group_id 推导入口 +如果现在直接 400,可能会把旧规则配置、旧上传链路或历史列表打断 +``` + +因此下一阶段不能一步到位硬改,必须按下面顺序推进: + +```text +先巡检旧数据 +再修复或回填孤儿数据 +再加后端软提示/日志 +最后才把新建和编辑一级业务大类改成必须有入口模块 +``` + +#### 14.13.3 实现顺序 + +1. 数据巡检。 + + 执行只读巡检脚本: + + ```bash + psql "$DATABASE_URL" -f scripts/创建sql/verify_entry_module_menu_profile.sql + ``` + + 重点看这几类结果: + + ```text + 文档 entry_module_id 为空 + 文档 entry_module_id 和 group/type 推导不一致 + 重复二级分组 + 一级分组 entry_module_id 为空 + 非法 menu_profile/features + ``` + +2. 旧数据处理。 + + 如果一级分组没有 `entry_module_id`,先按可推导关系回填: + + ```text + 一级分组有 document_type_id -> 用 leaudit_document_types.entry_module_id 回填 + 一级分组下二级分组有 document_type_id -> 用二级分组对应文档类型的 entry_module_id 回填 + 确实无法推导 -> 先列清单人工判断,不自动乱填 + ``` + + 注意: + + ```text + 不允许为了清零数据随便塞 entry_module_id=1 + 不允许把跨入口共用的旧分组直接强绑到某一个入口 + ``` + +3. 后端创建校验收紧。 + + 文件: + + ```text + fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py + ``` + + 目标: + + ```text + 新建一级业务大类 pid=0 时,必须传 entry_module_id + 新建一级业务大类不允许只靠 document_type_id 反推入口 + 新建二级业务类型 pid!=0 时,继承父级 entry_module_id + 新建二级业务类型必须绑定 document_type_id + 二级业务类型的 document_type_id 所属入口必须和父级入口一致 + ``` + + 兼容策略: + + ```text + 只强拦新建/编辑请求 + 不因为历史数据缺 entry_module_id 导致列表查询直接失败 + ``` + +4. 前端规则配置页补齐入口上下文。 + + 文件: + + ```text + legal-platform-frontend/app/(audit)/rule-groups/RuleGroupsClient.tsx + ``` + + 目标: + + ```text + 从业务入口进入规则配置页时,新增业务大类自动带当前 entryModuleId + 页面顶部显示“当前业务入口:xxx” + 如果没有入口上下文,不允许新增业务大类,只提示从首页业务入口进入 + ``` + + 用户看到的是: + + ```text + 当前业务入口:合同评查 + 新增业务大类时自动归属当前入口 + ``` + + 用户不需要看到: + + ```text + entry_module_id + pid = 0 + group_id + ``` + +5. 上传页保持现有两级选择。 + + 文件: + + ```text + legal-platform-frontend/app/(audit)/files/upload/FilesUploadClient.tsx + ``` + + 目标不变: + + ```text + 先选业务大类 + 再选业务类型 + 提交 entryModuleId/typeId/groupId/tenantCode + ``` + + 这里不要做自动创建业务类型,也不要跳过业务类型选择。 + +6. 验收。 + + 必须使用 Playwright 真实账号验收: + + ```text + 000/admin06111:新增业务大类 -> 新增业务类型 -> 配置规则 -> 上传 -> 列表只看当前入口 + mz001/mzyc06111:不能进入业务入口管理,能使用已分配入口 + yf001/yfyc06111:只能看到云浮已分配入口 + jy001/jyyc06111:只能看到揭阳已分配入口 + ``` + + 数据库验收: + + ```text + leaudit_evaluation_point_groups 一级分组有 entry_module_id + leaudit_evaluation_point_groups 二级分组能通过父级拿到 entry_module_id + leaudit_documents 上传后有 entry_module_id/type_id/group_id/tenant_code + leaudit_audit_runs 按 group_id_snapshot 命中 rule_binding_id_snapshot + ``` + +#### 14.13.4 推荐下一步直接做什么 + +下一步不先改代码,先做一次真实数据库巡检并记录结果。 + +执行顺序: + +```text +1. 运行 verify_entry_module_menu_profile.sql +2. 把异常结果整理到本计划文档 +3. 如果一级分组 entry_module_id 为空数量为 0,再开始后端强校验 +4. 如果不为 0,先补一个只读清单和人工回填 SQL 草案 +5. 回填完成后再改后端校验和前端规则配置页 +``` + +这样做的原因: + +```text +entry_module_id 是隔离字段,一旦强校验加错,会直接影响上传、规则配置、列表过滤和评查命中。 +先把数据状态摸清,再收紧代码,风险最低。 +``` + +#### 14.13.5 巡检记录 2026-05-24 + +已执行: + +```bash +PGPASSWORD='zhfw*123*' psql -h nas.7bm.co -p 54302 -U docauditai_admin -d leaudit_platform -f scripts/创建sql/verify_entry_module_menu_profile.sql +``` + +同时修正巡检脚本字段错误: + +```text +leaudit_documents 没有 filename 字段,实际字段为 normalized_name。 +已把脚本中的 d.filename 改为 d.normalized_name AS document_name。 +``` + +巡检结论: + +```text +必要字段存在: +leaudit_entry_modules.menu_profile +leaudit_entry_modules.features +leaudit_documents.entry_module_id + +必要索引存在: +idx_leaudit_entry_modules_menu_profile +idx_leaudit_documents_entry_module_id + +非法 menu_profile:0 条 +非法 feature 编码:0 条 +重复二级分组:0 条 +文档 entry_module_id 与 group/type 推导不一致:0 条 +``` + +发现的问题: + +```text +案卷智能评查 features 为空 +智慧法务助手 features 为空 +历史文档 entry_module_id 为空:68 条 +其中 65 条可以通过 group/type 推导入口 +其中 3 条公文示例没有 type_id/group_id,暂时无法自动推导入口 +一级业务大类 entry_module_id 为空:14 条 +``` + +68 条历史文档缺入口归属的推导结果: + +```text +可推导为 合同评查 entry_module_id=1:47 条 +可推导为 案卷智能评查 entry_module_id=2:14 条 +可推导为 内部公文 entry_module_id=3:4 条 +无法推导:3 条,均为旧公文示例,type_id/group_id 为空 +``` + +14 条一级业务大类缺入口归属的来源: + +```text +行政卷宗 root.casefile:可通过子业务类型推导为 案卷智能评查 entry_module_id=2 +testmzceshi:测试残留,无子节点、无 document_type_id,需要人工确认是否删除 +12 条 PW*/pw.* 测试业务大类:来自已软删除的 Playwright 临时入口,关联文档类型仍未软删 +``` + +关键判断: + +```text +当前正式 5 个入口本身没有结构性问题。 +真正阻塞强校验的是历史数据和 Playwright 测试残留。 +现在不能直接加“一级业务大类必须有 entry_module_id”的硬拦截,否则旧残留会继续污染巡检,也可能影响旧数据展示。 +``` + +建议下一步: + +```text +1. 先出只读清单 SQL,列出可自动回填的 65 条历史文档和 root.casefile。 +2. 出数据修复 SQL 草案,但先不执行。 +3. Playwright 测试残留建议统一软删:pw.* 文档类型、PW* 规则分组、对应临时入口关系。 +4. testmzceshi 需要人工确认是否删除。 +5. 3 条无 type_id/group_id 的旧公文示例不要自动回填,保留人工处理。 +6. 数据修复完成后,再改后端强校验和前端规则配置页入口上下文。 +``` + +#### 14.13.6 数据修复执行记录 2026-05-24 + +已新增并执行数据修复脚本: + +```text +scripts/创建sql/repair_entry_module_scope_data_20260524.sql +``` + +执行前做了事务回滚演练: + +```text +可回填历史文档:65 条 +可回填一级业务大类:1 条,root.casefile 行政卷宗 +可继承父级入口的二级业务类型:19 条 +可安全软删测试规则分组:25 条 +可补默认功能的入口模块:1 条,案卷智能评查 +``` + +正式执行结果: + +```text +UPDATE 65 +UPDATE 1 +UPDATE 19 +UPDATE 25 +UPDATE 1 +COMMIT +``` + +修复内容: + +```text +65 条历史文档已按 group/type 推导回填 entry_module_id。 +行政卷宗 root.casefile 已回填 entry_module_id=2,归属案卷智能评查。 +行政卷宗及合同正式二级业务类型已继承父级 entry_module_id。 +testmzceshi 和 PW*/pw.* 无引用测试规则分组已软删除。 +案卷智能评查已补齐默认功能:home/documents/upload/rules/rule_groups。 +``` + +修复后巡检结果: + +```text +非法 menu_profile:0 条 +非法 feature 编码:0 条 +文档 entry_module_id 与 group/type 推导不一致:0 条 +重复二级分组:0 条 +一级业务大类 entry_module_id 为空:0 条 +活跃 PW 测试文档类型:0 条 +活跃测试规则分组残留:0 条 +活跃文档类型指向已删除入口:0 条 +``` + +剩余问题: + +```text +仍有 3 条旧公文示例文档 entry_module_id 为空。 +这 3 条同时没有 type_id 和 group_id,无法确定属于哪个入口模块。 +本次没有自动回填,避免把历史公文示例错误塞进内部公文或其他入口。 +``` + +剩余 3 条: + +```text +id=46 公文示例-瑕疵 +id=47 公文示例-瑕疵 +id=48 公文示例-合规-OOXML修正版 +``` + +后续处理建议: + +```text +如果确认它们是内部公文旧测试数据,可以单独回填 entry_module_id=3。 +如果只是历史测试样例,也可以软删除。 +在未确认前,不建议自动处理。 +``` + +#### 14.13.7 旧公文示例回填记录 2026-05-24 + +用户已确认 3 条旧公文示例可以回填。 + +已新增并执行: + +```text +scripts/创建sql/repair_govdoc_sample_entry_module_20260524.sql +``` + +执行条件: + +```text +只更新 id IN (46, 47, 48) +只更新 deleted_at IS NULL +只更新 entry_module_id IS NULL +只更新 engine_type='govdoc' 且 review_scope='govdoc' +只回填 entry_module_id=3,不伪造 type_id/group_id +``` + +执行结果: + +```text +UPDATE 3 +COMMIT +``` + +回填后结果: + +```text +id=46 公文示例-瑕疵 -> entry_module_id=3 +id=47 公文示例-瑕疵 -> entry_module_id=3 +id=48 公文示例-合规-OOXML修正版 -> entry_module_id=3 +``` + +最终巡检结果: + +```text +documents_without_entry_module = 0 +文档 entry_module_id 与 group/type 推导不一致 = 0 +一级业务大类 entry_module_id 为空 = 0 +重复二级分组 = 0 +非法 menu_profile = 0 +非法 feature 编码 = 0 +``` diff --git a/docs/superpowers/plans/2026-05-23-entry-module-phase2-scope-completion.md b/docs/superpowers/plans/2026-05-23-entry-module-phase2-scope-completion.md new file mode 100644 index 0000000..31f69f3 --- /dev/null +++ b/docs/superpowers/plans/2026-05-23-entry-module-phase2-scope-completion.md @@ -0,0 +1,248 @@ +# 入口模块重构第二阶段实施计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 补齐入口模块到普通文档、上传、规则分组、页面布局的闭环,让用户从某个入口进去后,只看到、上传、配置该入口范围内的业务数据。 + +**Architecture:** 第一阶段已完成入口上下文、菜单 features 过滤、公文列表/上传入口归属。第二阶段继续沿用 `selectedEntryModuleContext`,所有业务页面从该上下文读取 `entryModuleId/documentTypeIds`,后端继续用 `entry_module_id/type_id/group_id/tenant_code` 做数据收口,不新增权限体系。 + +**Tech Stack:** FastAPI、SQLAlchemy 原生 SQL、Next.js、React、现有 RBAC 权限、现有 `leaudit_entry_modules / leaudit_document_types / leaudit_evaluation_point_groups / leaudit_documents` 表。 + +--- + +## 一、执行原则 + +- 不再用入口名称判断业务类型。 +- 不改现有 UI 视觉风格,只补数据范围、参数和必要提示。 +- 不删除旧接口,先补安全约束和兼容字段。 +- 先做普通文档链路,再做规则分组链路,最后处理重复侧边栏和验收。 +- 每一批改完都跑 `py_compile`、`tsc`、目标 `eslint`。 + +## 二、文件责任 + +- `legal-platform-frontend/lib/auth/entry-module-context.ts`:继续作为前端入口上下文唯一读写入口。 +- `legal-platform-frontend/lib/api/legacy/files/documents.ts`:普通文档列表、上传 API 参数补入口模块字段。 +- `legal-platform-frontend/app/(audit)/files/upload/*` 或实际上传客户端文件:上传表单带 `entryModuleId/typeId/groupId`。 +- `legal-platform-frontend/app/(audit)/documents/*` 或实际文档列表客户端文件:文档列表按入口上下文传 `entryModuleId/documentTypeIds`。 +- `fastapi_modules/fastapi_leaudit/controllers/documentController.py`:普通上传接口接收 `entryModuleId`。 +- `fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py`:创建文档写入 `entry_module_id`,校验 `group_id` 是二级分组且属于当前入口。 +- `legal-platform-frontend/app/(audit)/rule-groups/RuleGroupsClient.tsx`:规则分组页读取入口上下文,只展示当前入口分组。 +- `fastapi_modules/fastapi_leaudit/controllers/evaluationPointGroupController.py`:分组列表/创建/更新接收或校验入口模块参数。 +- `fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py`:一级/二级分组边界、入口模块归属、绑定二级分组校验。 +- `legal-platform-frontend/components/layout/Layout.tsx`:处理公文详情页内部工作区侧栏和平台 Sidebar 重复问题。 + +--- + +## 三、任务拆分 + +### Task 1:普通文档上传写入入口模块 + +**目标:** 用户从入口模块进入上传页时,上传出来的文档写入 `entry_module_id/type_id/group_id/tenant_code`。 + +**Files:** +- Modify: `fastapi_modules/fastapi_leaudit/controllers/documentController.py` +- Modify: `fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py` +- Modify: `legal-platform-frontend/lib/api/legacy/files/documents.ts` +- Modify: 实际上传页面客户端,先用 `rg -n "DocumentUpload|uploadDocument|/upload" legal-platform-frontend/app legal-platform-frontend/components` + +- [x] 后端 `UploadDocument` 增加 `entryModuleId: int | None = Form(None)`。 +- [x] `DocumentService.Upload(...)` 增加 `EntryModuleId` 参数。 +- [x] 创建 `LeauditDocument` 后写入 `entry_module_id`。 +- [x] 如果传了 `groupId`,校验该分组 `pid <> 0`。 +- [x] 如果传了 `entryModuleId + groupId`,校验二级分组所属入口等于 `entryModuleId`。 +- [x] 如果没传 `groupId`,只允许唯一二级分组时后端兜底;多个二级分组时报 400,让前端用户明确选择。 +- [x] 前端上传 API 从 `readEntryModuleContext()` 读取 `entryModuleId/documentTypeIds`。 +- [x] 上传表单把 `entryModuleId`、选中的 `typeId`、选中的 `groupId` 一起提交。 + +**验收:** +- 上传成功后数据库 `leaudit_documents.entry_module_id` 有值。 +- 多个二级分组时,不选 `groupId` 不能上传。 +- 传一级分组 ID 时后端返回 400。 + +### Task 2:普通文档列表按入口模块过滤 + +**目标:** 从不同入口进入普通文档列表,只看到当前入口范围的文档。 + +**Files:** +- Modify: `legal-platform-frontend/lib/api/legacy/files/documents.ts` +- Modify: 普通文档列表客户端,先用 `rg -n "listDocuments|getDocuments|documents.searchParams" legal-platform-frontend/app legal-platform-frontend/components` +- Backend already has: `documentController.py` 的 `entry_module_id/type_ids` 和 `documentServiceImpl.py` 的过滤基础。 + +- [x] 文档列表页面读取入口上下文作用域(URL/session,由入口首页写入)。 +- [x] 请求参数带 `entry_module_id`。 +- [x] 请求参数带 `type_ids`,作为入口文档类型范围的兜底过滤。 +- [x] 保留现有搜索、分页、状态过滤,不改 UI。 +- [x] 切换入口模块时清理 `documents.searchParams`,避免旧入口搜索条件污染新入口。 + +**验收:** +- 入口 A 上传的文档不出现在入口 B。 +- 没有入口上下文时,保留原有列表行为。 + +### Task 3:规则分组页按入口模块收口 + +**目标:** 规则分组页只展示当前入口模块下的一级分组和二级分组。 + +**Files:** +- Modify: `legal-platform-frontend/app/(audit)/rule-groups/RuleGroupsClient.tsx` +- Modify: `legal-platform-frontend/app/api/rule-groups/*` +- Modify: `fastapi_modules/fastapi_leaudit/controllers/evaluationPointGroupController.py` +- Modify: `fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py` + +- [x] 前端读取 `entryModuleId`。 +- [x] 分组列表请求带 `entry_module_id`。 +- [x] 后端列表用 `entry_module_id` 过滤一级分组。 +- [x] 二级分组根据父级一级分组自然收口。 +- [x] 新建一级分组默认带当前入口 `entry_module_id`。 +- [x] 新建二级分组必须带 `document_type_id`,且文档类型属于当前入口。 +- [x] 编辑二级分组时禁止移动到其他入口的一级分组下。 + +**验收:** +- 合同入口看不到公文入口的分组。 +- 公文入口看不到合同入口的分组。 +- 二级分组不能跨入口挂载。 + +### Task 4:规则绑定二级分组硬校验 + +**目标:** 规则只能绑定二级分组,不能绑定一级分组,也不能跨租户/跨入口污染。 + +**Files:** +- Modify: `fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py` +- Review: `fastapi_modules/fastapi_leaudit/services/impl/ruleServiceImpl.py` + +- [x] 保留新接口已有 `pid == 0` 拒绝绑定逻辑。 +- [x] 在绑定保存时再次校验 group 所属入口对当前用户可见。 +- [x] `_load_binding_map` 增加绑定级租户过滤:当前租户、`PUBLIC`、`PROVINCIAL` 可见,其它租户不可见。 +- [x] 旧规则绑定兼容接口只允许唯一可访问二级分组场景写入。 +- [x] 旧接口写入新绑定表时必须带 `tenant_code/scope_type`,保持第一阶段补丁。 + +**验收:** +- 一级分组绑定返回 400。 +- 普通租户看不到其它租户绑定。 +- 全局管理员能看到公共/省级范围绑定。 + +### Task 5:重复侧边栏处理 + +**目标:** 公文详情页不要同时出现平台 Sidebar 和公文工作区 Sidebar。 + +**Files:** +- Modify: `legal-platform-frontend/components/layout/Layout.tsx` +- Review: `legal-platform-frontend/components/govdoc-audit/GovdocAuditResultPage.tsx` + +- [x] 确认哪些公文页面内部有 `GovdocWorkspaceSidebar`。 +- [x] `Layout.tsx` 对公文详情/结果页隐藏平台 Sidebar。 +- [x] 公文列表、上传、概览仍保留平台 Sidebar。 +- [x] 不改公文工作区内部 UI,只处理外层布局冲突。 + +**验收:** +- `/govdoc/audits` 只有平台 Sidebar。 +- `/govdoc/[id]` 或详情页只有公文工作区 Sidebar。 +- 移动端布局不新增横向溢出。 + +### Task 6:前端权限和 UI 一致性复查 + +**目标:** features 只是入口功能编排,不能绕过 RBAC 页面/API 权限。 + +**Files:** +- Review: `legal-platform-frontend/components/layout/Sidebar.tsx` +- Review: `legal-platform-frontend/lib/auth/entry-module-menu.ts` +- Review: `legal-platform-frontend/lib/auth/user-routes.ts` + +- [x] 确认菜单先从 RBAC 路由结果生成,再按 features 过滤。 +- [x] 没有合同模板页面权限时,即使入口启用合同模板 feature,也不显示模板菜单。 +- [x] 没有规则分组权限时,即使入口启用 `rule_groups`,也不显示规则分组菜单。 +- [x] 所有新增提示沿用现有 CSS 变量和组件风格。 + +**验收:** +- 入口 features 不能“放大权限”,只能“收窄菜单”。 + +--- + +## 四、并行安排 + +建议用 subagent 并行: + +- Worker A:Task 1 普通文档上传链路。 +- Worker B:Task 2 普通文档列表过滤。 +- Worker C:Task 3/4 规则分组和绑定后端校验。 +- Worker D:Task 5 重复侧边栏处理。 +- 主线程:整合冲突、跑验证、更新计划文档。 + +注意:Worker A 和 Worker B 可能同时改 `lib/api/legacy/files/documents.ts`,需要一个人先做 API 类型,另一个只改页面调用,避免冲突。 + +--- + +## 五、验证命令 + +后端: + +```bash +python -m py_compile fastapi_modules/fastapi_leaudit/controllers/documentController.py fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py fastapi_modules/fastapi_leaudit/controllers/evaluationPointGroupController.py fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py fastapi_modules/fastapi_leaudit/services/impl/ruleServiceImpl.py +``` + +前端: + +```bash +cd legal-platform-frontend +npx tsc --noEmit --pretty false +npx eslint --no-ignore lib/auth/entry-module-context.ts lib/auth/entry-module-menu.ts components/layout/Sidebar.tsx components/layout/Layout.tsx +``` + +手工验收: + +- 创建一个名称不含“合同”的合同入口,启用合同模板功能,检查模板搜索/模板列表显示。 +- 创建一个名称不含“公文”的公文入口,启用公文功能,检查公文列表/上传显示。 +- 从入口 A 上传普通文档,入口 B 不应看到。 +- 从入口 A 进入规则分组,只看到入口 A 的一级/二级分组。 +- 尝试把规则绑到一级分组,应失败。 +- 公文详情页不出现双侧栏。 + +执行记录(2026-05-23): + +- 已跑后端目标编译:`python -m py_compile ...`,通过。 +- 已跑前端类型检查:`npx tsc --noEmit --pretty false`,通过。 +- 已跑目标 eslint:0 error,剩余 31 个 warning 为既有未使用变量 / any / hook dependency 类警告。 +- 已用 Playwright 真实账号 `000/admin06111` 做首页和侧边栏 smoke: + - 登录成功。 + - 点击入口后进入 `/documents/list?entryModuleId=...&documentTypeIds=...`。 + - 侧边栏顺序为:首页、文件上传、文档列表、规则管理。 + - 文档列表没有重复。 + - 上传文档按钮可见。 +- 已用 Playwright 真实后端链路跑优先项 `2/3/4/5`:`node /tmp/leaudit-playwright-priority-2-5-v3.js`,结果 `25 pass / 0 fail`。 + - 上传携带 `entryModuleId/groupId` 成功,文档详情返回正确 `typeId/groupId/tenantCode`。 + - 文档列表按入口 A/B 隔离通过。 + - 规则分组按入口 A/B 隔离通过。 + - 一级分组绑定规则集返回 400。 + - 二级分组绑定规则集返回 200。 + - 无入口模块创建/编辑/删除权限用户直接调接口返回 403。 +- 修复一次 Playwright 暴露的后端 500: + - 文件:`fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py` + - 方法:`_get_binding_row` + - 原因:SQL 文本没有使用 f-string,`{access_filter}` 原样发送到 PostgreSQL。 + - 修复:把查询文本改为 f-string,入口模块/租户可见性过滤正常注入。 +- 已跑后端规则绑定范围单测:`pytest -q tests/test_rule_group_binding_scope.py`,结果 `4 passed`。 +- 已重新跑前端类型检查:`npx tsc --noEmit --pretty false`,通过。 +- 已重新跑目标 eslint:0 error,剩余 6 个 warning,为既有 ``、未使用变量类提示。 +- 仍需继续做真实浏览器页面级验收: + - 入口模块管理页功能勾选保存和回显。 + - 上传页真实表单选择文档类型、二级分组后提交。 + - 规则分组页面真实 UI 下只展示当前入口范围。 + - 公文列表、公文上传、公文详情页侧边栏真实页面检查。 + - 多租户可见性需要用不同租户账号逐个验证。 + +下一步执行顺序: + +1. Playwright 页面级验收入口模块管理页:新建/编辑入口,检查 `menu_profile/features/tenants` 保存和回显。 +2. Playwright 页面级验收上传页:确认文件上传在 UI 上第二位,选择文档类型和二级分组后真实提交。 +3. Playwright 页面级验收规则分组页:入口 A/B 切换后页面树、绑定按钮、绑定弹窗范围正确。 +4. Playwright 页面级验收公文入口:名称不含“公文”但配置公文功能时,公文列表/上传可进入且标题不写死。 +5. 多租户账号验证:分别用云浮、揭阳、梅州或可用测试账号验证入口模块可见性和入口管理权限。 +6. 根据页面级验收结果修复前端展示问题,再回跑 `tsc`、目标 eslint、Playwright。 + +--- + +## 六、暂缓项 + +- 暂不删除旧 `leaudit_rule_type_bindings` 表。 +- 暂不重写评查运行引擎。 +- 暂不做同一入口模块按租户覆盖 features。 +- 暂不大改页面视觉,只做现有 UI 风格内的补充。 diff --git a/docs/superpowers/plans/2026-05-23-qichacha-migration.md b/docs/superpowers/plans/2026-05-23-qichacha-migration.md new file mode 100644 index 0000000..06656cb --- /dev/null +++ b/docs/superpowers/plans/2026-05-23-qichacha-migration.md @@ -0,0 +1,71 @@ +# Qichacha Migration Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Migrate the legacy Qichacha company verification module into the current LeAudit backend and keep the existing frontend company-info modal working. + +**Architecture:** Add a `[QICHACHA]` TOML settings section, a new `qcc_company_info` active-record model, DTO/VO objects, a service interface plus implementation, and a small HTTP client wrapper for Qichacha signing and API parsing. Controllers expose `/api/v2/qichacha/*` using unified `Result[...]`; the frontend wrapper unwraps both the new `Result` response and the old raw legacy shape. + +**Tech Stack:** FastAPI, SQLAlchemy async ORM, Pydantic v2, httpx, pytest, Next.js TypeScript. + +--- + +### Task 1: Configuration And Response Contract + +**Files:** +- Modify: `app.toml` +- Modify: `fastapi_admin/config/_settings.py` +- Modify: `fastapi_admin/config/__init__.py` +- Create: `tests/test_qichacha_config_client.py` + +- [ ] Write failing tests proving `QICHACHA_*` settings are exported and the client generates the expected MD5 token. +- [ ] Add `QichachaSettings` and `[QICHACHA]` placeholders. +- [ ] Implement `QichachaClient` with signing, retry-friendly GET calls, and response validation. +- [ ] Run `pytest tests/test_qichacha_config_client.py -q`. + +### Task 2: Backend Domain And Cache Service + +**Files:** +- Create: `fastapi_common/fastapi_common_web/exception/QichachaException.py` +- Create: `fastapi_modules/fastapi_leaudit/domian/Dto/qichachaDto.py` +- Create: `fastapi_modules/fastapi_leaudit/domian/vo/qichachaVo.py` +- Create: `fastapi_modules/fastapi_leaudit/models/qichachaCompanyInfo.py` +- Create: `fastapi_modules/fastapi_leaudit/services/qichachaService.py` +- Create: `fastapi_modules/fastapi_leaudit/services/impl/qichachaServiceImpl.py` +- Modify: `fastapi_modules/fastapi_leaudit/models/__init__.py` +- Modify: `fastapi_modules/fastapi_leaudit/services/__init__.py` +- Create: `tests/test_qichacha_service.py` + +- [ ] Write failing tests for cache hit, stale refresh, force refresh, and dishonesty count mapping. +- [ ] Implement model classmethods for keyword lookup and upsert. +- [ ] Implement service interface and implementation returning VO objects. +- [ ] Run `pytest tests/test_qichacha_service.py -q`. + +### Task 3: API Routes, Permissions, SQL + +**Files:** +- Create: `fastapi_modules/fastapi_leaudit/controllers/qichachaController.py` +- Modify: `fastapi_modules/fastapi_leaudit/services/impl/rbacAdminServiceImpl.py` +- Modify: `scripts/创建sql/user_rbac_seed.sql` +- Create: `scripts/创建sql/schema_qichacha_company_info.sql` + +- [ ] Add authenticated routes for `/v2/qichacha/company`, `/enterprise`, `/dishonesty`, `/batch`, `/status`. +- [ ] Add action permissions `qichacha:company:query` and `qichacha:status:read` without creating a route/menu entry. +- [ ] Add SQL schema for `qcc_company_info`. +- [ ] Run backend import and focused pytest checks. + +### Task 4: Frontend Compatibility + +**Files:** +- Modify: `legal-platform-frontend/lib/api/legacy/corporate-information/qichacha.ts` + +- [ ] Add a local unwrap helper that returns `response.data.data` for new `Result` responses and raw `response.data` for legacy responses. +- [ ] Use it in `queryCompanyInfo`, `queryEnterpriseInfo`, and `queryDishonestyInfo`. +- [ ] Run TypeScript or lint check for the changed file scope. + +### Task 5: Verification + +- [ ] Run focused backend tests: `pytest tests/test_qichacha_config_client.py tests/test_qichacha_service.py -q`. +- [ ] Run Python compile/import checks for new backend files. +- [ ] Run frontend lint/type verification if available. +- [ ] Review `git diff` to ensure no unrelated dirty files were reverted. diff --git a/docs/规则编辑/backups/frontend-rule-editor-smoke-cleanup-20260521-225643.json b/docs/规则编辑/backups/frontend-rule-editor-smoke-cleanup-20260521-225643.json new file mode 100644 index 0000000..2e8683c --- /dev/null +++ b/docs/规则编辑/backups/frontend-rule-editor-smoke-cleanup-20260521-225643.json @@ -0,0 +1,128 @@ +{ + "rule_set": { + "id": 127, + "rule_type": "contract.entrust", + "rule_name": "通用委托合同", + "domain_type": "contract", + "description": "依据《中华人民共和国民法典》合同编·通则(第467、470、490条)及委托合同章(第919-936条)。", + "entry_module": null, + "current_version_id": 655, + "status": "active", + "is_builtin": false, + "owner_user_id": null, + "created_at": "2026-05-21 09:46:17.228475+00:00", + "updated_at": "2026-05-21 14:54:33.571988+00:00", + "deleted_at": null, + "region": "MZ", + "tenant_code": "MZ", + "scope_type": "TENANT", + "source_rule_set_id": 22, + "tenant_name_snapshot": "梅州" + }, + "versions": [ + { + "id": 509, + "rule_set_id": 127, + "version_no": "2.0", + "version_seq": 31, + "status": "deprecated", + "source_type": "oss_yaml", + "dsl_format": "yaml", + "oss_url": "rules/contract.entrust/2.0/rules.yaml", + "file_sha256": "5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17", + "file_size": 38821, + "local_cache_path": null, + "metadata_type_id": "contract.entrust", + "metadata_name": "通用委托合同", + "metadata_version": "2.0", + "change_note": "从 leaudit-oss-yaml-files 全量导入", + "editor_user_id": null, + "publisher_user_id": null, + "published_at": "2026-05-21 14:19:42.573203+00:00", + "created_at": "2026-05-21 14:19:42.573203+00:00", + "updated_at": "2026-05-21 14:54:33.571988+00:00", + "deleted_at": null, + "tenant_code_snapshot": "MZ", + "scope_type_snapshot": "TENANT", + "source_version_id": null + }, + { + "id": 653, + "rule_set_id": 127, + "version_no": "v32", + "version_seq": 32, + "status": "deprecated", + "source_type": "oss_yaml", + "dsl_format": "yaml", + "oss_url": "rules/contract.entrust/v32/rules.yaml", + "file_sha256": "a1a2bd60b6e2508791a08d68f395d19907c5abd886994b39f5cbd50dea430425", + "file_size": 43686, + "local_cache_path": null, + "metadata_type_id": "contract.entrust", + "metadata_name": "通用委托合同", + "metadata_version": "", + "change_note": "保存 通用委托合同/合同/通用委托合同 规则配置", + "editor_user_id": 5, + "publisher_user_id": 5, + "published_at": "2026-05-21 14:44:19.989164+00:00", + "created_at": "2026-05-21 14:44:15.414578+00:00", + "updated_at": "2026-05-21 14:54:33.571988+00:00", + "deleted_at": null, + "tenant_code_snapshot": "MZ", + "scope_type_snapshot": "PROVINCIAL", + "source_version_id": null + }, + { + "id": 654, + "rule_set_id": 127, + "version_no": "v33", + "version_seq": 33, + "status": "deprecated", + "source_type": "oss_yaml", + "dsl_format": "yaml", + "oss_url": "rules/contract.entrust/v33/rules.yaml", + "file_sha256": "e21a2a618cdab45b447f8ff5e6fdfbf92b2f448c30df90bae579015da164e8e6", + "file_size": 43965, + "local_cache_path": null, + "metadata_type_id": "contract.entrust", + "metadata_name": "通用委托合同", + "metadata_version": "", + "change_note": "保存 通用委托合同/合同/通用委托合同 规则配置", + "editor_user_id": 5, + "publisher_user_id": 5, + "published_at": "2026-05-21 14:53:04.545640+00:00", + "created_at": "2026-05-21 14:52:59.952306+00:00", + "updated_at": "2026-05-21 14:54:33.571988+00:00", + "deleted_at": null, + "tenant_code_snapshot": "MZ", + "scope_type_snapshot": "PROVINCIAL", + "source_version_id": null + }, + { + "id": 655, + "rule_set_id": 127, + "version_no": "v34", + "version_seq": 34, + "status": "published", + "source_type": "oss_yaml", + "dsl_format": "yaml", + "oss_url": "rules/contract.entrust/v34/rules.yaml", + "file_sha256": "133d936d52666857c5b043b5910d0dcc04b1db2da2ac2e0ee03bb905d49f1c04", + "file_size": 44276, + "local_cache_path": null, + "metadata_type_id": "contract.entrust", + "metadata_name": "通用委托合同", + "metadata_version": "", + "change_note": "保存 通用委托合同/合同/通用委托合同 规则配置", + "editor_user_id": 5, + "publisher_user_id": 5, + "published_at": "2026-05-21 14:54:33.571988+00:00", + "created_at": "2026-05-21 14:54:30.387264+00:00", + "updated_at": "2026-05-21 14:54:33.571988+00:00", + "deleted_at": null, + "tenant_code_snapshot": "MZ", + "scope_type_snapshot": "PROVINCIAL", + "source_version_id": null + } + ] +} \ No newline at end of file diff --git a/docs/规则编辑/backups/rule-domain-before-reset-20260521-221930.sql b/docs/规则编辑/backups/rule-domain-before-reset-20260521-221930.sql new file mode 100644 index 0000000..409c3f7 --- /dev/null +++ b/docs/规则编辑/backups/rule-domain-before-reset-20260521-221930.sql @@ -0,0 +1,527 @@ +-- Rule domain backup before reset +-- generated_at: 2026-05-21T22:19:30 +-- rule_sets: 147 +-- rule_versions: 371 + +-- This file is an audit snapshot, not an automatic restore script. +-- Use the rows below to inspect pre-reset IDs, current_version_id, oss_url and sha. + +-- rule_set id=21 tenant_code=PUBLIC rule_type=contract.construction.general current_version_id=311 status=active deleted_at=None +-- rule_set id=22 tenant_code=PUBLIC rule_type=contract.entrust current_version_id=320 status=active deleted_at=None +-- rule_set id=23 tenant_code=PUBLIC rule_type=contract.evaluation.delegation current_version_id=321 status=active deleted_at=None +-- rule_set id=24 tenant_code=PUBLIC rule_type=contract.gift.charity current_version_id=322 status=active deleted_at=None +-- rule_set id=25 tenant_code=PUBLIC rule_type=contract.gift.general current_version_id=323 status=active deleted_at=None +-- rule_set id=26 tenant_code=PUBLIC rule_type=contract.lease current_version_id=324 status=active deleted_at=None +-- rule_set id=27 tenant_code=PUBLIC rule_type=contract.loan.general current_version_id=325 status=active deleted_at=None +-- rule_set id=28 tenant_code=PUBLIC rule_type=contract.purchase.general current_version_id=326 status=active deleted_at=None +-- rule_set id=29 tenant_code=PUBLIC rule_type=contract.sale current_version_id=327 status=active deleted_at=None +-- rule_set id=30 tenant_code=PUBLIC rule_type=contract.tech current_version_id=328 status=active deleted_at=None +-- rule_set id=31 tenant_code=PUBLIC rule_type=行政卷宗.行政处罚 current_version_id=330 status=active deleted_at=None +-- rule_set id=32 tenant_code=PUBLIC rule_type=行政卷宗.行政许可.停业 current_version_id=331 status=active deleted_at=None +-- rule_set id=33 tenant_code=PUBLIC rule_type=行政卷宗.行政许可.变更 current_version_id=332 status=active deleted_at=None +-- rule_set id=34 tenant_code=PUBLIC rule_type=行政卷宗.行政许可.延续 current_version_id=333 status=active deleted_at=None +-- rule_set id=35 tenant_code=PUBLIC rule_type=行政卷宗.行政许可.恢复营业 current_version_id=334 status=active deleted_at=None +-- rule_set id=36 tenant_code=PUBLIC rule_type=行政卷宗.行政许可.收回 current_version_id=335 status=active deleted_at=None +-- rule_set id=37 tenant_code=PUBLIC rule_type=行政卷宗.行政许可.新办 current_version_id=336 status=active deleted_at=None +-- rule_set id=38 tenant_code=PUBLIC rule_type=行政卷宗.行政许可.歇业 current_version_id=337 status=active deleted_at=None +-- rule_set id=39 tenant_code=PUBLIC rule_type=行政卷宗.行政许可.注销 current_version_id=338 status=active deleted_at=None +-- rule_set id=40 tenant_code=PUBLIC rule_type=行政卷宗.行政许可.补办 current_version_id=339 status=active deleted_at=None +-- rule_set id=41 tenant_code=PUBLIC rule_type=govdoc.general current_version_id=329 status=active deleted_at=None +-- rule_set id=44 tenant_code=JY rule_type=contract.entrust current_version_id=258 status=active deleted_at=None +-- rule_set id=107 tenant_code=MZ rule_type=govdoc.general current_version_id=298 status=active deleted_at=None +-- rule_set id=108 tenant_code=MZ rule_type=contract.gift.charity current_version_id=291 status=active deleted_at=None +-- rule_set id=109 tenant_code=MZ rule_type=contract.gift.general current_version_id=292 status=active deleted_at=None +-- rule_set id=110 tenant_code=MZ rule_type=contract.lease current_version_id=293 status=active deleted_at=None +-- rule_set id=111 tenant_code=MZ rule_type=contract.loan.general current_version_id=294 status=active deleted_at=None +-- rule_set id=112 tenant_code=MZ rule_type=contract.purchase.general current_version_id=295 status=active deleted_at=None +-- rule_set id=113 tenant_code=MZ rule_type=contract.sale current_version_id=296 status=active deleted_at=None +-- rule_set id=114 tenant_code=MZ rule_type=行政卷宗.行政处罚 current_version_id=299 status=active deleted_at=None +-- rule_set id=115 tenant_code=MZ rule_type=行政卷宗.行政许可.停业 current_version_id=300 status=active deleted_at=None +-- rule_set id=116 tenant_code=MZ rule_type=行政卷宗.行政许可.变更 current_version_id=301 status=active deleted_at=None +-- rule_set id=117 tenant_code=MZ rule_type=行政卷宗.行政许可.延续 current_version_id=302 status=active deleted_at=None +-- rule_set id=118 tenant_code=MZ rule_type=行政卷宗.行政许可.恢复营业 current_version_id=303 status=active deleted_at=None +-- rule_set id=119 tenant_code=MZ rule_type=行政卷宗.行政许可.收回 current_version_id=304 status=active deleted_at=None +-- rule_set id=120 tenant_code=MZ rule_type=行政卷宗.行政许可.新办 current_version_id=305 status=active deleted_at=None +-- rule_set id=121 tenant_code=MZ rule_type=行政卷宗.行政许可.歇业 current_version_id=306 status=active deleted_at=None +-- rule_set id=122 tenant_code=MZ rule_type=行政卷宗.行政许可.注销 current_version_id=307 status=active deleted_at=None +-- rule_set id=123 tenant_code=MZ rule_type=行政卷宗.行政许可.补办 current_version_id=308 status=active deleted_at=None +-- rule_set id=124 tenant_code=MZ rule_type=contract.tech current_version_id=297 status=active deleted_at=None +-- rule_set id=125 tenant_code=MZ rule_type=contract.evaluation.delegation current_version_id=290 status=active deleted_at=None +-- rule_set id=126 tenant_code=MZ rule_type=contract.construction.general current_version_id=280 status=active deleted_at=None +-- rule_set id=127 tenant_code=MZ rule_type=contract.entrust current_version_id=289 status=active deleted_at=None +-- rule_set id=128 tenant_code=YF rule_type=govdoc.general current_version_id=360 status=active deleted_at=None +-- rule_set id=129 tenant_code=YF rule_type=contract.gift.charity current_version_id=353 status=active deleted_at=None +-- rule_set id=130 tenant_code=YF rule_type=contract.gift.general current_version_id=354 status=active deleted_at=None +-- rule_set id=131 tenant_code=YF rule_type=contract.lease current_version_id=355 status=active deleted_at=None +-- rule_set id=132 tenant_code=YF rule_type=contract.loan.general current_version_id=356 status=active deleted_at=None +-- rule_set id=133 tenant_code=YF rule_type=contract.purchase.general current_version_id=357 status=active deleted_at=None +-- rule_set id=134 tenant_code=YF rule_type=contract.sale current_version_id=358 status=active deleted_at=None +-- rule_set id=135 tenant_code=YF rule_type=行政卷宗.行政处罚 current_version_id=361 status=active deleted_at=None +-- rule_set id=136 tenant_code=YF rule_type=行政卷宗.行政许可.停业 current_version_id=362 status=active deleted_at=None +-- rule_set id=137 tenant_code=YF rule_type=行政卷宗.行政许可.变更 current_version_id=363 status=active deleted_at=None +-- rule_set id=138 tenant_code=YF rule_type=行政卷宗.行政许可.延续 current_version_id=364 status=active deleted_at=None +-- rule_set id=139 tenant_code=YF rule_type=行政卷宗.行政许可.恢复营业 current_version_id=365 status=active deleted_at=None +-- rule_set id=140 tenant_code=YF rule_type=行政卷宗.行政许可.收回 current_version_id=366 status=active deleted_at=None +-- rule_set id=141 tenant_code=YF rule_type=行政卷宗.行政许可.新办 current_version_id=367 status=active deleted_at=None +-- rule_set id=142 tenant_code=YF rule_type=行政卷宗.行政许可.歇业 current_version_id=368 status=active deleted_at=None +-- rule_set id=143 tenant_code=YF rule_type=行政卷宗.行政许可.注销 current_version_id=369 status=active deleted_at=None +-- rule_set id=144 tenant_code=YF rule_type=行政卷宗.行政许可.补办 current_version_id=370 status=active deleted_at=None +-- rule_set id=145 tenant_code=YF rule_type=contract.tech current_version_id=359 status=active deleted_at=None +-- rule_set id=146 tenant_code=YF rule_type=contract.evaluation.delegation current_version_id=352 status=active deleted_at=None +-- rule_set id=147 tenant_code=YF rule_type=contract.construction.general current_version_id=342 status=active deleted_at=None +-- rule_set id=148 tenant_code=YF rule_type=contract.entrust current_version_id=351 status=active deleted_at=None +-- rule_set id=149 tenant_code=JY rule_type=govdoc.general current_version_id=267 status=active deleted_at=None +-- rule_set id=150 tenant_code=JY rule_type=contract.gift.charity current_version_id=260 status=active deleted_at=None +-- rule_set id=151 tenant_code=JY rule_type=contract.gift.general current_version_id=261 status=active deleted_at=None +-- rule_set id=152 tenant_code=JY rule_type=contract.lease current_version_id=262 status=active deleted_at=None +-- rule_set id=153 tenant_code=JY rule_type=contract.loan.general current_version_id=263 status=active deleted_at=None +-- rule_set id=154 tenant_code=JY rule_type=contract.purchase.general current_version_id=264 status=active deleted_at=None +-- rule_set id=155 tenant_code=JY rule_type=contract.sale current_version_id=265 status=active deleted_at=None +-- rule_set id=156 tenant_code=JY rule_type=行政卷宗.行政处罚 current_version_id=268 status=active deleted_at=None +-- rule_set id=157 tenant_code=JY rule_type=行政卷宗.行政许可.停业 current_version_id=269 status=active deleted_at=None +-- rule_set id=158 tenant_code=JY rule_type=行政卷宗.行政许可.变更 current_version_id=270 status=active deleted_at=None +-- rule_set id=159 tenant_code=JY rule_type=行政卷宗.行政许可.延续 current_version_id=271 status=active deleted_at=None +-- rule_set id=160 tenant_code=JY rule_type=行政卷宗.行政许可.恢复营业 current_version_id=272 status=active deleted_at=None +-- rule_set id=161 tenant_code=JY rule_type=行政卷宗.行政许可.收回 current_version_id=273 status=active deleted_at=None +-- rule_set id=162 tenant_code=JY rule_type=行政卷宗.行政许可.新办 current_version_id=274 status=active deleted_at=None +-- rule_set id=163 tenant_code=JY rule_type=行政卷宗.行政许可.歇业 current_version_id=275 status=active deleted_at=None +-- rule_set id=164 tenant_code=JY rule_type=行政卷宗.行政许可.注销 current_version_id=276 status=active deleted_at=None +-- rule_set id=165 tenant_code=JY rule_type=行政卷宗.行政许可.补办 current_version_id=277 status=active deleted_at=None +-- rule_set id=166 tenant_code=JY rule_type=contract.tech current_version_id=266 status=active deleted_at=None +-- rule_set id=167 tenant_code=JY rule_type=contract.evaluation.delegation current_version_id=259 status=active deleted_at=None +-- rule_set id=168 tenant_code=JY rule_type=contract.construction.general current_version_id=249 status=active deleted_at=None +-- rule_set id=169 tenant_code=CZ rule_type=govdoc.general current_version_id=236 status=active deleted_at=None +-- rule_set id=170 tenant_code=CZ rule_type=contract.gift.charity current_version_id=229 status=active deleted_at=None +-- rule_set id=171 tenant_code=CZ rule_type=contract.gift.general current_version_id=230 status=active deleted_at=None +-- rule_set id=172 tenant_code=CZ rule_type=contract.lease current_version_id=231 status=active deleted_at=None +-- rule_set id=173 tenant_code=CZ rule_type=contract.loan.general current_version_id=232 status=active deleted_at=None +-- rule_set id=174 tenant_code=CZ rule_type=contract.purchase.general current_version_id=233 status=active deleted_at=None +-- rule_set id=175 tenant_code=CZ rule_type=contract.sale current_version_id=234 status=active deleted_at=None +-- rule_set id=176 tenant_code=CZ rule_type=行政卷宗.行政处罚 current_version_id=237 status=active deleted_at=None +-- rule_set id=177 tenant_code=CZ rule_type=行政卷宗.行政许可.停业 current_version_id=238 status=active deleted_at=None +-- rule_set id=178 tenant_code=CZ rule_type=行政卷宗.行政许可.变更 current_version_id=239 status=active deleted_at=None +-- rule_set id=179 tenant_code=CZ rule_type=行政卷宗.行政许可.延续 current_version_id=240 status=active deleted_at=None +-- rule_set id=180 tenant_code=CZ rule_type=行政卷宗.行政许可.恢复营业 current_version_id=241 status=active deleted_at=None +-- rule_set id=181 tenant_code=CZ rule_type=行政卷宗.行政许可.收回 current_version_id=242 status=active deleted_at=None +-- rule_set id=182 tenant_code=CZ rule_type=行政卷宗.行政许可.新办 current_version_id=243 status=active deleted_at=None +-- rule_set id=183 tenant_code=CZ rule_type=行政卷宗.行政许可.歇业 current_version_id=244 status=active deleted_at=None +-- rule_set id=184 tenant_code=CZ rule_type=行政卷宗.行政许可.注销 current_version_id=245 status=active deleted_at=None +-- rule_set id=185 tenant_code=CZ rule_type=行政卷宗.行政许可.补办 current_version_id=246 status=active deleted_at=None +-- rule_set id=186 tenant_code=CZ rule_type=contract.tech current_version_id=235 status=active deleted_at=None +-- rule_set id=187 tenant_code=CZ rule_type=contract.evaluation.delegation current_version_id=228 status=active deleted_at=None +-- rule_set id=188 tenant_code=CZ rule_type=contract.construction.general current_version_id=218 status=active deleted_at=None +-- rule_set id=189 tenant_code=CZ rule_type=contract.entrust current_version_id=227 status=active deleted_at=None +-- rule_set id=190 tenant_code=PTA01 rule_type=contract.gift.charity current_version_id=371 status=active deleted_at=None +-- rule_set id=191 tenant_code=PTA01 rule_type=govdoc.general current_version_id=372 status=active deleted_at=None +-- rule_set id=192 tenant_code=PTA01 rule_type=contract.construction.general current_version_id=375 status=active deleted_at=None +-- rule_set id=193 tenant_code=PTA01 rule_type=contract.entrust current_version_id=384 status=active deleted_at=None +-- rule_set id=194 tenant_code=PTA01 rule_type=contract.evaluation.delegation current_version_id=385 status=active deleted_at=None +-- rule_set id=195 tenant_code=PTA01 rule_type=contract.gift.general current_version_id=386 status=active deleted_at=None +-- rule_set id=196 tenant_code=PTA01 rule_type=contract.lease current_version_id=387 status=active deleted_at=None +-- rule_set id=197 tenant_code=PTA01 rule_type=contract.loan.general current_version_id=388 status=active deleted_at=None +-- rule_set id=198 tenant_code=PTA01 rule_type=contract.purchase.general current_version_id=389 status=active deleted_at=None +-- rule_set id=199 tenant_code=PTA01 rule_type=contract.sale current_version_id=390 status=active deleted_at=None +-- rule_set id=200 tenant_code=PTA01 rule_type=contract.tech current_version_id=391 status=active deleted_at=None +-- rule_set id=201 tenant_code=PTA01 rule_type=行政卷宗.行政处罚 current_version_id=392 status=active deleted_at=None +-- rule_set id=202 tenant_code=PTA01 rule_type=行政卷宗.行政许可.停业 current_version_id=393 status=active deleted_at=None +-- rule_set id=203 tenant_code=PTA01 rule_type=行政卷宗.行政许可.变更 current_version_id=394 status=active deleted_at=None +-- rule_set id=204 tenant_code=PTA01 rule_type=行政卷宗.行政许可.延续 current_version_id=395 status=active deleted_at=None +-- rule_set id=205 tenant_code=PTA01 rule_type=行政卷宗.行政许可.恢复营业 current_version_id=396 status=active deleted_at=None +-- rule_set id=206 tenant_code=PTA01 rule_type=行政卷宗.行政许可.收回 current_version_id=397 status=active deleted_at=None +-- rule_set id=207 tenant_code=PTA01 rule_type=行政卷宗.行政许可.新办 current_version_id=398 status=active deleted_at=None +-- rule_set id=208 tenant_code=PTA01 rule_type=行政卷宗.行政许可.歇业 current_version_id=399 status=active deleted_at=None +-- rule_set id=209 tenant_code=PTA01 rule_type=行政卷宗.行政许可.注销 current_version_id=400 status=active deleted_at=None +-- rule_set id=210 tenant_code=PTA01 rule_type=行政卷宗.行政许可.补办 current_version_id=401 status=active deleted_at=None +-- rule_set id=211 tenant_code=PTB01 rule_type=contract.gift.charity current_version_id=402 status=active deleted_at=None +-- rule_set id=212 tenant_code=PTB01 rule_type=govdoc.general current_version_id=403 status=active deleted_at=None +-- rule_set id=213 tenant_code=PTB01 rule_type=contract.construction.general current_version_id=406 status=active deleted_at=None +-- rule_set id=214 tenant_code=PTB01 rule_type=contract.entrust current_version_id=415 status=active deleted_at=None +-- rule_set id=215 tenant_code=PTB01 rule_type=contract.evaluation.delegation current_version_id=416 status=active deleted_at=None +-- rule_set id=216 tenant_code=PTB01 rule_type=contract.gift.general current_version_id=417 status=active deleted_at=None +-- rule_set id=217 tenant_code=PTB01 rule_type=contract.lease current_version_id=418 status=active deleted_at=None +-- rule_set id=218 tenant_code=PTB01 rule_type=contract.loan.general current_version_id=419 status=active deleted_at=None +-- rule_set id=219 tenant_code=PTB01 rule_type=contract.purchase.general current_version_id=420 status=active deleted_at=None +-- rule_set id=220 tenant_code=PTB01 rule_type=contract.sale current_version_id=421 status=active deleted_at=None +-- rule_set id=221 tenant_code=PTB01 rule_type=contract.tech current_version_id=422 status=active deleted_at=None +-- rule_set id=222 tenant_code=PTB01 rule_type=行政卷宗.行政处罚 current_version_id=423 status=active deleted_at=None +-- rule_set id=223 tenant_code=PTB01 rule_type=行政卷宗.行政许可.停业 current_version_id=424 status=active deleted_at=None +-- rule_set id=224 tenant_code=PTB01 rule_type=行政卷宗.行政许可.变更 current_version_id=425 status=active deleted_at=None +-- rule_set id=225 tenant_code=PTB01 rule_type=行政卷宗.行政许可.延续 current_version_id=426 status=active deleted_at=None +-- rule_set id=226 tenant_code=PTB01 rule_type=行政卷宗.行政许可.恢复营业 current_version_id=427 status=active deleted_at=None +-- rule_set id=227 tenant_code=PTB01 rule_type=行政卷宗.行政许可.收回 current_version_id=428 status=active deleted_at=None +-- rule_set id=228 tenant_code=PTB01 rule_type=行政卷宗.行政许可.新办 current_version_id=429 status=active deleted_at=None +-- rule_set id=229 tenant_code=PTB01 rule_type=行政卷宗.行政许可.歇业 current_version_id=430 status=active deleted_at=None +-- rule_set id=230 tenant_code=PTB01 rule_type=行政卷宗.行政许可.注销 current_version_id=431 status=active deleted_at=None +-- rule_set id=231 tenant_code=PTB01 rule_type=行政卷宗.行政许可.补办 current_version_id=432 status=active deleted_at=None + +-- rule_version id=1 rule_set_id=21 version_no=1.2 status=rollback oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=2 rule_set_id=22 version_no=2.0 status=rollback oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=3 rule_set_id=23 version_no=0.1 status=deprecated oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=7d59151c9a41903eca748c0f7bb283ddffad699a68c963af655a84e8fe621745 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=4 rule_set_id=24 version_no=1.0 status=deprecated oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=5a7a89f1e88e34c6afa9405010b1aaef517f56704cd264227d652b794e4a0acd deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=5 rule_set_id=25 version_no=1.0 status=deprecated oss_url=rules/contract.gift.general/1.0/rules.yaml sha=efeca05a6c0a0a1e1d113c781d38f6cd761d18513eb0c2274e9255e50955940e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=6 rule_set_id=26 version_no=2.0 status=deprecated oss_url=rules/contract.lease/2.0/rules.yaml sha=536265d6490c87bd7dfb66fdb0428a8164895bd368008073e74c788d2d3f1564 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=7 rule_set_id=27 version_no=1.0 status=deprecated oss_url=rules/contract.loan.general/1.0/rules.yaml sha=134cc339b7edfdc061c98becefd9e5f904d25f6e19f5a27664c46610650e07d6 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=8 rule_set_id=28 version_no=1.0 status=deprecated oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=d7d9e3b19e83716f067021fc4084a01cc0b1cc0df7f4a851faf8a0a3d33040ca deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=9 rule_set_id=29 version_no=2.1 status=deprecated oss_url=rules/contract.sale/2.1/rules.yaml sha=e246bf9554003b078c6cccfde2ac9deecad1fbe083c536edf21735f613aeae7d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=10 rule_set_id=30 version_no=1.0 status=deprecated oss_url=rules/contract.tech/1.0/rules.yaml sha=95aa3a0be32b44d5594d5ec101d6568480c36a71a1aeec31951680d8aa14e05f deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=11 rule_set_id=31 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=e96c7925535ab0dee8e1cdfa0db5ab398c9951adcda8b680c713d66cd6fa5878 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=12 rule_set_id=32 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=13 rule_set_id=33 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=14 rule_set_id=34 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=15 rule_set_id=35 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=16 rule_set_id=36 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=17 rule_set_id=37 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=18 rule_set_id=38 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=19 rule_set_id=39 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=20 rule_set_id=40 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=21 rule_set_id=21 version_no=v2 status=deprecated oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=22 rule_set_id=22 version_no=v2 status=rollback oss_url=rules/contract.entrust/v2/rules.yaml sha=5948e85e7d965b0d70710b5fef6acde5eb2a633993f3ada971447bd51c512395 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=23 rule_set_id=22 version_no=v3 status=rollback oss_url=rules/contract.entrust/v3/rules.yaml sha=9d03f9917d0d401efcbb120abdc48a7e03783cac83364692bce32c2eece8a40e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=24 rule_set_id=22 version_no=v4 status=deprecated oss_url=rules/contract.entrust/v4/rules.yaml sha=d400d2801a42b94ad87b87bbad291b7f036acf0808a6dbc025336dedc181d21f deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=25 rule_set_id=22 version_no=v5 status=rollback oss_url=rules/contract.entrust/v5/rules.yaml sha=fa0e241874262f7229387da9e352073dc3decfe3b8048e559c35cad0ff4b2b49 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=26 rule_set_id=22 version_no=v6 status=deprecated oss_url=rules/contract.entrust/v6/rules.yaml sha=86fe184ffcc2a0ef8639f91632f0859519e96fb96f2f7d29b0f6e2343d03ab07 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=27 rule_set_id=22 version_no=v7 status=deprecated oss_url=rules/contract.entrust/v7/rules.yaml sha=ba305b20965a5ed794539fe23a3c0534c9149380fc3a67d977ddfe7f0ecbf9f8 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=28 rule_set_id=22 version_no=v8 status=deprecated oss_url=rules/contract.entrust/v8/rules.yaml sha=cf1938696438a2ae6a8b584207f99a4f167512383e456dd0041a2d9287db38b0 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=29 rule_set_id=22 version_no=v9 status=rollback oss_url=rules/contract.entrust/v9/rules.yaml sha=81d41fe8d27d394e4372a0668a2acb8975003975929d1c5f56c379c41cc23d33 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=30 rule_set_id=21 version_no=v3 status=rollback oss_url=rules/contract.construction.general/v3/rules.yaml sha=a5c21fac8c4352fcf75c6b17a8ec5c6ff71b54b453a3338b3f5a68b6f496f355 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=31 rule_set_id=41 version_no=0.1 status=deprecated oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=32 rule_set_id=44 version_no=v10 status=deprecated oss_url=rules/contract.entrust/v10/rules.yaml sha=92310866a9ee3030e3b4198623ab4dc45ac5dfdd0b2cb08d334622544eb6e599 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=97 rule_set_id=107 version_no=0.1 status=deprecated oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=98 rule_set_id=126 version_no=1.2 status=rollback oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=99 rule_set_id=126 version_no=v2 status=rollback oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=100 rule_set_id=126 version_no=v3 status=deprecated oss_url=rules/contract.construction.general/v3/rules.yaml sha=a5c21fac8c4352fcf75c6b17a8ec5c6ff71b54b453a3338b3f5a68b6f496f355 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=101 rule_set_id=108 version_no=1.0 status=deprecated oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=5a7a89f1e88e34c6afa9405010b1aaef517f56704cd264227d652b794e4a0acd deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=102 rule_set_id=109 version_no=1.0 status=deprecated oss_url=rules/contract.gift.general/1.0/rules.yaml sha=efeca05a6c0a0a1e1d113c781d38f6cd761d18513eb0c2274e9255e50955940e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=103 rule_set_id=110 version_no=2.0 status=deprecated oss_url=rules/contract.lease/2.0/rules.yaml sha=536265d6490c87bd7dfb66fdb0428a8164895bd368008073e74c788d2d3f1564 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=104 rule_set_id=111 version_no=1.0 status=deprecated oss_url=rules/contract.loan.general/1.0/rules.yaml sha=134cc339b7edfdc061c98becefd9e5f904d25f6e19f5a27664c46610650e07d6 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=105 rule_set_id=112 version_no=1.0 status=deprecated oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=d7d9e3b19e83716f067021fc4084a01cc0b1cc0df7f4a851faf8a0a3d33040ca deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=106 rule_set_id=113 version_no=2.1 status=deprecated oss_url=rules/contract.sale/2.1/rules.yaml sha=e246bf9554003b078c6cccfde2ac9deecad1fbe083c536edf21735f613aeae7d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=107 rule_set_id=114 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=e96c7925535ab0dee8e1cdfa0db5ab398c9951adcda8b680c713d66cd6fa5878 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=108 rule_set_id=115 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=109 rule_set_id=116 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=110 rule_set_id=117 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=111 rule_set_id=118 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=112 rule_set_id=119 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=113 rule_set_id=120 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=114 rule_set_id=121 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=115 rule_set_id=122 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=116 rule_set_id=123 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=117 rule_set_id=124 version_no=1.0 status=deprecated oss_url=rules/contract.tech/1.0/rules.yaml sha=95aa3a0be32b44d5594d5ec101d6568480c36a71a1aeec31951680d8aa14e05f deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=118 rule_set_id=125 version_no=0.1 status=deprecated oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=7d59151c9a41903eca748c0f7bb283ddffad699a68c963af655a84e8fe621745 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=119 rule_set_id=127 version_no=v5 status=rollback oss_url=rules/contract.entrust/v5/rules.yaml sha=fa0e241874262f7229387da9e352073dc3decfe3b8048e559c35cad0ff4b2b49 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=120 rule_set_id=127 version_no=2.0 status=rollback oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=121 rule_set_id=127 version_no=v8 status=deprecated oss_url=rules/contract.entrust/v8/rules.yaml sha=cf1938696438a2ae6a8b584207f99a4f167512383e456dd0041a2d9287db38b0 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=122 rule_set_id=127 version_no=v9 status=rollback oss_url=rules/contract.entrust/v9/rules.yaml sha=81d41fe8d27d394e4372a0668a2acb8975003975929d1c5f56c379c41cc23d33 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=123 rule_set_id=127 version_no=v6 status=deprecated oss_url=rules/contract.entrust/v6/rules.yaml sha=86fe184ffcc2a0ef8639f91632f0859519e96fb96f2f7d29b0f6e2343d03ab07 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=124 rule_set_id=127 version_no=v7 status=deprecated oss_url=rules/contract.entrust/v7/rules.yaml sha=ba305b20965a5ed794539fe23a3c0534c9149380fc3a67d977ddfe7f0ecbf9f8 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=125 rule_set_id=127 version_no=v4 status=deprecated oss_url=rules/contract.entrust/v4/rules.yaml sha=d400d2801a42b94ad87b87bbad291b7f036acf0808a6dbc025336dedc181d21f deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=126 rule_set_id=127 version_no=v3 status=rollback oss_url=rules/contract.entrust/v3/rules.yaml sha=9d03f9917d0d401efcbb120abdc48a7e03783cac83364692bce32c2eece8a40e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=127 rule_set_id=127 version_no=v2 status=rollback oss_url=rules/contract.entrust/v2/rules.yaml sha=5948e85e7d965b0d70710b5fef6acde5eb2a633993f3ada971447bd51c512395 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=128 rule_set_id=128 version_no=0.1 status=deprecated oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=129 rule_set_id=129 version_no=1.0 status=deprecated oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=5a7a89f1e88e34c6afa9405010b1aaef517f56704cd264227d652b794e4a0acd deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=130 rule_set_id=130 version_no=1.0 status=deprecated oss_url=rules/contract.gift.general/1.0/rules.yaml sha=efeca05a6c0a0a1e1d113c781d38f6cd761d18513eb0c2274e9255e50955940e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=131 rule_set_id=131 version_no=2.0 status=deprecated oss_url=rules/contract.lease/2.0/rules.yaml sha=536265d6490c87bd7dfb66fdb0428a8164895bd368008073e74c788d2d3f1564 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=132 rule_set_id=132 version_no=1.0 status=deprecated oss_url=rules/contract.loan.general/1.0/rules.yaml sha=134cc339b7edfdc061c98becefd9e5f904d25f6e19f5a27664c46610650e07d6 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=133 rule_set_id=133 version_no=1.0 status=deprecated oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=d7d9e3b19e83716f067021fc4084a01cc0b1cc0df7f4a851faf8a0a3d33040ca deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=134 rule_set_id=134 version_no=2.1 status=deprecated oss_url=rules/contract.sale/2.1/rules.yaml sha=e246bf9554003b078c6cccfde2ac9deecad1fbe083c536edf21735f613aeae7d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=135 rule_set_id=135 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=e96c7925535ab0dee8e1cdfa0db5ab398c9951adcda8b680c713d66cd6fa5878 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=136 rule_set_id=136 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=137 rule_set_id=137 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=138 rule_set_id=138 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=139 rule_set_id=139 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=140 rule_set_id=140 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=141 rule_set_id=141 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=142 rule_set_id=142 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=143 rule_set_id=143 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=144 rule_set_id=144 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=145 rule_set_id=145 version_no=1.0 status=deprecated oss_url=rules/contract.tech/1.0/rules.yaml sha=95aa3a0be32b44d5594d5ec101d6568480c36a71a1aeec31951680d8aa14e05f deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=146 rule_set_id=146 version_no=0.1 status=deprecated oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=7d59151c9a41903eca748c0f7bb283ddffad699a68c963af655a84e8fe621745 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=147 rule_set_id=147 version_no=1.2 status=rollback oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=148 rule_set_id=147 version_no=v2 status=deprecated oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=149 rule_set_id=147 version_no=v3 status=rollback oss_url=rules/contract.construction.general/v3/rules.yaml sha=a5c21fac8c4352fcf75c6b17a8ec5c6ff71b54b453a3338b3f5a68b6f496f355 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=150 rule_set_id=148 version_no=v5 status=rollback oss_url=rules/contract.entrust/v5/rules.yaml sha=fa0e241874262f7229387da9e352073dc3decfe3b8048e559c35cad0ff4b2b49 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=151 rule_set_id=148 version_no=2.0 status=rollback oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=152 rule_set_id=148 version_no=v8 status=deprecated oss_url=rules/contract.entrust/v8/rules.yaml sha=cf1938696438a2ae6a8b584207f99a4f167512383e456dd0041a2d9287db38b0 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=153 rule_set_id=148 version_no=v9 status=rollback oss_url=rules/contract.entrust/v9/rules.yaml sha=81d41fe8d27d394e4372a0668a2acb8975003975929d1c5f56c379c41cc23d33 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=154 rule_set_id=148 version_no=v6 status=deprecated oss_url=rules/contract.entrust/v6/rules.yaml sha=86fe184ffcc2a0ef8639f91632f0859519e96fb96f2f7d29b0f6e2343d03ab07 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=155 rule_set_id=148 version_no=v7 status=deprecated oss_url=rules/contract.entrust/v7/rules.yaml sha=ba305b20965a5ed794539fe23a3c0534c9149380fc3a67d977ddfe7f0ecbf9f8 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=156 rule_set_id=148 version_no=v4 status=deprecated oss_url=rules/contract.entrust/v4/rules.yaml sha=d400d2801a42b94ad87b87bbad291b7f036acf0808a6dbc025336dedc181d21f deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=157 rule_set_id=148 version_no=v3 status=rollback oss_url=rules/contract.entrust/v3/rules.yaml sha=9d03f9917d0d401efcbb120abdc48a7e03783cac83364692bce32c2eece8a40e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=158 rule_set_id=148 version_no=v2 status=rollback oss_url=rules/contract.entrust/v2/rules.yaml sha=5948e85e7d965b0d70710b5fef6acde5eb2a633993f3ada971447bd51c512395 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=159 rule_set_id=149 version_no=0.1 status=deprecated oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=160 rule_set_id=150 version_no=1.0 status=deprecated oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=5a7a89f1e88e34c6afa9405010b1aaef517f56704cd264227d652b794e4a0acd deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=161 rule_set_id=151 version_no=1.0 status=deprecated oss_url=rules/contract.gift.general/1.0/rules.yaml sha=efeca05a6c0a0a1e1d113c781d38f6cd761d18513eb0c2274e9255e50955940e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=162 rule_set_id=152 version_no=2.0 status=deprecated oss_url=rules/contract.lease/2.0/rules.yaml sha=536265d6490c87bd7dfb66fdb0428a8164895bd368008073e74c788d2d3f1564 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=163 rule_set_id=153 version_no=1.0 status=deprecated oss_url=rules/contract.loan.general/1.0/rules.yaml sha=134cc339b7edfdc061c98becefd9e5f904d25f6e19f5a27664c46610650e07d6 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=164 rule_set_id=154 version_no=1.0 status=deprecated oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=d7d9e3b19e83716f067021fc4084a01cc0b1cc0df7f4a851faf8a0a3d33040ca deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=165 rule_set_id=155 version_no=2.1 status=deprecated oss_url=rules/contract.sale/2.1/rules.yaml sha=e246bf9554003b078c6cccfde2ac9deecad1fbe083c536edf21735f613aeae7d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=166 rule_set_id=156 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=e96c7925535ab0dee8e1cdfa0db5ab398c9951adcda8b680c713d66cd6fa5878 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=167 rule_set_id=157 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=168 rule_set_id=158 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=169 rule_set_id=159 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=170 rule_set_id=160 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=171 rule_set_id=161 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=172 rule_set_id=162 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=173 rule_set_id=163 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=174 rule_set_id=164 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=175 rule_set_id=165 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=176 rule_set_id=166 version_no=1.0 status=deprecated oss_url=rules/contract.tech/1.0/rules.yaml sha=95aa3a0be32b44d5594d5ec101d6568480c36a71a1aeec31951680d8aa14e05f deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=177 rule_set_id=167 version_no=0.1 status=deprecated oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=7d59151c9a41903eca748c0f7bb283ddffad699a68c963af655a84e8fe621745 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=178 rule_set_id=168 version_no=1.2 status=deprecated oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=179 rule_set_id=168 version_no=v2 status=rollback oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=180 rule_set_id=168 version_no=v3 status=rollback oss_url=rules/contract.construction.general/v3/rules.yaml sha=a5c21fac8c4352fcf75c6b17a8ec5c6ff71b54b453a3338b3f5a68b6f496f355 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=181 rule_set_id=169 version_no=0.1 status=deprecated oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=182 rule_set_id=170 version_no=1.0 status=deprecated oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=5a7a89f1e88e34c6afa9405010b1aaef517f56704cd264227d652b794e4a0acd deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=183 rule_set_id=171 version_no=1.0 status=deprecated oss_url=rules/contract.gift.general/1.0/rules.yaml sha=efeca05a6c0a0a1e1d113c781d38f6cd761d18513eb0c2274e9255e50955940e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=184 rule_set_id=172 version_no=2.0 status=deprecated oss_url=rules/contract.lease/2.0/rules.yaml sha=536265d6490c87bd7dfb66fdb0428a8164895bd368008073e74c788d2d3f1564 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=185 rule_set_id=173 version_no=1.0 status=deprecated oss_url=rules/contract.loan.general/1.0/rules.yaml sha=134cc339b7edfdc061c98becefd9e5f904d25f6e19f5a27664c46610650e07d6 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=186 rule_set_id=174 version_no=1.0 status=deprecated oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=d7d9e3b19e83716f067021fc4084a01cc0b1cc0df7f4a851faf8a0a3d33040ca deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=187 rule_set_id=175 version_no=2.1 status=deprecated oss_url=rules/contract.sale/2.1/rules.yaml sha=e246bf9554003b078c6cccfde2ac9deecad1fbe083c536edf21735f613aeae7d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=188 rule_set_id=176 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=e96c7925535ab0dee8e1cdfa0db5ab398c9951adcda8b680c713d66cd6fa5878 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=189 rule_set_id=177 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=190 rule_set_id=178 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=191 rule_set_id=179 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=192 rule_set_id=180 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=193 rule_set_id=181 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=194 rule_set_id=182 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=195 rule_set_id=183 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=196 rule_set_id=184 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=197 rule_set_id=185 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=198 rule_set_id=186 version_no=1.0 status=deprecated oss_url=rules/contract.tech/1.0/rules.yaml sha=95aa3a0be32b44d5594d5ec101d6568480c36a71a1aeec31951680d8aa14e05f deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=199 rule_set_id=187 version_no=0.1 status=deprecated oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=7d59151c9a41903eca748c0f7bb283ddffad699a68c963af655a84e8fe621745 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=200 rule_set_id=188 version_no=1.2 status=rollback oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=201 rule_set_id=188 version_no=v2 status=deprecated oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=202 rule_set_id=188 version_no=v3 status=rollback oss_url=rules/contract.construction.general/v3/rules.yaml sha=a5c21fac8c4352fcf75c6b17a8ec5c6ff71b54b453a3338b3f5a68b6f496f355 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=203 rule_set_id=189 version_no=v5 status=rollback oss_url=rules/contract.entrust/v5/rules.yaml sha=fa0e241874262f7229387da9e352073dc3decfe3b8048e559c35cad0ff4b2b49 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=204 rule_set_id=189 version_no=2.0 status=rollback oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=205 rule_set_id=189 version_no=v8 status=deprecated oss_url=rules/contract.entrust/v8/rules.yaml sha=cf1938696438a2ae6a8b584207f99a4f167512383e456dd0041a2d9287db38b0 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=206 rule_set_id=189 version_no=v9 status=rollback oss_url=rules/contract.entrust/v9/rules.yaml sha=81d41fe8d27d394e4372a0668a2acb8975003975929d1c5f56c379c41cc23d33 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=207 rule_set_id=189 version_no=v6 status=deprecated oss_url=rules/contract.entrust/v6/rules.yaml sha=86fe184ffcc2a0ef8639f91632f0859519e96fb96f2f7d29b0f6e2343d03ab07 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=208 rule_set_id=189 version_no=v7 status=deprecated oss_url=rules/contract.entrust/v7/rules.yaml sha=ba305b20965a5ed794539fe23a3c0534c9149380fc3a67d977ddfe7f0ecbf9f8 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=209 rule_set_id=189 version_no=v4 status=deprecated oss_url=rules/contract.entrust/v4/rules.yaml sha=d400d2801a42b94ad87b87bbad291b7f036acf0808a6dbc025336dedc181d21f deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=210 rule_set_id=189 version_no=v3 status=rollback oss_url=rules/contract.entrust/v3/rules.yaml sha=9d03f9917d0d401efcbb120abdc48a7e03783cac83364692bce32c2eece8a40e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=211 rule_set_id=189 version_no=v2 status=rollback oss_url=rules/contract.entrust/v2/rules.yaml sha=5948e85e7d965b0d70710b5fef6acde5eb2a633993f3ada971447bd51c512395 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=212 rule_set_id=168 version_no=v4 status=rollback oss_url=rules/contract.construction.general/v4/rules.yaml sha=5b058dbb0dd14a31132110f04e649d26cc2d9f544ec2f8ffa56c4ada1917cd13 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=213 rule_set_id=22 version_no=v10 status=draft oss_url=rules/contract.entrust/v10/rules.yaml sha=0481fb582bf30fb137d9552cecdae22fd6ee525954b893ccaec386b186caabcc deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=214 rule_set_id=22 version_no=v11 status=draft oss_url=rules/contract.entrust/v11/rules.yaml sha=4fb270e13c9c3ab9d771807f9f333232a9cf78567163521606165b0889200c3e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=215 rule_set_id=127 version_no=pytest-vm-1779365463 status=rollback oss_url=rules/contract.entrust/pytest-vm-1779365463/rules.yaml sha=59763ad2915c90871dfa641264b74fe22f164b7590bd26a71f448dccba66d4e0 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=216 rule_set_id=188 version_no=v2 status=draft oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=None +-- rule_version id=217 rule_set_id=188 version_no=v3 status=draft oss_url=rules/contract.construction.general/v3/rules.yaml sha=b8b03368e540e86bec8cff3dd3b07dacc3bded4dba0f07f25d9454b713f249b8 deleted_at=None +-- rule_version id=218 rule_set_id=188 version_no=1.2 status=published oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=None +-- rule_version id=219 rule_set_id=189 version_no=v2 status=draft oss_url=rules/contract.entrust/v2/rules.yaml sha=2d5490d16057697e60099d6cec33d51aaac2f748fc0038ae154ae2dd67f2d5a2 deleted_at=None +-- rule_version id=220 rule_set_id=189 version_no=v3 status=draft oss_url=rules/contract.entrust/v3/rules.yaml sha=18025aa0035f08f571775c2236d23ce0b6c23b0c32dd7f3ece8f97ffe81b4944 deleted_at=None +-- rule_version id=221 rule_set_id=189 version_no=v4 status=draft oss_url=rules/contract.entrust/v4/rules.yaml sha=212c1a3724b14dec4074c4e4ea62485c0b07ac5d772bce8e9a6108efe312ec05 deleted_at=None +-- rule_version id=222 rule_set_id=189 version_no=v5 status=draft oss_url=rules/contract.entrust/v5/rules.yaml sha=5424c16a3a35eb7c44131904a99e97203b41e02148b8eaba1df9782bc4309cfc deleted_at=None +-- rule_version id=223 rule_set_id=189 version_no=v6 status=draft oss_url=rules/contract.entrust/v6/rules.yaml sha=0ee30fc1e3fdaab53f47c4a8cc3479b89fabf1be821ee9b65cea9ea223a40d35 deleted_at=None +-- rule_version id=224 rule_set_id=189 version_no=v7 status=draft oss_url=rules/contract.entrust/v7/rules.yaml sha=75c817acc45e7661aab5171a987ffc9e602e79353488c89bc12fcb2f749791df deleted_at=None +-- rule_version id=225 rule_set_id=189 version_no=v8 status=draft oss_url=rules/contract.entrust/v8/rules.yaml sha=cae719d588766213e251b141d4b910388046883e135b173dae2c71888ebe4a0f deleted_at=None +-- rule_version id=226 rule_set_id=189 version_no=v9 status=draft oss_url=rules/contract.entrust/v9/rules.yaml sha=11f41652de07617382eaa68b401cdaa2f7ff2a46ac61a903e8b44cf240768784 deleted_at=None +-- rule_version id=227 rule_set_id=189 version_no=2.0 status=published oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=None +-- rule_version id=228 rule_set_id=187 version_no=0.1 status=published oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=f2159624c569d6f18f37afb8bf6fa7ea099cc2e019cbf8b3bf3a2ce25d54a727 deleted_at=None +-- rule_version id=229 rule_set_id=170 version_no=1.0 status=published oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=4f4c2b64cc84cf791be770b785986cc67f4ef864174f95d9ba7e4a84fdadc8cc deleted_at=None +-- rule_version id=230 rule_set_id=171 version_no=1.0 status=published oss_url=rules/contract.gift.general/1.0/rules.yaml sha=dd268a9c38d6143523f5afeddffeacaa3d396111d0eec987abe299ea9756df6b deleted_at=None +-- rule_version id=231 rule_set_id=172 version_no=2.0 status=published oss_url=rules/contract.lease/2.0/rules.yaml sha=5da7fe94f3fa7ead4c333fb7d273ba9e22ed7002ad5d7e9c6338418fcbad3c6b deleted_at=None +-- rule_version id=232 rule_set_id=173 version_no=1.0 status=published oss_url=rules/contract.loan.general/1.0/rules.yaml sha=426d0f66acaf1bac1f9bd18fda2187c2e2bffe5530aff8c76cc3cc4641892d23 deleted_at=None +-- rule_version id=233 rule_set_id=174 version_no=1.0 status=published oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=e60b679aa58958187d193d00097611c87d0bfd19f6b910f1d587fd95dd35826b deleted_at=None +-- rule_version id=234 rule_set_id=175 version_no=2.1 status=published oss_url=rules/contract.sale/2.1/rules.yaml sha=698b6df771cad864d3979ba4f5f134bf9bfe6af2f6b4aea85c8588c292ebbafc deleted_at=None +-- rule_version id=235 rule_set_id=186 version_no=1.0 status=published oss_url=rules/contract.tech/1.0/rules.yaml sha=c5ab06b6df9542715705aac6fbb0defcad35ae76ee9dea4125f57d0585b7d8ec deleted_at=None +-- rule_version id=236 rule_set_id=169 version_no=0.1 status=published oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=None +-- rule_version id=237 rule_set_id=176 version_no=1.0 status=published oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=f504ac3eccb0c148ca092d40b7bb2a87b95915b6e708395f4be00b8d337179de deleted_at=None +-- rule_version id=238 rule_set_id=177 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=None +-- rule_version id=239 rule_set_id=178 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=None +-- rule_version id=240 rule_set_id=179 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=None +-- rule_version id=241 rule_set_id=180 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=None +-- rule_version id=242 rule_set_id=181 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=None +-- rule_version id=243 rule_set_id=182 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=None +-- rule_version id=244 rule_set_id=183 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=None +-- rule_version id=245 rule_set_id=184 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=None +-- rule_version id=246 rule_set_id=185 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=None +-- rule_version id=247 rule_set_id=168 version_no=v2 status=draft oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=None +-- rule_version id=248 rule_set_id=168 version_no=v3 status=draft oss_url=rules/contract.construction.general/v3/rules.yaml sha=b8b03368e540e86bec8cff3dd3b07dacc3bded4dba0f07f25d9454b713f249b8 deleted_at=None +-- rule_version id=249 rule_set_id=168 version_no=1.2 status=published oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=None +-- rule_version id=250 rule_set_id=44 version_no=v2 status=draft oss_url=rules/contract.entrust/v2/rules.yaml sha=2d5490d16057697e60099d6cec33d51aaac2f748fc0038ae154ae2dd67f2d5a2 deleted_at=None +-- rule_version id=251 rule_set_id=44 version_no=v3 status=draft oss_url=rules/contract.entrust/v3/rules.yaml sha=18025aa0035f08f571775c2236d23ce0b6c23b0c32dd7f3ece8f97ffe81b4944 deleted_at=None +-- rule_version id=252 rule_set_id=44 version_no=v4 status=draft oss_url=rules/contract.entrust/v4/rules.yaml sha=212c1a3724b14dec4074c4e4ea62485c0b07ac5d772bce8e9a6108efe312ec05 deleted_at=None +-- rule_version id=253 rule_set_id=44 version_no=v5 status=draft oss_url=rules/contract.entrust/v5/rules.yaml sha=5424c16a3a35eb7c44131904a99e97203b41e02148b8eaba1df9782bc4309cfc deleted_at=None +-- rule_version id=254 rule_set_id=44 version_no=v6 status=draft oss_url=rules/contract.entrust/v6/rules.yaml sha=0ee30fc1e3fdaab53f47c4a8cc3479b89fabf1be821ee9b65cea9ea223a40d35 deleted_at=None +-- rule_version id=255 rule_set_id=44 version_no=v7 status=draft oss_url=rules/contract.entrust/v7/rules.yaml sha=75c817acc45e7661aab5171a987ffc9e602e79353488c89bc12fcb2f749791df deleted_at=None +-- rule_version id=256 rule_set_id=44 version_no=v8 status=draft oss_url=rules/contract.entrust/v8/rules.yaml sha=cae719d588766213e251b141d4b910388046883e135b173dae2c71888ebe4a0f deleted_at=None +-- rule_version id=257 rule_set_id=44 version_no=v9 status=draft oss_url=rules/contract.entrust/v9/rules.yaml sha=11f41652de07617382eaa68b401cdaa2f7ff2a46ac61a903e8b44cf240768784 deleted_at=None +-- rule_version id=258 rule_set_id=44 version_no=2.0 status=published oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=None +-- rule_version id=259 rule_set_id=167 version_no=0.1 status=published oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=f2159624c569d6f18f37afb8bf6fa7ea099cc2e019cbf8b3bf3a2ce25d54a727 deleted_at=None +-- rule_version id=260 rule_set_id=150 version_no=1.0 status=published oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=4f4c2b64cc84cf791be770b785986cc67f4ef864174f95d9ba7e4a84fdadc8cc deleted_at=None +-- rule_version id=261 rule_set_id=151 version_no=1.0 status=published oss_url=rules/contract.gift.general/1.0/rules.yaml sha=dd268a9c38d6143523f5afeddffeacaa3d396111d0eec987abe299ea9756df6b deleted_at=None +-- rule_version id=262 rule_set_id=152 version_no=2.0 status=published oss_url=rules/contract.lease/2.0/rules.yaml sha=5da7fe94f3fa7ead4c333fb7d273ba9e22ed7002ad5d7e9c6338418fcbad3c6b deleted_at=None +-- rule_version id=263 rule_set_id=153 version_no=1.0 status=published oss_url=rules/contract.loan.general/1.0/rules.yaml sha=426d0f66acaf1bac1f9bd18fda2187c2e2bffe5530aff8c76cc3cc4641892d23 deleted_at=None +-- rule_version id=264 rule_set_id=154 version_no=1.0 status=published oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=e60b679aa58958187d193d00097611c87d0bfd19f6b910f1d587fd95dd35826b deleted_at=None +-- rule_version id=265 rule_set_id=155 version_no=2.1 status=published oss_url=rules/contract.sale/2.1/rules.yaml sha=698b6df771cad864d3979ba4f5f134bf9bfe6af2f6b4aea85c8588c292ebbafc deleted_at=None +-- rule_version id=266 rule_set_id=166 version_no=1.0 status=published oss_url=rules/contract.tech/1.0/rules.yaml sha=c5ab06b6df9542715705aac6fbb0defcad35ae76ee9dea4125f57d0585b7d8ec deleted_at=None +-- rule_version id=267 rule_set_id=149 version_no=0.1 status=published oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=None +-- rule_version id=268 rule_set_id=156 version_no=1.0 status=published oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=f504ac3eccb0c148ca092d40b7bb2a87b95915b6e708395f4be00b8d337179de deleted_at=None +-- rule_version id=269 rule_set_id=157 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=None +-- rule_version id=270 rule_set_id=158 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=None +-- rule_version id=271 rule_set_id=159 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=None +-- rule_version id=272 rule_set_id=160 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=None +-- rule_version id=273 rule_set_id=161 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=None +-- rule_version id=274 rule_set_id=162 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=None +-- rule_version id=275 rule_set_id=163 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=None +-- rule_version id=276 rule_set_id=164 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=None +-- rule_version id=277 rule_set_id=165 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=None +-- rule_version id=278 rule_set_id=126 version_no=v2 status=draft oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=None +-- rule_version id=279 rule_set_id=126 version_no=v3 status=draft oss_url=rules/contract.construction.general/v3/rules.yaml sha=b8b03368e540e86bec8cff3dd3b07dacc3bded4dba0f07f25d9454b713f249b8 deleted_at=None +-- rule_version id=280 rule_set_id=126 version_no=1.2 status=published oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=None +-- rule_version id=281 rule_set_id=127 version_no=v2 status=draft oss_url=rules/contract.entrust/v2/rules.yaml sha=2d5490d16057697e60099d6cec33d51aaac2f748fc0038ae154ae2dd67f2d5a2 deleted_at=None +-- rule_version id=282 rule_set_id=127 version_no=v3 status=draft oss_url=rules/contract.entrust/v3/rules.yaml sha=18025aa0035f08f571775c2236d23ce0b6c23b0c32dd7f3ece8f97ffe81b4944 deleted_at=None +-- rule_version id=283 rule_set_id=127 version_no=v4 status=draft oss_url=rules/contract.entrust/v4/rules.yaml sha=212c1a3724b14dec4074c4e4ea62485c0b07ac5d772bce8e9a6108efe312ec05 deleted_at=None +-- rule_version id=284 rule_set_id=127 version_no=v5 status=draft oss_url=rules/contract.entrust/v5/rules.yaml sha=5424c16a3a35eb7c44131904a99e97203b41e02148b8eaba1df9782bc4309cfc deleted_at=None +-- rule_version id=285 rule_set_id=127 version_no=v6 status=draft oss_url=rules/contract.entrust/v6/rules.yaml sha=0ee30fc1e3fdaab53f47c4a8cc3479b89fabf1be821ee9b65cea9ea223a40d35 deleted_at=None +-- rule_version id=286 rule_set_id=127 version_no=v7 status=draft oss_url=rules/contract.entrust/v7/rules.yaml sha=75c817acc45e7661aab5171a987ffc9e602e79353488c89bc12fcb2f749791df deleted_at=None +-- rule_version id=287 rule_set_id=127 version_no=v8 status=draft oss_url=rules/contract.entrust/v8/rules.yaml sha=cae719d588766213e251b141d4b910388046883e135b173dae2c71888ebe4a0f deleted_at=None +-- rule_version id=288 rule_set_id=127 version_no=v9 status=draft oss_url=rules/contract.entrust/v9/rules.yaml sha=11f41652de07617382eaa68b401cdaa2f7ff2a46ac61a903e8b44cf240768784 deleted_at=None +-- rule_version id=289 rule_set_id=127 version_no=2.0 status=published oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=None +-- rule_version id=290 rule_set_id=125 version_no=0.1 status=published oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=f2159624c569d6f18f37afb8bf6fa7ea099cc2e019cbf8b3bf3a2ce25d54a727 deleted_at=None +-- rule_version id=291 rule_set_id=108 version_no=1.0 status=published oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=4f4c2b64cc84cf791be770b785986cc67f4ef864174f95d9ba7e4a84fdadc8cc deleted_at=None +-- rule_version id=292 rule_set_id=109 version_no=1.0 status=published oss_url=rules/contract.gift.general/1.0/rules.yaml sha=dd268a9c38d6143523f5afeddffeacaa3d396111d0eec987abe299ea9756df6b deleted_at=None +-- rule_version id=293 rule_set_id=110 version_no=2.0 status=published oss_url=rules/contract.lease/2.0/rules.yaml sha=5da7fe94f3fa7ead4c333fb7d273ba9e22ed7002ad5d7e9c6338418fcbad3c6b deleted_at=None +-- rule_version id=294 rule_set_id=111 version_no=1.0 status=published oss_url=rules/contract.loan.general/1.0/rules.yaml sha=426d0f66acaf1bac1f9bd18fda2187c2e2bffe5530aff8c76cc3cc4641892d23 deleted_at=None +-- rule_version id=295 rule_set_id=112 version_no=1.0 status=published oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=e60b679aa58958187d193d00097611c87d0bfd19f6b910f1d587fd95dd35826b deleted_at=None +-- rule_version id=296 rule_set_id=113 version_no=2.1 status=published oss_url=rules/contract.sale/2.1/rules.yaml sha=698b6df771cad864d3979ba4f5f134bf9bfe6af2f6b4aea85c8588c292ebbafc deleted_at=None +-- rule_version id=297 rule_set_id=124 version_no=1.0 status=published oss_url=rules/contract.tech/1.0/rules.yaml sha=c5ab06b6df9542715705aac6fbb0defcad35ae76ee9dea4125f57d0585b7d8ec deleted_at=None +-- rule_version id=298 rule_set_id=107 version_no=0.1 status=published oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=None +-- rule_version id=299 rule_set_id=114 version_no=1.0 status=published oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=f504ac3eccb0c148ca092d40b7bb2a87b95915b6e708395f4be00b8d337179de deleted_at=None +-- rule_version id=300 rule_set_id=115 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=None +-- rule_version id=301 rule_set_id=116 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=None +-- rule_version id=302 rule_set_id=117 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=None +-- rule_version id=303 rule_set_id=118 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=None +-- rule_version id=304 rule_set_id=119 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=None +-- rule_version id=305 rule_set_id=120 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=None +-- rule_version id=306 rule_set_id=121 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=None +-- rule_version id=307 rule_set_id=122 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=None +-- rule_version id=308 rule_set_id=123 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=None +-- rule_version id=309 rule_set_id=21 version_no=v2 status=draft oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=None +-- rule_version id=310 rule_set_id=21 version_no=v3 status=draft oss_url=rules/contract.construction.general/v3/rules.yaml sha=b8b03368e540e86bec8cff3dd3b07dacc3bded4dba0f07f25d9454b713f249b8 deleted_at=None +-- rule_version id=311 rule_set_id=21 version_no=1.2 status=published oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=None +-- rule_version id=312 rule_set_id=22 version_no=v2 status=draft oss_url=rules/contract.entrust/v2/rules.yaml sha=2d5490d16057697e60099d6cec33d51aaac2f748fc0038ae154ae2dd67f2d5a2 deleted_at=None +-- rule_version id=313 rule_set_id=22 version_no=v3 status=draft oss_url=rules/contract.entrust/v3/rules.yaml sha=18025aa0035f08f571775c2236d23ce0b6c23b0c32dd7f3ece8f97ffe81b4944 deleted_at=None +-- rule_version id=314 rule_set_id=22 version_no=v4 status=draft oss_url=rules/contract.entrust/v4/rules.yaml sha=212c1a3724b14dec4074c4e4ea62485c0b07ac5d772bce8e9a6108efe312ec05 deleted_at=None +-- rule_version id=315 rule_set_id=22 version_no=v5 status=draft oss_url=rules/contract.entrust/v5/rules.yaml sha=5424c16a3a35eb7c44131904a99e97203b41e02148b8eaba1df9782bc4309cfc deleted_at=None +-- rule_version id=316 rule_set_id=22 version_no=v6 status=draft oss_url=rules/contract.entrust/v6/rules.yaml sha=0ee30fc1e3fdaab53f47c4a8cc3479b89fabf1be821ee9b65cea9ea223a40d35 deleted_at=None +-- rule_version id=317 rule_set_id=22 version_no=v7 status=draft oss_url=rules/contract.entrust/v7/rules.yaml sha=75c817acc45e7661aab5171a987ffc9e602e79353488c89bc12fcb2f749791df deleted_at=None +-- rule_version id=318 rule_set_id=22 version_no=v8 status=draft oss_url=rules/contract.entrust/v8/rules.yaml sha=cae719d588766213e251b141d4b910388046883e135b173dae2c71888ebe4a0f deleted_at=None +-- rule_version id=319 rule_set_id=22 version_no=v9 status=draft oss_url=rules/contract.entrust/v9/rules.yaml sha=11f41652de07617382eaa68b401cdaa2f7ff2a46ac61a903e8b44cf240768784 deleted_at=None +-- rule_version id=320 rule_set_id=22 version_no=2.0 status=published oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=None +-- rule_version id=321 rule_set_id=23 version_no=0.1 status=published oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=f2159624c569d6f18f37afb8bf6fa7ea099cc2e019cbf8b3bf3a2ce25d54a727 deleted_at=None +-- rule_version id=322 rule_set_id=24 version_no=1.0 status=published oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=4f4c2b64cc84cf791be770b785986cc67f4ef864174f95d9ba7e4a84fdadc8cc deleted_at=None +-- rule_version id=323 rule_set_id=25 version_no=1.0 status=published oss_url=rules/contract.gift.general/1.0/rules.yaml sha=dd268a9c38d6143523f5afeddffeacaa3d396111d0eec987abe299ea9756df6b deleted_at=None +-- rule_version id=324 rule_set_id=26 version_no=2.0 status=published oss_url=rules/contract.lease/2.0/rules.yaml sha=5da7fe94f3fa7ead4c333fb7d273ba9e22ed7002ad5d7e9c6338418fcbad3c6b deleted_at=None +-- rule_version id=325 rule_set_id=27 version_no=1.0 status=published oss_url=rules/contract.loan.general/1.0/rules.yaml sha=426d0f66acaf1bac1f9bd18fda2187c2e2bffe5530aff8c76cc3cc4641892d23 deleted_at=None +-- rule_version id=326 rule_set_id=28 version_no=1.0 status=published oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=e60b679aa58958187d193d00097611c87d0bfd19f6b910f1d587fd95dd35826b deleted_at=None +-- rule_version id=327 rule_set_id=29 version_no=2.1 status=published oss_url=rules/contract.sale/2.1/rules.yaml sha=698b6df771cad864d3979ba4f5f134bf9bfe6af2f6b4aea85c8588c292ebbafc deleted_at=None +-- rule_version id=328 rule_set_id=30 version_no=1.0 status=published oss_url=rules/contract.tech/1.0/rules.yaml sha=c5ab06b6df9542715705aac6fbb0defcad35ae76ee9dea4125f57d0585b7d8ec deleted_at=None +-- rule_version id=329 rule_set_id=41 version_no=0.1 status=published oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=None +-- rule_version id=330 rule_set_id=31 version_no=1.0 status=published oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=f504ac3eccb0c148ca092d40b7bb2a87b95915b6e708395f4be00b8d337179de deleted_at=None +-- rule_version id=331 rule_set_id=32 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=None +-- rule_version id=332 rule_set_id=33 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=None +-- rule_version id=333 rule_set_id=34 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=None +-- rule_version id=334 rule_set_id=35 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=None +-- rule_version id=335 rule_set_id=36 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=None +-- rule_version id=336 rule_set_id=37 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=None +-- rule_version id=337 rule_set_id=38 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=None +-- rule_version id=338 rule_set_id=39 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=None +-- rule_version id=339 rule_set_id=40 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=None +-- rule_version id=340 rule_set_id=147 version_no=v2 status=draft oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=None +-- rule_version id=341 rule_set_id=147 version_no=v3 status=draft oss_url=rules/contract.construction.general/v3/rules.yaml sha=b8b03368e540e86bec8cff3dd3b07dacc3bded4dba0f07f25d9454b713f249b8 deleted_at=None +-- rule_version id=342 rule_set_id=147 version_no=1.2 status=published oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=None +-- rule_version id=343 rule_set_id=148 version_no=v2 status=draft oss_url=rules/contract.entrust/v2/rules.yaml sha=2d5490d16057697e60099d6cec33d51aaac2f748fc0038ae154ae2dd67f2d5a2 deleted_at=None +-- rule_version id=344 rule_set_id=148 version_no=v3 status=draft oss_url=rules/contract.entrust/v3/rules.yaml sha=18025aa0035f08f571775c2236d23ce0b6c23b0c32dd7f3ece8f97ffe81b4944 deleted_at=None +-- rule_version id=345 rule_set_id=148 version_no=v4 status=draft oss_url=rules/contract.entrust/v4/rules.yaml sha=212c1a3724b14dec4074c4e4ea62485c0b07ac5d772bce8e9a6108efe312ec05 deleted_at=None +-- rule_version id=346 rule_set_id=148 version_no=v5 status=draft oss_url=rules/contract.entrust/v5/rules.yaml sha=5424c16a3a35eb7c44131904a99e97203b41e02148b8eaba1df9782bc4309cfc deleted_at=None +-- rule_version id=347 rule_set_id=148 version_no=v6 status=draft oss_url=rules/contract.entrust/v6/rules.yaml sha=0ee30fc1e3fdaab53f47c4a8cc3479b89fabf1be821ee9b65cea9ea223a40d35 deleted_at=None +-- rule_version id=348 rule_set_id=148 version_no=v7 status=draft oss_url=rules/contract.entrust/v7/rules.yaml sha=75c817acc45e7661aab5171a987ffc9e602e79353488c89bc12fcb2f749791df deleted_at=None +-- rule_version id=349 rule_set_id=148 version_no=v8 status=draft oss_url=rules/contract.entrust/v8/rules.yaml sha=cae719d588766213e251b141d4b910388046883e135b173dae2c71888ebe4a0f deleted_at=None +-- rule_version id=350 rule_set_id=148 version_no=v9 status=draft oss_url=rules/contract.entrust/v9/rules.yaml sha=11f41652de07617382eaa68b401cdaa2f7ff2a46ac61a903e8b44cf240768784 deleted_at=None +-- rule_version id=351 rule_set_id=148 version_no=2.0 status=published oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=None +-- rule_version id=352 rule_set_id=146 version_no=0.1 status=published oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=f2159624c569d6f18f37afb8bf6fa7ea099cc2e019cbf8b3bf3a2ce25d54a727 deleted_at=None +-- rule_version id=353 rule_set_id=129 version_no=1.0 status=published oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=4f4c2b64cc84cf791be770b785986cc67f4ef864174f95d9ba7e4a84fdadc8cc deleted_at=None +-- rule_version id=354 rule_set_id=130 version_no=1.0 status=published oss_url=rules/contract.gift.general/1.0/rules.yaml sha=dd268a9c38d6143523f5afeddffeacaa3d396111d0eec987abe299ea9756df6b deleted_at=None +-- rule_version id=355 rule_set_id=131 version_no=2.0 status=published oss_url=rules/contract.lease/2.0/rules.yaml sha=5da7fe94f3fa7ead4c333fb7d273ba9e22ed7002ad5d7e9c6338418fcbad3c6b deleted_at=None +-- rule_version id=356 rule_set_id=132 version_no=1.0 status=published oss_url=rules/contract.loan.general/1.0/rules.yaml sha=426d0f66acaf1bac1f9bd18fda2187c2e2bffe5530aff8c76cc3cc4641892d23 deleted_at=None +-- rule_version id=357 rule_set_id=133 version_no=1.0 status=published oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=e60b679aa58958187d193d00097611c87d0bfd19f6b910f1d587fd95dd35826b deleted_at=None +-- rule_version id=358 rule_set_id=134 version_no=2.1 status=published oss_url=rules/contract.sale/2.1/rules.yaml sha=698b6df771cad864d3979ba4f5f134bf9bfe6af2f6b4aea85c8588c292ebbafc deleted_at=None +-- rule_version id=359 rule_set_id=145 version_no=1.0 status=published oss_url=rules/contract.tech/1.0/rules.yaml sha=c5ab06b6df9542715705aac6fbb0defcad35ae76ee9dea4125f57d0585b7d8ec deleted_at=None +-- rule_version id=360 rule_set_id=128 version_no=0.1 status=published oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=None +-- rule_version id=361 rule_set_id=135 version_no=1.0 status=published oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=f504ac3eccb0c148ca092d40b7bb2a87b95915b6e708395f4be00b8d337179de deleted_at=None +-- rule_version id=362 rule_set_id=136 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=None +-- rule_version id=363 rule_set_id=137 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=None +-- rule_version id=364 rule_set_id=138 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=None +-- rule_version id=365 rule_set_id=139 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=None +-- rule_version id=366 rule_set_id=140 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=None +-- rule_version id=367 rule_set_id=141 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=None +-- rule_version id=368 rule_set_id=142 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=None +-- rule_version id=369 rule_set_id=143 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=None +-- rule_version id=370 rule_set_id=144 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=None +-- rule_version id=371 rule_set_id=190 version_no=1.0 status=published oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=4f4c2b64cc84cf791be770b785986cc67f4ef864174f95d9ba7e4a84fdadc8cc deleted_at=None +-- rule_version id=372 rule_set_id=191 version_no=0.1 status=published oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=None +-- rule_version id=373 rule_set_id=192 version_no=v2 status=draft oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=None +-- rule_version id=374 rule_set_id=192 version_no=v3 status=draft oss_url=rules/contract.construction.general/v3/rules.yaml sha=b8b03368e540e86bec8cff3dd3b07dacc3bded4dba0f07f25d9454b713f249b8 deleted_at=None +-- rule_version id=375 rule_set_id=192 version_no=1.2 status=published oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=None +-- rule_version id=376 rule_set_id=193 version_no=v2 status=draft oss_url=rules/contract.entrust/v2/rules.yaml sha=2d5490d16057697e60099d6cec33d51aaac2f748fc0038ae154ae2dd67f2d5a2 deleted_at=None +-- rule_version id=377 rule_set_id=193 version_no=v3 status=draft oss_url=rules/contract.entrust/v3/rules.yaml sha=18025aa0035f08f571775c2236d23ce0b6c23b0c32dd7f3ece8f97ffe81b4944 deleted_at=None +-- rule_version id=378 rule_set_id=193 version_no=v4 status=draft oss_url=rules/contract.entrust/v4/rules.yaml sha=212c1a3724b14dec4074c4e4ea62485c0b07ac5d772bce8e9a6108efe312ec05 deleted_at=None +-- rule_version id=379 rule_set_id=193 version_no=v5 status=draft oss_url=rules/contract.entrust/v5/rules.yaml sha=5424c16a3a35eb7c44131904a99e97203b41e02148b8eaba1df9782bc4309cfc deleted_at=None +-- rule_version id=380 rule_set_id=193 version_no=v6 status=draft oss_url=rules/contract.entrust/v6/rules.yaml sha=0ee30fc1e3fdaab53f47c4a8cc3479b89fabf1be821ee9b65cea9ea223a40d35 deleted_at=None +-- rule_version id=381 rule_set_id=193 version_no=v7 status=draft oss_url=rules/contract.entrust/v7/rules.yaml sha=75c817acc45e7661aab5171a987ffc9e602e79353488c89bc12fcb2f749791df deleted_at=None +-- rule_version id=382 rule_set_id=193 version_no=v8 status=draft oss_url=rules/contract.entrust/v8/rules.yaml sha=cae719d588766213e251b141d4b910388046883e135b173dae2c71888ebe4a0f deleted_at=None +-- rule_version id=383 rule_set_id=193 version_no=v9 status=draft oss_url=rules/contract.entrust/v9/rules.yaml sha=11f41652de07617382eaa68b401cdaa2f7ff2a46ac61a903e8b44cf240768784 deleted_at=None +-- rule_version id=384 rule_set_id=193 version_no=2.0 status=published oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=None +-- rule_version id=385 rule_set_id=194 version_no=0.1 status=published oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=f2159624c569d6f18f37afb8bf6fa7ea099cc2e019cbf8b3bf3a2ce25d54a727 deleted_at=None +-- rule_version id=386 rule_set_id=195 version_no=1.0 status=published oss_url=rules/contract.gift.general/1.0/rules.yaml sha=dd268a9c38d6143523f5afeddffeacaa3d396111d0eec987abe299ea9756df6b deleted_at=None +-- rule_version id=387 rule_set_id=196 version_no=2.0 status=published oss_url=rules/contract.lease/2.0/rules.yaml sha=5da7fe94f3fa7ead4c333fb7d273ba9e22ed7002ad5d7e9c6338418fcbad3c6b deleted_at=None +-- rule_version id=388 rule_set_id=197 version_no=1.0 status=published oss_url=rules/contract.loan.general/1.0/rules.yaml sha=426d0f66acaf1bac1f9bd18fda2187c2e2bffe5530aff8c76cc3cc4641892d23 deleted_at=None +-- rule_version id=389 rule_set_id=198 version_no=1.0 status=published oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=e60b679aa58958187d193d00097611c87d0bfd19f6b910f1d587fd95dd35826b deleted_at=None +-- rule_version id=390 rule_set_id=199 version_no=2.1 status=published oss_url=rules/contract.sale/2.1/rules.yaml sha=698b6df771cad864d3979ba4f5f134bf9bfe6af2f6b4aea85c8588c292ebbafc deleted_at=None +-- rule_version id=391 rule_set_id=200 version_no=1.0 status=published oss_url=rules/contract.tech/1.0/rules.yaml sha=c5ab06b6df9542715705aac6fbb0defcad35ae76ee9dea4125f57d0585b7d8ec deleted_at=None +-- rule_version id=392 rule_set_id=201 version_no=1.0 status=published oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=f504ac3eccb0c148ca092d40b7bb2a87b95915b6e708395f4be00b8d337179de deleted_at=None +-- rule_version id=393 rule_set_id=202 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=None +-- rule_version id=394 rule_set_id=203 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=None +-- rule_version id=395 rule_set_id=204 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=None +-- rule_version id=396 rule_set_id=205 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=None +-- rule_version id=397 rule_set_id=206 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=None +-- rule_version id=398 rule_set_id=207 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=None +-- rule_version id=399 rule_set_id=208 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=None +-- rule_version id=400 rule_set_id=209 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=None +-- rule_version id=401 rule_set_id=210 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=None +-- rule_version id=402 rule_set_id=211 version_no=1.0 status=published oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=4f4c2b64cc84cf791be770b785986cc67f4ef864174f95d9ba7e4a84fdadc8cc deleted_at=None +-- rule_version id=403 rule_set_id=212 version_no=0.1 status=published oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=None +-- rule_version id=404 rule_set_id=213 version_no=v2 status=draft oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=None +-- rule_version id=405 rule_set_id=213 version_no=v3 status=draft oss_url=rules/contract.construction.general/v3/rules.yaml sha=b8b03368e540e86bec8cff3dd3b07dacc3bded4dba0f07f25d9454b713f249b8 deleted_at=None +-- rule_version id=406 rule_set_id=213 version_no=1.2 status=published oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=None +-- rule_version id=407 rule_set_id=214 version_no=v2 status=draft oss_url=rules/contract.entrust/v2/rules.yaml sha=2d5490d16057697e60099d6cec33d51aaac2f748fc0038ae154ae2dd67f2d5a2 deleted_at=None +-- rule_version id=408 rule_set_id=214 version_no=v3 status=draft oss_url=rules/contract.entrust/v3/rules.yaml sha=18025aa0035f08f571775c2236d23ce0b6c23b0c32dd7f3ece8f97ffe81b4944 deleted_at=None +-- rule_version id=409 rule_set_id=214 version_no=v4 status=draft oss_url=rules/contract.entrust/v4/rules.yaml sha=212c1a3724b14dec4074c4e4ea62485c0b07ac5d772bce8e9a6108efe312ec05 deleted_at=None +-- rule_version id=410 rule_set_id=214 version_no=v5 status=draft oss_url=rules/contract.entrust/v5/rules.yaml sha=5424c16a3a35eb7c44131904a99e97203b41e02148b8eaba1df9782bc4309cfc deleted_at=None +-- rule_version id=411 rule_set_id=214 version_no=v6 status=draft oss_url=rules/contract.entrust/v6/rules.yaml sha=0ee30fc1e3fdaab53f47c4a8cc3479b89fabf1be821ee9b65cea9ea223a40d35 deleted_at=None +-- rule_version id=412 rule_set_id=214 version_no=v7 status=draft oss_url=rules/contract.entrust/v7/rules.yaml sha=75c817acc45e7661aab5171a987ffc9e602e79353488c89bc12fcb2f749791df deleted_at=None +-- rule_version id=413 rule_set_id=214 version_no=v8 status=draft oss_url=rules/contract.entrust/v8/rules.yaml sha=cae719d588766213e251b141d4b910388046883e135b173dae2c71888ebe4a0f deleted_at=None +-- rule_version id=414 rule_set_id=214 version_no=v9 status=draft oss_url=rules/contract.entrust/v9/rules.yaml sha=11f41652de07617382eaa68b401cdaa2f7ff2a46ac61a903e8b44cf240768784 deleted_at=None +-- rule_version id=415 rule_set_id=214 version_no=2.0 status=published oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=None +-- rule_version id=416 rule_set_id=215 version_no=0.1 status=published oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=f2159624c569d6f18f37afb8bf6fa7ea099cc2e019cbf8b3bf3a2ce25d54a727 deleted_at=None +-- rule_version id=417 rule_set_id=216 version_no=1.0 status=published oss_url=rules/contract.gift.general/1.0/rules.yaml sha=dd268a9c38d6143523f5afeddffeacaa3d396111d0eec987abe299ea9756df6b deleted_at=None +-- rule_version id=418 rule_set_id=217 version_no=2.0 status=published oss_url=rules/contract.lease/2.0/rules.yaml sha=5da7fe94f3fa7ead4c333fb7d273ba9e22ed7002ad5d7e9c6338418fcbad3c6b deleted_at=None +-- rule_version id=419 rule_set_id=218 version_no=1.0 status=published oss_url=rules/contract.loan.general/1.0/rules.yaml sha=426d0f66acaf1bac1f9bd18fda2187c2e2bffe5530aff8c76cc3cc4641892d23 deleted_at=None +-- rule_version id=420 rule_set_id=219 version_no=1.0 status=published oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=e60b679aa58958187d193d00097611c87d0bfd19f6b910f1d587fd95dd35826b deleted_at=None +-- rule_version id=421 rule_set_id=220 version_no=2.1 status=published oss_url=rules/contract.sale/2.1/rules.yaml sha=698b6df771cad864d3979ba4f5f134bf9bfe6af2f6b4aea85c8588c292ebbafc deleted_at=None +-- rule_version id=422 rule_set_id=221 version_no=1.0 status=published oss_url=rules/contract.tech/1.0/rules.yaml sha=c5ab06b6df9542715705aac6fbb0defcad35ae76ee9dea4125f57d0585b7d8ec deleted_at=None +-- rule_version id=423 rule_set_id=222 version_no=1.0 status=published oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=f504ac3eccb0c148ca092d40b7bb2a87b95915b6e708395f4be00b8d337179de deleted_at=None +-- rule_version id=424 rule_set_id=223 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=None +-- rule_version id=425 rule_set_id=224 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=None +-- rule_version id=426 rule_set_id=225 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=None +-- rule_version id=427 rule_set_id=226 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=None +-- rule_version id=428 rule_set_id=227 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=None +-- rule_version id=429 rule_set_id=228 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=None +-- rule_version id=430 rule_set_id=229 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=None +-- rule_version id=431 rule_set_id=230 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=None +-- rule_version id=432 rule_set_id=231 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=None +-- rule_version id=433 rule_set_id=127 version_no=pytest-vm-1779371496 status=rollback oss_url=rules/contract.entrust/pytest-vm-1779371496/rules.yaml sha=6bb8ef6ffec27b920c9f713243e85fd19f858c3cb1b6a3216819a5c167a7bfaf deleted_at=None +-- rule_version id=434 rule_set_id=127 version_no=pytest-vm-1779371996 status=rollback oss_url=rules/contract.entrust/pytest-vm-1779371996/rules.yaml sha=4ba3e8f648ffd28d18a97d52c1c826d2ea6a6ef582c15e4e1f2bc1f1fb31e2e5 deleted_at=None +-- rule_version id=435 rule_set_id=127 version_no=pytest-vm-1779372327 status=rollback oss_url=rules/contract.entrust/pytest-vm-1779372327/rules.yaml sha=82efcd120f121d7ba6f9c6c3b310d3431ff69372e328de30c3ec73ad77b8d0cf deleted_at=None diff --git a/docs/规则编辑/backups/rule-domain-before-reset-20260521-221940.sql b/docs/规则编辑/backups/rule-domain-before-reset-20260521-221940.sql new file mode 100644 index 0000000..5f82b50 --- /dev/null +++ b/docs/规则编辑/backups/rule-domain-before-reset-20260521-221940.sql @@ -0,0 +1,527 @@ +-- Rule domain backup before reset +-- generated_at: 2026-05-21T22:19:40 +-- rule_sets: 147 +-- rule_versions: 371 + +-- This file is an audit snapshot, not an automatic restore script. +-- Use the rows below to inspect pre-reset IDs, current_version_id, oss_url and sha. + +-- rule_set id=21 tenant_code=PUBLIC rule_type=contract.construction.general current_version_id=311 status=active deleted_at=None +-- rule_set id=22 tenant_code=PUBLIC rule_type=contract.entrust current_version_id=320 status=active deleted_at=None +-- rule_set id=23 tenant_code=PUBLIC rule_type=contract.evaluation.delegation current_version_id=321 status=active deleted_at=None +-- rule_set id=24 tenant_code=PUBLIC rule_type=contract.gift.charity current_version_id=322 status=active deleted_at=None +-- rule_set id=25 tenant_code=PUBLIC rule_type=contract.gift.general current_version_id=323 status=active deleted_at=None +-- rule_set id=26 tenant_code=PUBLIC rule_type=contract.lease current_version_id=324 status=active deleted_at=None +-- rule_set id=27 tenant_code=PUBLIC rule_type=contract.loan.general current_version_id=325 status=active deleted_at=None +-- rule_set id=28 tenant_code=PUBLIC rule_type=contract.purchase.general current_version_id=326 status=active deleted_at=None +-- rule_set id=29 tenant_code=PUBLIC rule_type=contract.sale current_version_id=327 status=active deleted_at=None +-- rule_set id=30 tenant_code=PUBLIC rule_type=contract.tech current_version_id=328 status=active deleted_at=None +-- rule_set id=31 tenant_code=PUBLIC rule_type=行政卷宗.行政处罚 current_version_id=330 status=active deleted_at=None +-- rule_set id=32 tenant_code=PUBLIC rule_type=行政卷宗.行政许可.停业 current_version_id=331 status=active deleted_at=None +-- rule_set id=33 tenant_code=PUBLIC rule_type=行政卷宗.行政许可.变更 current_version_id=332 status=active deleted_at=None +-- rule_set id=34 tenant_code=PUBLIC rule_type=行政卷宗.行政许可.延续 current_version_id=333 status=active deleted_at=None +-- rule_set id=35 tenant_code=PUBLIC rule_type=行政卷宗.行政许可.恢复营业 current_version_id=334 status=active deleted_at=None +-- rule_set id=36 tenant_code=PUBLIC rule_type=行政卷宗.行政许可.收回 current_version_id=335 status=active deleted_at=None +-- rule_set id=37 tenant_code=PUBLIC rule_type=行政卷宗.行政许可.新办 current_version_id=336 status=active deleted_at=None +-- rule_set id=38 tenant_code=PUBLIC rule_type=行政卷宗.行政许可.歇业 current_version_id=337 status=active deleted_at=None +-- rule_set id=39 tenant_code=PUBLIC rule_type=行政卷宗.行政许可.注销 current_version_id=338 status=active deleted_at=None +-- rule_set id=40 tenant_code=PUBLIC rule_type=行政卷宗.行政许可.补办 current_version_id=339 status=active deleted_at=None +-- rule_set id=41 tenant_code=PUBLIC rule_type=govdoc.general current_version_id=329 status=active deleted_at=None +-- rule_set id=44 tenant_code=JY rule_type=contract.entrust current_version_id=258 status=active deleted_at=None +-- rule_set id=107 tenant_code=MZ rule_type=govdoc.general current_version_id=298 status=active deleted_at=None +-- rule_set id=108 tenant_code=MZ rule_type=contract.gift.charity current_version_id=291 status=active deleted_at=None +-- rule_set id=109 tenant_code=MZ rule_type=contract.gift.general current_version_id=292 status=active deleted_at=None +-- rule_set id=110 tenant_code=MZ rule_type=contract.lease current_version_id=293 status=active deleted_at=None +-- rule_set id=111 tenant_code=MZ rule_type=contract.loan.general current_version_id=294 status=active deleted_at=None +-- rule_set id=112 tenant_code=MZ rule_type=contract.purchase.general current_version_id=295 status=active deleted_at=None +-- rule_set id=113 tenant_code=MZ rule_type=contract.sale current_version_id=296 status=active deleted_at=None +-- rule_set id=114 tenant_code=MZ rule_type=行政卷宗.行政处罚 current_version_id=299 status=active deleted_at=None +-- rule_set id=115 tenant_code=MZ rule_type=行政卷宗.行政许可.停业 current_version_id=300 status=active deleted_at=None +-- rule_set id=116 tenant_code=MZ rule_type=行政卷宗.行政许可.变更 current_version_id=301 status=active deleted_at=None +-- rule_set id=117 tenant_code=MZ rule_type=行政卷宗.行政许可.延续 current_version_id=302 status=active deleted_at=None +-- rule_set id=118 tenant_code=MZ rule_type=行政卷宗.行政许可.恢复营业 current_version_id=303 status=active deleted_at=None +-- rule_set id=119 tenant_code=MZ rule_type=行政卷宗.行政许可.收回 current_version_id=304 status=active deleted_at=None +-- rule_set id=120 tenant_code=MZ rule_type=行政卷宗.行政许可.新办 current_version_id=305 status=active deleted_at=None +-- rule_set id=121 tenant_code=MZ rule_type=行政卷宗.行政许可.歇业 current_version_id=306 status=active deleted_at=None +-- rule_set id=122 tenant_code=MZ rule_type=行政卷宗.行政许可.注销 current_version_id=307 status=active deleted_at=None +-- rule_set id=123 tenant_code=MZ rule_type=行政卷宗.行政许可.补办 current_version_id=308 status=active deleted_at=None +-- rule_set id=124 tenant_code=MZ rule_type=contract.tech current_version_id=297 status=active deleted_at=None +-- rule_set id=125 tenant_code=MZ rule_type=contract.evaluation.delegation current_version_id=290 status=active deleted_at=None +-- rule_set id=126 tenant_code=MZ rule_type=contract.construction.general current_version_id=280 status=active deleted_at=None +-- rule_set id=127 tenant_code=MZ rule_type=contract.entrust current_version_id=289 status=active deleted_at=None +-- rule_set id=128 tenant_code=YF rule_type=govdoc.general current_version_id=360 status=active deleted_at=None +-- rule_set id=129 tenant_code=YF rule_type=contract.gift.charity current_version_id=353 status=active deleted_at=None +-- rule_set id=130 tenant_code=YF rule_type=contract.gift.general current_version_id=354 status=active deleted_at=None +-- rule_set id=131 tenant_code=YF rule_type=contract.lease current_version_id=355 status=active deleted_at=None +-- rule_set id=132 tenant_code=YF rule_type=contract.loan.general current_version_id=356 status=active deleted_at=None +-- rule_set id=133 tenant_code=YF rule_type=contract.purchase.general current_version_id=357 status=active deleted_at=None +-- rule_set id=134 tenant_code=YF rule_type=contract.sale current_version_id=358 status=active deleted_at=None +-- rule_set id=135 tenant_code=YF rule_type=行政卷宗.行政处罚 current_version_id=361 status=active deleted_at=None +-- rule_set id=136 tenant_code=YF rule_type=行政卷宗.行政许可.停业 current_version_id=362 status=active deleted_at=None +-- rule_set id=137 tenant_code=YF rule_type=行政卷宗.行政许可.变更 current_version_id=363 status=active deleted_at=None +-- rule_set id=138 tenant_code=YF rule_type=行政卷宗.行政许可.延续 current_version_id=364 status=active deleted_at=None +-- rule_set id=139 tenant_code=YF rule_type=行政卷宗.行政许可.恢复营业 current_version_id=365 status=active deleted_at=None +-- rule_set id=140 tenant_code=YF rule_type=行政卷宗.行政许可.收回 current_version_id=366 status=active deleted_at=None +-- rule_set id=141 tenant_code=YF rule_type=行政卷宗.行政许可.新办 current_version_id=367 status=active deleted_at=None +-- rule_set id=142 tenant_code=YF rule_type=行政卷宗.行政许可.歇业 current_version_id=368 status=active deleted_at=None +-- rule_set id=143 tenant_code=YF rule_type=行政卷宗.行政许可.注销 current_version_id=369 status=active deleted_at=None +-- rule_set id=144 tenant_code=YF rule_type=行政卷宗.行政许可.补办 current_version_id=370 status=active deleted_at=None +-- rule_set id=145 tenant_code=YF rule_type=contract.tech current_version_id=359 status=active deleted_at=None +-- rule_set id=146 tenant_code=YF rule_type=contract.evaluation.delegation current_version_id=352 status=active deleted_at=None +-- rule_set id=147 tenant_code=YF rule_type=contract.construction.general current_version_id=342 status=active deleted_at=None +-- rule_set id=148 tenant_code=YF rule_type=contract.entrust current_version_id=351 status=active deleted_at=None +-- rule_set id=149 tenant_code=JY rule_type=govdoc.general current_version_id=267 status=active deleted_at=None +-- rule_set id=150 tenant_code=JY rule_type=contract.gift.charity current_version_id=260 status=active deleted_at=None +-- rule_set id=151 tenant_code=JY rule_type=contract.gift.general current_version_id=261 status=active deleted_at=None +-- rule_set id=152 tenant_code=JY rule_type=contract.lease current_version_id=262 status=active deleted_at=None +-- rule_set id=153 tenant_code=JY rule_type=contract.loan.general current_version_id=263 status=active deleted_at=None +-- rule_set id=154 tenant_code=JY rule_type=contract.purchase.general current_version_id=264 status=active deleted_at=None +-- rule_set id=155 tenant_code=JY rule_type=contract.sale current_version_id=265 status=active deleted_at=None +-- rule_set id=156 tenant_code=JY rule_type=行政卷宗.行政处罚 current_version_id=268 status=active deleted_at=None +-- rule_set id=157 tenant_code=JY rule_type=行政卷宗.行政许可.停业 current_version_id=269 status=active deleted_at=None +-- rule_set id=158 tenant_code=JY rule_type=行政卷宗.行政许可.变更 current_version_id=270 status=active deleted_at=None +-- rule_set id=159 tenant_code=JY rule_type=行政卷宗.行政许可.延续 current_version_id=271 status=active deleted_at=None +-- rule_set id=160 tenant_code=JY rule_type=行政卷宗.行政许可.恢复营业 current_version_id=272 status=active deleted_at=None +-- rule_set id=161 tenant_code=JY rule_type=行政卷宗.行政许可.收回 current_version_id=273 status=active deleted_at=None +-- rule_set id=162 tenant_code=JY rule_type=行政卷宗.行政许可.新办 current_version_id=274 status=active deleted_at=None +-- rule_set id=163 tenant_code=JY rule_type=行政卷宗.行政许可.歇业 current_version_id=275 status=active deleted_at=None +-- rule_set id=164 tenant_code=JY rule_type=行政卷宗.行政许可.注销 current_version_id=276 status=active deleted_at=None +-- rule_set id=165 tenant_code=JY rule_type=行政卷宗.行政许可.补办 current_version_id=277 status=active deleted_at=None +-- rule_set id=166 tenant_code=JY rule_type=contract.tech current_version_id=266 status=active deleted_at=None +-- rule_set id=167 tenant_code=JY rule_type=contract.evaluation.delegation current_version_id=259 status=active deleted_at=None +-- rule_set id=168 tenant_code=JY rule_type=contract.construction.general current_version_id=249 status=active deleted_at=None +-- rule_set id=169 tenant_code=CZ rule_type=govdoc.general current_version_id=236 status=active deleted_at=None +-- rule_set id=170 tenant_code=CZ rule_type=contract.gift.charity current_version_id=229 status=active deleted_at=None +-- rule_set id=171 tenant_code=CZ rule_type=contract.gift.general current_version_id=230 status=active deleted_at=None +-- rule_set id=172 tenant_code=CZ rule_type=contract.lease current_version_id=231 status=active deleted_at=None +-- rule_set id=173 tenant_code=CZ rule_type=contract.loan.general current_version_id=232 status=active deleted_at=None +-- rule_set id=174 tenant_code=CZ rule_type=contract.purchase.general current_version_id=233 status=active deleted_at=None +-- rule_set id=175 tenant_code=CZ rule_type=contract.sale current_version_id=234 status=active deleted_at=None +-- rule_set id=176 tenant_code=CZ rule_type=行政卷宗.行政处罚 current_version_id=237 status=active deleted_at=None +-- rule_set id=177 tenant_code=CZ rule_type=行政卷宗.行政许可.停业 current_version_id=238 status=active deleted_at=None +-- rule_set id=178 tenant_code=CZ rule_type=行政卷宗.行政许可.变更 current_version_id=239 status=active deleted_at=None +-- rule_set id=179 tenant_code=CZ rule_type=行政卷宗.行政许可.延续 current_version_id=240 status=active deleted_at=None +-- rule_set id=180 tenant_code=CZ rule_type=行政卷宗.行政许可.恢复营业 current_version_id=241 status=active deleted_at=None +-- rule_set id=181 tenant_code=CZ rule_type=行政卷宗.行政许可.收回 current_version_id=242 status=active deleted_at=None +-- rule_set id=182 tenant_code=CZ rule_type=行政卷宗.行政许可.新办 current_version_id=243 status=active deleted_at=None +-- rule_set id=183 tenant_code=CZ rule_type=行政卷宗.行政许可.歇业 current_version_id=244 status=active deleted_at=None +-- rule_set id=184 tenant_code=CZ rule_type=行政卷宗.行政许可.注销 current_version_id=245 status=active deleted_at=None +-- rule_set id=185 tenant_code=CZ rule_type=行政卷宗.行政许可.补办 current_version_id=246 status=active deleted_at=None +-- rule_set id=186 tenant_code=CZ rule_type=contract.tech current_version_id=235 status=active deleted_at=None +-- rule_set id=187 tenant_code=CZ rule_type=contract.evaluation.delegation current_version_id=228 status=active deleted_at=None +-- rule_set id=188 tenant_code=CZ rule_type=contract.construction.general current_version_id=218 status=active deleted_at=None +-- rule_set id=189 tenant_code=CZ rule_type=contract.entrust current_version_id=227 status=active deleted_at=None +-- rule_set id=190 tenant_code=PTA01 rule_type=contract.gift.charity current_version_id=371 status=active deleted_at=None +-- rule_set id=191 tenant_code=PTA01 rule_type=govdoc.general current_version_id=372 status=active deleted_at=None +-- rule_set id=192 tenant_code=PTA01 rule_type=contract.construction.general current_version_id=375 status=active deleted_at=None +-- rule_set id=193 tenant_code=PTA01 rule_type=contract.entrust current_version_id=384 status=active deleted_at=None +-- rule_set id=194 tenant_code=PTA01 rule_type=contract.evaluation.delegation current_version_id=385 status=active deleted_at=None +-- rule_set id=195 tenant_code=PTA01 rule_type=contract.gift.general current_version_id=386 status=active deleted_at=None +-- rule_set id=196 tenant_code=PTA01 rule_type=contract.lease current_version_id=387 status=active deleted_at=None +-- rule_set id=197 tenant_code=PTA01 rule_type=contract.loan.general current_version_id=388 status=active deleted_at=None +-- rule_set id=198 tenant_code=PTA01 rule_type=contract.purchase.general current_version_id=389 status=active deleted_at=None +-- rule_set id=199 tenant_code=PTA01 rule_type=contract.sale current_version_id=390 status=active deleted_at=None +-- rule_set id=200 tenant_code=PTA01 rule_type=contract.tech current_version_id=391 status=active deleted_at=None +-- rule_set id=201 tenant_code=PTA01 rule_type=行政卷宗.行政处罚 current_version_id=392 status=active deleted_at=None +-- rule_set id=202 tenant_code=PTA01 rule_type=行政卷宗.行政许可.停业 current_version_id=393 status=active deleted_at=None +-- rule_set id=203 tenant_code=PTA01 rule_type=行政卷宗.行政许可.变更 current_version_id=394 status=active deleted_at=None +-- rule_set id=204 tenant_code=PTA01 rule_type=行政卷宗.行政许可.延续 current_version_id=395 status=active deleted_at=None +-- rule_set id=205 tenant_code=PTA01 rule_type=行政卷宗.行政许可.恢复营业 current_version_id=396 status=active deleted_at=None +-- rule_set id=206 tenant_code=PTA01 rule_type=行政卷宗.行政许可.收回 current_version_id=397 status=active deleted_at=None +-- rule_set id=207 tenant_code=PTA01 rule_type=行政卷宗.行政许可.新办 current_version_id=398 status=active deleted_at=None +-- rule_set id=208 tenant_code=PTA01 rule_type=行政卷宗.行政许可.歇业 current_version_id=399 status=active deleted_at=None +-- rule_set id=209 tenant_code=PTA01 rule_type=行政卷宗.行政许可.注销 current_version_id=400 status=active deleted_at=None +-- rule_set id=210 tenant_code=PTA01 rule_type=行政卷宗.行政许可.补办 current_version_id=401 status=active deleted_at=None +-- rule_set id=211 tenant_code=PTB01 rule_type=contract.gift.charity current_version_id=402 status=active deleted_at=None +-- rule_set id=212 tenant_code=PTB01 rule_type=govdoc.general current_version_id=403 status=active deleted_at=None +-- rule_set id=213 tenant_code=PTB01 rule_type=contract.construction.general current_version_id=406 status=active deleted_at=None +-- rule_set id=214 tenant_code=PTB01 rule_type=contract.entrust current_version_id=415 status=active deleted_at=None +-- rule_set id=215 tenant_code=PTB01 rule_type=contract.evaluation.delegation current_version_id=416 status=active deleted_at=None +-- rule_set id=216 tenant_code=PTB01 rule_type=contract.gift.general current_version_id=417 status=active deleted_at=None +-- rule_set id=217 tenant_code=PTB01 rule_type=contract.lease current_version_id=418 status=active deleted_at=None +-- rule_set id=218 tenant_code=PTB01 rule_type=contract.loan.general current_version_id=419 status=active deleted_at=None +-- rule_set id=219 tenant_code=PTB01 rule_type=contract.purchase.general current_version_id=420 status=active deleted_at=None +-- rule_set id=220 tenant_code=PTB01 rule_type=contract.sale current_version_id=421 status=active deleted_at=None +-- rule_set id=221 tenant_code=PTB01 rule_type=contract.tech current_version_id=422 status=active deleted_at=None +-- rule_set id=222 tenant_code=PTB01 rule_type=行政卷宗.行政处罚 current_version_id=423 status=active deleted_at=None +-- rule_set id=223 tenant_code=PTB01 rule_type=行政卷宗.行政许可.停业 current_version_id=424 status=active deleted_at=None +-- rule_set id=224 tenant_code=PTB01 rule_type=行政卷宗.行政许可.变更 current_version_id=425 status=active deleted_at=None +-- rule_set id=225 tenant_code=PTB01 rule_type=行政卷宗.行政许可.延续 current_version_id=426 status=active deleted_at=None +-- rule_set id=226 tenant_code=PTB01 rule_type=行政卷宗.行政许可.恢复营业 current_version_id=427 status=active deleted_at=None +-- rule_set id=227 tenant_code=PTB01 rule_type=行政卷宗.行政许可.收回 current_version_id=428 status=active deleted_at=None +-- rule_set id=228 tenant_code=PTB01 rule_type=行政卷宗.行政许可.新办 current_version_id=429 status=active deleted_at=None +-- rule_set id=229 tenant_code=PTB01 rule_type=行政卷宗.行政许可.歇业 current_version_id=430 status=active deleted_at=None +-- rule_set id=230 tenant_code=PTB01 rule_type=行政卷宗.行政许可.注销 current_version_id=431 status=active deleted_at=None +-- rule_set id=231 tenant_code=PTB01 rule_type=行政卷宗.行政许可.补办 current_version_id=432 status=active deleted_at=None + +-- rule_version id=1 rule_set_id=21 version_no=1.2 status=rollback oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=2 rule_set_id=22 version_no=2.0 status=rollback oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=3 rule_set_id=23 version_no=0.1 status=deprecated oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=7d59151c9a41903eca748c0f7bb283ddffad699a68c963af655a84e8fe621745 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=4 rule_set_id=24 version_no=1.0 status=deprecated oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=5a7a89f1e88e34c6afa9405010b1aaef517f56704cd264227d652b794e4a0acd deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=5 rule_set_id=25 version_no=1.0 status=deprecated oss_url=rules/contract.gift.general/1.0/rules.yaml sha=efeca05a6c0a0a1e1d113c781d38f6cd761d18513eb0c2274e9255e50955940e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=6 rule_set_id=26 version_no=2.0 status=deprecated oss_url=rules/contract.lease/2.0/rules.yaml sha=536265d6490c87bd7dfb66fdb0428a8164895bd368008073e74c788d2d3f1564 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=7 rule_set_id=27 version_no=1.0 status=deprecated oss_url=rules/contract.loan.general/1.0/rules.yaml sha=134cc339b7edfdc061c98becefd9e5f904d25f6e19f5a27664c46610650e07d6 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=8 rule_set_id=28 version_no=1.0 status=deprecated oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=d7d9e3b19e83716f067021fc4084a01cc0b1cc0df7f4a851faf8a0a3d33040ca deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=9 rule_set_id=29 version_no=2.1 status=deprecated oss_url=rules/contract.sale/2.1/rules.yaml sha=e246bf9554003b078c6cccfde2ac9deecad1fbe083c536edf21735f613aeae7d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=10 rule_set_id=30 version_no=1.0 status=deprecated oss_url=rules/contract.tech/1.0/rules.yaml sha=95aa3a0be32b44d5594d5ec101d6568480c36a71a1aeec31951680d8aa14e05f deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=11 rule_set_id=31 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=e96c7925535ab0dee8e1cdfa0db5ab398c9951adcda8b680c713d66cd6fa5878 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=12 rule_set_id=32 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=13 rule_set_id=33 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=14 rule_set_id=34 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=15 rule_set_id=35 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=16 rule_set_id=36 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=17 rule_set_id=37 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=18 rule_set_id=38 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=19 rule_set_id=39 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=20 rule_set_id=40 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=21 rule_set_id=21 version_no=v2 status=deprecated oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=22 rule_set_id=22 version_no=v2 status=rollback oss_url=rules/contract.entrust/v2/rules.yaml sha=5948e85e7d965b0d70710b5fef6acde5eb2a633993f3ada971447bd51c512395 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=23 rule_set_id=22 version_no=v3 status=rollback oss_url=rules/contract.entrust/v3/rules.yaml sha=9d03f9917d0d401efcbb120abdc48a7e03783cac83364692bce32c2eece8a40e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=24 rule_set_id=22 version_no=v4 status=deprecated oss_url=rules/contract.entrust/v4/rules.yaml sha=d400d2801a42b94ad87b87bbad291b7f036acf0808a6dbc025336dedc181d21f deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=25 rule_set_id=22 version_no=v5 status=rollback oss_url=rules/contract.entrust/v5/rules.yaml sha=fa0e241874262f7229387da9e352073dc3decfe3b8048e559c35cad0ff4b2b49 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=26 rule_set_id=22 version_no=v6 status=deprecated oss_url=rules/contract.entrust/v6/rules.yaml sha=86fe184ffcc2a0ef8639f91632f0859519e96fb96f2f7d29b0f6e2343d03ab07 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=27 rule_set_id=22 version_no=v7 status=deprecated oss_url=rules/contract.entrust/v7/rules.yaml sha=ba305b20965a5ed794539fe23a3c0534c9149380fc3a67d977ddfe7f0ecbf9f8 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=28 rule_set_id=22 version_no=v8 status=deprecated oss_url=rules/contract.entrust/v8/rules.yaml sha=cf1938696438a2ae6a8b584207f99a4f167512383e456dd0041a2d9287db38b0 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=29 rule_set_id=22 version_no=v9 status=rollback oss_url=rules/contract.entrust/v9/rules.yaml sha=81d41fe8d27d394e4372a0668a2acb8975003975929d1c5f56c379c41cc23d33 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=30 rule_set_id=21 version_no=v3 status=rollback oss_url=rules/contract.construction.general/v3/rules.yaml sha=a5c21fac8c4352fcf75c6b17a8ec5c6ff71b54b453a3338b3f5a68b6f496f355 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=31 rule_set_id=41 version_no=0.1 status=deprecated oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=32 rule_set_id=44 version_no=v10 status=deprecated oss_url=rules/contract.entrust/v10/rules.yaml sha=92310866a9ee3030e3b4198623ab4dc45ac5dfdd0b2cb08d334622544eb6e599 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=97 rule_set_id=107 version_no=0.1 status=deprecated oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=98 rule_set_id=126 version_no=1.2 status=rollback oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=99 rule_set_id=126 version_no=v2 status=rollback oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=100 rule_set_id=126 version_no=v3 status=deprecated oss_url=rules/contract.construction.general/v3/rules.yaml sha=a5c21fac8c4352fcf75c6b17a8ec5c6ff71b54b453a3338b3f5a68b6f496f355 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=101 rule_set_id=108 version_no=1.0 status=deprecated oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=5a7a89f1e88e34c6afa9405010b1aaef517f56704cd264227d652b794e4a0acd deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=102 rule_set_id=109 version_no=1.0 status=deprecated oss_url=rules/contract.gift.general/1.0/rules.yaml sha=efeca05a6c0a0a1e1d113c781d38f6cd761d18513eb0c2274e9255e50955940e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=103 rule_set_id=110 version_no=2.0 status=deprecated oss_url=rules/contract.lease/2.0/rules.yaml sha=536265d6490c87bd7dfb66fdb0428a8164895bd368008073e74c788d2d3f1564 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=104 rule_set_id=111 version_no=1.0 status=deprecated oss_url=rules/contract.loan.general/1.0/rules.yaml sha=134cc339b7edfdc061c98becefd9e5f904d25f6e19f5a27664c46610650e07d6 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=105 rule_set_id=112 version_no=1.0 status=deprecated oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=d7d9e3b19e83716f067021fc4084a01cc0b1cc0df7f4a851faf8a0a3d33040ca deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=106 rule_set_id=113 version_no=2.1 status=deprecated oss_url=rules/contract.sale/2.1/rules.yaml sha=e246bf9554003b078c6cccfde2ac9deecad1fbe083c536edf21735f613aeae7d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=107 rule_set_id=114 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=e96c7925535ab0dee8e1cdfa0db5ab398c9951adcda8b680c713d66cd6fa5878 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=108 rule_set_id=115 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=109 rule_set_id=116 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=110 rule_set_id=117 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=111 rule_set_id=118 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=112 rule_set_id=119 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=113 rule_set_id=120 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=114 rule_set_id=121 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=115 rule_set_id=122 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=116 rule_set_id=123 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=117 rule_set_id=124 version_no=1.0 status=deprecated oss_url=rules/contract.tech/1.0/rules.yaml sha=95aa3a0be32b44d5594d5ec101d6568480c36a71a1aeec31951680d8aa14e05f deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=118 rule_set_id=125 version_no=0.1 status=deprecated oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=7d59151c9a41903eca748c0f7bb283ddffad699a68c963af655a84e8fe621745 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=119 rule_set_id=127 version_no=v5 status=rollback oss_url=rules/contract.entrust/v5/rules.yaml sha=fa0e241874262f7229387da9e352073dc3decfe3b8048e559c35cad0ff4b2b49 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=120 rule_set_id=127 version_no=2.0 status=rollback oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=121 rule_set_id=127 version_no=v8 status=deprecated oss_url=rules/contract.entrust/v8/rules.yaml sha=cf1938696438a2ae6a8b584207f99a4f167512383e456dd0041a2d9287db38b0 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=122 rule_set_id=127 version_no=v9 status=rollback oss_url=rules/contract.entrust/v9/rules.yaml sha=81d41fe8d27d394e4372a0668a2acb8975003975929d1c5f56c379c41cc23d33 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=123 rule_set_id=127 version_no=v6 status=deprecated oss_url=rules/contract.entrust/v6/rules.yaml sha=86fe184ffcc2a0ef8639f91632f0859519e96fb96f2f7d29b0f6e2343d03ab07 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=124 rule_set_id=127 version_no=v7 status=deprecated oss_url=rules/contract.entrust/v7/rules.yaml sha=ba305b20965a5ed794539fe23a3c0534c9149380fc3a67d977ddfe7f0ecbf9f8 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=125 rule_set_id=127 version_no=v4 status=deprecated oss_url=rules/contract.entrust/v4/rules.yaml sha=d400d2801a42b94ad87b87bbad291b7f036acf0808a6dbc025336dedc181d21f deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=126 rule_set_id=127 version_no=v3 status=rollback oss_url=rules/contract.entrust/v3/rules.yaml sha=9d03f9917d0d401efcbb120abdc48a7e03783cac83364692bce32c2eece8a40e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=127 rule_set_id=127 version_no=v2 status=rollback oss_url=rules/contract.entrust/v2/rules.yaml sha=5948e85e7d965b0d70710b5fef6acde5eb2a633993f3ada971447bd51c512395 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=128 rule_set_id=128 version_no=0.1 status=deprecated oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=129 rule_set_id=129 version_no=1.0 status=deprecated oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=5a7a89f1e88e34c6afa9405010b1aaef517f56704cd264227d652b794e4a0acd deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=130 rule_set_id=130 version_no=1.0 status=deprecated oss_url=rules/contract.gift.general/1.0/rules.yaml sha=efeca05a6c0a0a1e1d113c781d38f6cd761d18513eb0c2274e9255e50955940e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=131 rule_set_id=131 version_no=2.0 status=deprecated oss_url=rules/contract.lease/2.0/rules.yaml sha=536265d6490c87bd7dfb66fdb0428a8164895bd368008073e74c788d2d3f1564 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=132 rule_set_id=132 version_no=1.0 status=deprecated oss_url=rules/contract.loan.general/1.0/rules.yaml sha=134cc339b7edfdc061c98becefd9e5f904d25f6e19f5a27664c46610650e07d6 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=133 rule_set_id=133 version_no=1.0 status=deprecated oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=d7d9e3b19e83716f067021fc4084a01cc0b1cc0df7f4a851faf8a0a3d33040ca deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=134 rule_set_id=134 version_no=2.1 status=deprecated oss_url=rules/contract.sale/2.1/rules.yaml sha=e246bf9554003b078c6cccfde2ac9deecad1fbe083c536edf21735f613aeae7d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=135 rule_set_id=135 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=e96c7925535ab0dee8e1cdfa0db5ab398c9951adcda8b680c713d66cd6fa5878 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=136 rule_set_id=136 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=137 rule_set_id=137 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=138 rule_set_id=138 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=139 rule_set_id=139 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=140 rule_set_id=140 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=141 rule_set_id=141 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=142 rule_set_id=142 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=143 rule_set_id=143 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=144 rule_set_id=144 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=145 rule_set_id=145 version_no=1.0 status=deprecated oss_url=rules/contract.tech/1.0/rules.yaml sha=95aa3a0be32b44d5594d5ec101d6568480c36a71a1aeec31951680d8aa14e05f deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=146 rule_set_id=146 version_no=0.1 status=deprecated oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=7d59151c9a41903eca748c0f7bb283ddffad699a68c963af655a84e8fe621745 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=147 rule_set_id=147 version_no=1.2 status=rollback oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=148 rule_set_id=147 version_no=v2 status=deprecated oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=149 rule_set_id=147 version_no=v3 status=rollback oss_url=rules/contract.construction.general/v3/rules.yaml sha=a5c21fac8c4352fcf75c6b17a8ec5c6ff71b54b453a3338b3f5a68b6f496f355 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=150 rule_set_id=148 version_no=v5 status=rollback oss_url=rules/contract.entrust/v5/rules.yaml sha=fa0e241874262f7229387da9e352073dc3decfe3b8048e559c35cad0ff4b2b49 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=151 rule_set_id=148 version_no=2.0 status=rollback oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=152 rule_set_id=148 version_no=v8 status=deprecated oss_url=rules/contract.entrust/v8/rules.yaml sha=cf1938696438a2ae6a8b584207f99a4f167512383e456dd0041a2d9287db38b0 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=153 rule_set_id=148 version_no=v9 status=rollback oss_url=rules/contract.entrust/v9/rules.yaml sha=81d41fe8d27d394e4372a0668a2acb8975003975929d1c5f56c379c41cc23d33 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=154 rule_set_id=148 version_no=v6 status=deprecated oss_url=rules/contract.entrust/v6/rules.yaml sha=86fe184ffcc2a0ef8639f91632f0859519e96fb96f2f7d29b0f6e2343d03ab07 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=155 rule_set_id=148 version_no=v7 status=deprecated oss_url=rules/contract.entrust/v7/rules.yaml sha=ba305b20965a5ed794539fe23a3c0534c9149380fc3a67d977ddfe7f0ecbf9f8 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=156 rule_set_id=148 version_no=v4 status=deprecated oss_url=rules/contract.entrust/v4/rules.yaml sha=d400d2801a42b94ad87b87bbad291b7f036acf0808a6dbc025336dedc181d21f deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=157 rule_set_id=148 version_no=v3 status=rollback oss_url=rules/contract.entrust/v3/rules.yaml sha=9d03f9917d0d401efcbb120abdc48a7e03783cac83364692bce32c2eece8a40e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=158 rule_set_id=148 version_no=v2 status=rollback oss_url=rules/contract.entrust/v2/rules.yaml sha=5948e85e7d965b0d70710b5fef6acde5eb2a633993f3ada971447bd51c512395 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=159 rule_set_id=149 version_no=0.1 status=deprecated oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=160 rule_set_id=150 version_no=1.0 status=deprecated oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=5a7a89f1e88e34c6afa9405010b1aaef517f56704cd264227d652b794e4a0acd deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=161 rule_set_id=151 version_no=1.0 status=deprecated oss_url=rules/contract.gift.general/1.0/rules.yaml sha=efeca05a6c0a0a1e1d113c781d38f6cd761d18513eb0c2274e9255e50955940e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=162 rule_set_id=152 version_no=2.0 status=deprecated oss_url=rules/contract.lease/2.0/rules.yaml sha=536265d6490c87bd7dfb66fdb0428a8164895bd368008073e74c788d2d3f1564 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=163 rule_set_id=153 version_no=1.0 status=deprecated oss_url=rules/contract.loan.general/1.0/rules.yaml sha=134cc339b7edfdc061c98becefd9e5f904d25f6e19f5a27664c46610650e07d6 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=164 rule_set_id=154 version_no=1.0 status=deprecated oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=d7d9e3b19e83716f067021fc4084a01cc0b1cc0df7f4a851faf8a0a3d33040ca deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=165 rule_set_id=155 version_no=2.1 status=deprecated oss_url=rules/contract.sale/2.1/rules.yaml sha=e246bf9554003b078c6cccfde2ac9deecad1fbe083c536edf21735f613aeae7d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=166 rule_set_id=156 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=e96c7925535ab0dee8e1cdfa0db5ab398c9951adcda8b680c713d66cd6fa5878 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=167 rule_set_id=157 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=168 rule_set_id=158 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=169 rule_set_id=159 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=170 rule_set_id=160 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=171 rule_set_id=161 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=172 rule_set_id=162 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=173 rule_set_id=163 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=174 rule_set_id=164 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=175 rule_set_id=165 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=176 rule_set_id=166 version_no=1.0 status=deprecated oss_url=rules/contract.tech/1.0/rules.yaml sha=95aa3a0be32b44d5594d5ec101d6568480c36a71a1aeec31951680d8aa14e05f deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=177 rule_set_id=167 version_no=0.1 status=deprecated oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=7d59151c9a41903eca748c0f7bb283ddffad699a68c963af655a84e8fe621745 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=178 rule_set_id=168 version_no=1.2 status=deprecated oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=179 rule_set_id=168 version_no=v2 status=rollback oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=180 rule_set_id=168 version_no=v3 status=rollback oss_url=rules/contract.construction.general/v3/rules.yaml sha=a5c21fac8c4352fcf75c6b17a8ec5c6ff71b54b453a3338b3f5a68b6f496f355 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=181 rule_set_id=169 version_no=0.1 status=deprecated oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=182 rule_set_id=170 version_no=1.0 status=deprecated oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=5a7a89f1e88e34c6afa9405010b1aaef517f56704cd264227d652b794e4a0acd deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=183 rule_set_id=171 version_no=1.0 status=deprecated oss_url=rules/contract.gift.general/1.0/rules.yaml sha=efeca05a6c0a0a1e1d113c781d38f6cd761d18513eb0c2274e9255e50955940e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=184 rule_set_id=172 version_no=2.0 status=deprecated oss_url=rules/contract.lease/2.0/rules.yaml sha=536265d6490c87bd7dfb66fdb0428a8164895bd368008073e74c788d2d3f1564 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=185 rule_set_id=173 version_no=1.0 status=deprecated oss_url=rules/contract.loan.general/1.0/rules.yaml sha=134cc339b7edfdc061c98becefd9e5f904d25f6e19f5a27664c46610650e07d6 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=186 rule_set_id=174 version_no=1.0 status=deprecated oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=d7d9e3b19e83716f067021fc4084a01cc0b1cc0df7f4a851faf8a0a3d33040ca deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=187 rule_set_id=175 version_no=2.1 status=deprecated oss_url=rules/contract.sale/2.1/rules.yaml sha=e246bf9554003b078c6cccfde2ac9deecad1fbe083c536edf21735f613aeae7d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=188 rule_set_id=176 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=e96c7925535ab0dee8e1cdfa0db5ab398c9951adcda8b680c713d66cd6fa5878 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=189 rule_set_id=177 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=190 rule_set_id=178 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=191 rule_set_id=179 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=192 rule_set_id=180 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=193 rule_set_id=181 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=194 rule_set_id=182 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=195 rule_set_id=183 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=196 rule_set_id=184 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=197 rule_set_id=185 version_no=1.0 status=deprecated oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=198 rule_set_id=186 version_no=1.0 status=deprecated oss_url=rules/contract.tech/1.0/rules.yaml sha=95aa3a0be32b44d5594d5ec101d6568480c36a71a1aeec31951680d8aa14e05f deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=199 rule_set_id=187 version_no=0.1 status=deprecated oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=7d59151c9a41903eca748c0f7bb283ddffad699a68c963af655a84e8fe621745 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=200 rule_set_id=188 version_no=1.2 status=rollback oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=201 rule_set_id=188 version_no=v2 status=deprecated oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=202 rule_set_id=188 version_no=v3 status=rollback oss_url=rules/contract.construction.general/v3/rules.yaml sha=a5c21fac8c4352fcf75c6b17a8ec5c6ff71b54b453a3338b3f5a68b6f496f355 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=203 rule_set_id=189 version_no=v5 status=rollback oss_url=rules/contract.entrust/v5/rules.yaml sha=fa0e241874262f7229387da9e352073dc3decfe3b8048e559c35cad0ff4b2b49 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=204 rule_set_id=189 version_no=2.0 status=rollback oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=205 rule_set_id=189 version_no=v8 status=deprecated oss_url=rules/contract.entrust/v8/rules.yaml sha=cf1938696438a2ae6a8b584207f99a4f167512383e456dd0041a2d9287db38b0 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=206 rule_set_id=189 version_no=v9 status=rollback oss_url=rules/contract.entrust/v9/rules.yaml sha=81d41fe8d27d394e4372a0668a2acb8975003975929d1c5f56c379c41cc23d33 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=207 rule_set_id=189 version_no=v6 status=deprecated oss_url=rules/contract.entrust/v6/rules.yaml sha=86fe184ffcc2a0ef8639f91632f0859519e96fb96f2f7d29b0f6e2343d03ab07 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=208 rule_set_id=189 version_no=v7 status=deprecated oss_url=rules/contract.entrust/v7/rules.yaml sha=ba305b20965a5ed794539fe23a3c0534c9149380fc3a67d977ddfe7f0ecbf9f8 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=209 rule_set_id=189 version_no=v4 status=deprecated oss_url=rules/contract.entrust/v4/rules.yaml sha=d400d2801a42b94ad87b87bbad291b7f036acf0808a6dbc025336dedc181d21f deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=210 rule_set_id=189 version_no=v3 status=rollback oss_url=rules/contract.entrust/v3/rules.yaml sha=9d03f9917d0d401efcbb120abdc48a7e03783cac83364692bce32c2eece8a40e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=211 rule_set_id=189 version_no=v2 status=rollback oss_url=rules/contract.entrust/v2/rules.yaml sha=5948e85e7d965b0d70710b5fef6acde5eb2a633993f3ada971447bd51c512395 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=212 rule_set_id=168 version_no=v4 status=rollback oss_url=rules/contract.construction.general/v4/rules.yaml sha=5b058dbb0dd14a31132110f04e649d26cc2d9f544ec2f8ffa56c4ada1917cd13 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=213 rule_set_id=22 version_no=v10 status=draft oss_url=rules/contract.entrust/v10/rules.yaml sha=0481fb582bf30fb137d9552cecdae22fd6ee525954b893ccaec386b186caabcc deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=214 rule_set_id=22 version_no=v11 status=draft oss_url=rules/contract.entrust/v11/rules.yaml sha=4fb270e13c9c3ab9d771807f9f333232a9cf78567163521606165b0889200c3e deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=215 rule_set_id=127 version_no=pytest-vm-1779365463 status=rollback oss_url=rules/contract.entrust/pytest-vm-1779365463/rules.yaml sha=59763ad2915c90871dfa641264b74fe22f164b7590bd26a71f448dccba66d4e0 deleted_at=2026-05-21 13:46:08.095958+00:00 +-- rule_version id=216 rule_set_id=188 version_no=v2 status=draft oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=None +-- rule_version id=217 rule_set_id=188 version_no=v3 status=draft oss_url=rules/contract.construction.general/v3/rules.yaml sha=b8b03368e540e86bec8cff3dd3b07dacc3bded4dba0f07f25d9454b713f249b8 deleted_at=None +-- rule_version id=218 rule_set_id=188 version_no=1.2 status=published oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=None +-- rule_version id=219 rule_set_id=189 version_no=v2 status=draft oss_url=rules/contract.entrust/v2/rules.yaml sha=2d5490d16057697e60099d6cec33d51aaac2f748fc0038ae154ae2dd67f2d5a2 deleted_at=None +-- rule_version id=220 rule_set_id=189 version_no=v3 status=draft oss_url=rules/contract.entrust/v3/rules.yaml sha=18025aa0035f08f571775c2236d23ce0b6c23b0c32dd7f3ece8f97ffe81b4944 deleted_at=None +-- rule_version id=221 rule_set_id=189 version_no=v4 status=draft oss_url=rules/contract.entrust/v4/rules.yaml sha=212c1a3724b14dec4074c4e4ea62485c0b07ac5d772bce8e9a6108efe312ec05 deleted_at=None +-- rule_version id=222 rule_set_id=189 version_no=v5 status=draft oss_url=rules/contract.entrust/v5/rules.yaml sha=5424c16a3a35eb7c44131904a99e97203b41e02148b8eaba1df9782bc4309cfc deleted_at=None +-- rule_version id=223 rule_set_id=189 version_no=v6 status=draft oss_url=rules/contract.entrust/v6/rules.yaml sha=0ee30fc1e3fdaab53f47c4a8cc3479b89fabf1be821ee9b65cea9ea223a40d35 deleted_at=None +-- rule_version id=224 rule_set_id=189 version_no=v7 status=draft oss_url=rules/contract.entrust/v7/rules.yaml sha=75c817acc45e7661aab5171a987ffc9e602e79353488c89bc12fcb2f749791df deleted_at=None +-- rule_version id=225 rule_set_id=189 version_no=v8 status=draft oss_url=rules/contract.entrust/v8/rules.yaml sha=cae719d588766213e251b141d4b910388046883e135b173dae2c71888ebe4a0f deleted_at=None +-- rule_version id=226 rule_set_id=189 version_no=v9 status=draft oss_url=rules/contract.entrust/v9/rules.yaml sha=11f41652de07617382eaa68b401cdaa2f7ff2a46ac61a903e8b44cf240768784 deleted_at=None +-- rule_version id=227 rule_set_id=189 version_no=2.0 status=published oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=None +-- rule_version id=228 rule_set_id=187 version_no=0.1 status=published oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=f2159624c569d6f18f37afb8bf6fa7ea099cc2e019cbf8b3bf3a2ce25d54a727 deleted_at=None +-- rule_version id=229 rule_set_id=170 version_no=1.0 status=published oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=4f4c2b64cc84cf791be770b785986cc67f4ef864174f95d9ba7e4a84fdadc8cc deleted_at=None +-- rule_version id=230 rule_set_id=171 version_no=1.0 status=published oss_url=rules/contract.gift.general/1.0/rules.yaml sha=dd268a9c38d6143523f5afeddffeacaa3d396111d0eec987abe299ea9756df6b deleted_at=None +-- rule_version id=231 rule_set_id=172 version_no=2.0 status=published oss_url=rules/contract.lease/2.0/rules.yaml sha=5da7fe94f3fa7ead4c333fb7d273ba9e22ed7002ad5d7e9c6338418fcbad3c6b deleted_at=None +-- rule_version id=232 rule_set_id=173 version_no=1.0 status=published oss_url=rules/contract.loan.general/1.0/rules.yaml sha=426d0f66acaf1bac1f9bd18fda2187c2e2bffe5530aff8c76cc3cc4641892d23 deleted_at=None +-- rule_version id=233 rule_set_id=174 version_no=1.0 status=published oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=e60b679aa58958187d193d00097611c87d0bfd19f6b910f1d587fd95dd35826b deleted_at=None +-- rule_version id=234 rule_set_id=175 version_no=2.1 status=published oss_url=rules/contract.sale/2.1/rules.yaml sha=698b6df771cad864d3979ba4f5f134bf9bfe6af2f6b4aea85c8588c292ebbafc deleted_at=None +-- rule_version id=235 rule_set_id=186 version_no=1.0 status=published oss_url=rules/contract.tech/1.0/rules.yaml sha=c5ab06b6df9542715705aac6fbb0defcad35ae76ee9dea4125f57d0585b7d8ec deleted_at=None +-- rule_version id=236 rule_set_id=169 version_no=0.1 status=published oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=None +-- rule_version id=237 rule_set_id=176 version_no=1.0 status=published oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=f504ac3eccb0c148ca092d40b7bb2a87b95915b6e708395f4be00b8d337179de deleted_at=None +-- rule_version id=238 rule_set_id=177 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=None +-- rule_version id=239 rule_set_id=178 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=None +-- rule_version id=240 rule_set_id=179 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=None +-- rule_version id=241 rule_set_id=180 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=None +-- rule_version id=242 rule_set_id=181 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=None +-- rule_version id=243 rule_set_id=182 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=None +-- rule_version id=244 rule_set_id=183 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=None +-- rule_version id=245 rule_set_id=184 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=None +-- rule_version id=246 rule_set_id=185 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=None +-- rule_version id=247 rule_set_id=168 version_no=v2 status=draft oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=None +-- rule_version id=248 rule_set_id=168 version_no=v3 status=draft oss_url=rules/contract.construction.general/v3/rules.yaml sha=b8b03368e540e86bec8cff3dd3b07dacc3bded4dba0f07f25d9454b713f249b8 deleted_at=None +-- rule_version id=249 rule_set_id=168 version_no=1.2 status=published oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=None +-- rule_version id=250 rule_set_id=44 version_no=v2 status=draft oss_url=rules/contract.entrust/v2/rules.yaml sha=2d5490d16057697e60099d6cec33d51aaac2f748fc0038ae154ae2dd67f2d5a2 deleted_at=None +-- rule_version id=251 rule_set_id=44 version_no=v3 status=draft oss_url=rules/contract.entrust/v3/rules.yaml sha=18025aa0035f08f571775c2236d23ce0b6c23b0c32dd7f3ece8f97ffe81b4944 deleted_at=None +-- rule_version id=252 rule_set_id=44 version_no=v4 status=draft oss_url=rules/contract.entrust/v4/rules.yaml sha=212c1a3724b14dec4074c4e4ea62485c0b07ac5d772bce8e9a6108efe312ec05 deleted_at=None +-- rule_version id=253 rule_set_id=44 version_no=v5 status=draft oss_url=rules/contract.entrust/v5/rules.yaml sha=5424c16a3a35eb7c44131904a99e97203b41e02148b8eaba1df9782bc4309cfc deleted_at=None +-- rule_version id=254 rule_set_id=44 version_no=v6 status=draft oss_url=rules/contract.entrust/v6/rules.yaml sha=0ee30fc1e3fdaab53f47c4a8cc3479b89fabf1be821ee9b65cea9ea223a40d35 deleted_at=None +-- rule_version id=255 rule_set_id=44 version_no=v7 status=draft oss_url=rules/contract.entrust/v7/rules.yaml sha=75c817acc45e7661aab5171a987ffc9e602e79353488c89bc12fcb2f749791df deleted_at=None +-- rule_version id=256 rule_set_id=44 version_no=v8 status=draft oss_url=rules/contract.entrust/v8/rules.yaml sha=cae719d588766213e251b141d4b910388046883e135b173dae2c71888ebe4a0f deleted_at=None +-- rule_version id=257 rule_set_id=44 version_no=v9 status=draft oss_url=rules/contract.entrust/v9/rules.yaml sha=11f41652de07617382eaa68b401cdaa2f7ff2a46ac61a903e8b44cf240768784 deleted_at=None +-- rule_version id=258 rule_set_id=44 version_no=2.0 status=published oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=None +-- rule_version id=259 rule_set_id=167 version_no=0.1 status=published oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=f2159624c569d6f18f37afb8bf6fa7ea099cc2e019cbf8b3bf3a2ce25d54a727 deleted_at=None +-- rule_version id=260 rule_set_id=150 version_no=1.0 status=published oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=4f4c2b64cc84cf791be770b785986cc67f4ef864174f95d9ba7e4a84fdadc8cc deleted_at=None +-- rule_version id=261 rule_set_id=151 version_no=1.0 status=published oss_url=rules/contract.gift.general/1.0/rules.yaml sha=dd268a9c38d6143523f5afeddffeacaa3d396111d0eec987abe299ea9756df6b deleted_at=None +-- rule_version id=262 rule_set_id=152 version_no=2.0 status=published oss_url=rules/contract.lease/2.0/rules.yaml sha=5da7fe94f3fa7ead4c333fb7d273ba9e22ed7002ad5d7e9c6338418fcbad3c6b deleted_at=None +-- rule_version id=263 rule_set_id=153 version_no=1.0 status=published oss_url=rules/contract.loan.general/1.0/rules.yaml sha=426d0f66acaf1bac1f9bd18fda2187c2e2bffe5530aff8c76cc3cc4641892d23 deleted_at=None +-- rule_version id=264 rule_set_id=154 version_no=1.0 status=published oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=e60b679aa58958187d193d00097611c87d0bfd19f6b910f1d587fd95dd35826b deleted_at=None +-- rule_version id=265 rule_set_id=155 version_no=2.1 status=published oss_url=rules/contract.sale/2.1/rules.yaml sha=698b6df771cad864d3979ba4f5f134bf9bfe6af2f6b4aea85c8588c292ebbafc deleted_at=None +-- rule_version id=266 rule_set_id=166 version_no=1.0 status=published oss_url=rules/contract.tech/1.0/rules.yaml sha=c5ab06b6df9542715705aac6fbb0defcad35ae76ee9dea4125f57d0585b7d8ec deleted_at=None +-- rule_version id=267 rule_set_id=149 version_no=0.1 status=published oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=None +-- rule_version id=268 rule_set_id=156 version_no=1.0 status=published oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=f504ac3eccb0c148ca092d40b7bb2a87b95915b6e708395f4be00b8d337179de deleted_at=None +-- rule_version id=269 rule_set_id=157 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=None +-- rule_version id=270 rule_set_id=158 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=None +-- rule_version id=271 rule_set_id=159 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=None +-- rule_version id=272 rule_set_id=160 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=None +-- rule_version id=273 rule_set_id=161 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=None +-- rule_version id=274 rule_set_id=162 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=None +-- rule_version id=275 rule_set_id=163 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=None +-- rule_version id=276 rule_set_id=164 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=None +-- rule_version id=277 rule_set_id=165 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=None +-- rule_version id=278 rule_set_id=126 version_no=v2 status=draft oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=None +-- rule_version id=279 rule_set_id=126 version_no=v3 status=draft oss_url=rules/contract.construction.general/v3/rules.yaml sha=b8b03368e540e86bec8cff3dd3b07dacc3bded4dba0f07f25d9454b713f249b8 deleted_at=None +-- rule_version id=280 rule_set_id=126 version_no=1.2 status=published oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=None +-- rule_version id=281 rule_set_id=127 version_no=v2 status=draft oss_url=rules/contract.entrust/v2/rules.yaml sha=2d5490d16057697e60099d6cec33d51aaac2f748fc0038ae154ae2dd67f2d5a2 deleted_at=None +-- rule_version id=282 rule_set_id=127 version_no=v3 status=draft oss_url=rules/contract.entrust/v3/rules.yaml sha=18025aa0035f08f571775c2236d23ce0b6c23b0c32dd7f3ece8f97ffe81b4944 deleted_at=None +-- rule_version id=283 rule_set_id=127 version_no=v4 status=draft oss_url=rules/contract.entrust/v4/rules.yaml sha=212c1a3724b14dec4074c4e4ea62485c0b07ac5d772bce8e9a6108efe312ec05 deleted_at=None +-- rule_version id=284 rule_set_id=127 version_no=v5 status=draft oss_url=rules/contract.entrust/v5/rules.yaml sha=5424c16a3a35eb7c44131904a99e97203b41e02148b8eaba1df9782bc4309cfc deleted_at=None +-- rule_version id=285 rule_set_id=127 version_no=v6 status=draft oss_url=rules/contract.entrust/v6/rules.yaml sha=0ee30fc1e3fdaab53f47c4a8cc3479b89fabf1be821ee9b65cea9ea223a40d35 deleted_at=None +-- rule_version id=286 rule_set_id=127 version_no=v7 status=draft oss_url=rules/contract.entrust/v7/rules.yaml sha=75c817acc45e7661aab5171a987ffc9e602e79353488c89bc12fcb2f749791df deleted_at=None +-- rule_version id=287 rule_set_id=127 version_no=v8 status=draft oss_url=rules/contract.entrust/v8/rules.yaml sha=cae719d588766213e251b141d4b910388046883e135b173dae2c71888ebe4a0f deleted_at=None +-- rule_version id=288 rule_set_id=127 version_no=v9 status=draft oss_url=rules/contract.entrust/v9/rules.yaml sha=11f41652de07617382eaa68b401cdaa2f7ff2a46ac61a903e8b44cf240768784 deleted_at=None +-- rule_version id=289 rule_set_id=127 version_no=2.0 status=published oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=None +-- rule_version id=290 rule_set_id=125 version_no=0.1 status=published oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=f2159624c569d6f18f37afb8bf6fa7ea099cc2e019cbf8b3bf3a2ce25d54a727 deleted_at=None +-- rule_version id=291 rule_set_id=108 version_no=1.0 status=published oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=4f4c2b64cc84cf791be770b785986cc67f4ef864174f95d9ba7e4a84fdadc8cc deleted_at=None +-- rule_version id=292 rule_set_id=109 version_no=1.0 status=published oss_url=rules/contract.gift.general/1.0/rules.yaml sha=dd268a9c38d6143523f5afeddffeacaa3d396111d0eec987abe299ea9756df6b deleted_at=None +-- rule_version id=293 rule_set_id=110 version_no=2.0 status=published oss_url=rules/contract.lease/2.0/rules.yaml sha=5da7fe94f3fa7ead4c333fb7d273ba9e22ed7002ad5d7e9c6338418fcbad3c6b deleted_at=None +-- rule_version id=294 rule_set_id=111 version_no=1.0 status=published oss_url=rules/contract.loan.general/1.0/rules.yaml sha=426d0f66acaf1bac1f9bd18fda2187c2e2bffe5530aff8c76cc3cc4641892d23 deleted_at=None +-- rule_version id=295 rule_set_id=112 version_no=1.0 status=published oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=e60b679aa58958187d193d00097611c87d0bfd19f6b910f1d587fd95dd35826b deleted_at=None +-- rule_version id=296 rule_set_id=113 version_no=2.1 status=published oss_url=rules/contract.sale/2.1/rules.yaml sha=698b6df771cad864d3979ba4f5f134bf9bfe6af2f6b4aea85c8588c292ebbafc deleted_at=None +-- rule_version id=297 rule_set_id=124 version_no=1.0 status=published oss_url=rules/contract.tech/1.0/rules.yaml sha=c5ab06b6df9542715705aac6fbb0defcad35ae76ee9dea4125f57d0585b7d8ec deleted_at=None +-- rule_version id=298 rule_set_id=107 version_no=0.1 status=published oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=None +-- rule_version id=299 rule_set_id=114 version_no=1.0 status=published oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=f504ac3eccb0c148ca092d40b7bb2a87b95915b6e708395f4be00b8d337179de deleted_at=None +-- rule_version id=300 rule_set_id=115 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=None +-- rule_version id=301 rule_set_id=116 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=None +-- rule_version id=302 rule_set_id=117 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=None +-- rule_version id=303 rule_set_id=118 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=None +-- rule_version id=304 rule_set_id=119 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=None +-- rule_version id=305 rule_set_id=120 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=None +-- rule_version id=306 rule_set_id=121 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=None +-- rule_version id=307 rule_set_id=122 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=None +-- rule_version id=308 rule_set_id=123 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=None +-- rule_version id=309 rule_set_id=21 version_no=v2 status=draft oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=None +-- rule_version id=310 rule_set_id=21 version_no=v3 status=draft oss_url=rules/contract.construction.general/v3/rules.yaml sha=b8b03368e540e86bec8cff3dd3b07dacc3bded4dba0f07f25d9454b713f249b8 deleted_at=None +-- rule_version id=311 rule_set_id=21 version_no=1.2 status=published oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=None +-- rule_version id=312 rule_set_id=22 version_no=v2 status=draft oss_url=rules/contract.entrust/v2/rules.yaml sha=2d5490d16057697e60099d6cec33d51aaac2f748fc0038ae154ae2dd67f2d5a2 deleted_at=None +-- rule_version id=313 rule_set_id=22 version_no=v3 status=draft oss_url=rules/contract.entrust/v3/rules.yaml sha=18025aa0035f08f571775c2236d23ce0b6c23b0c32dd7f3ece8f97ffe81b4944 deleted_at=None +-- rule_version id=314 rule_set_id=22 version_no=v4 status=draft oss_url=rules/contract.entrust/v4/rules.yaml sha=212c1a3724b14dec4074c4e4ea62485c0b07ac5d772bce8e9a6108efe312ec05 deleted_at=None +-- rule_version id=315 rule_set_id=22 version_no=v5 status=draft oss_url=rules/contract.entrust/v5/rules.yaml sha=5424c16a3a35eb7c44131904a99e97203b41e02148b8eaba1df9782bc4309cfc deleted_at=None +-- rule_version id=316 rule_set_id=22 version_no=v6 status=draft oss_url=rules/contract.entrust/v6/rules.yaml sha=0ee30fc1e3fdaab53f47c4a8cc3479b89fabf1be821ee9b65cea9ea223a40d35 deleted_at=None +-- rule_version id=317 rule_set_id=22 version_no=v7 status=draft oss_url=rules/contract.entrust/v7/rules.yaml sha=75c817acc45e7661aab5171a987ffc9e602e79353488c89bc12fcb2f749791df deleted_at=None +-- rule_version id=318 rule_set_id=22 version_no=v8 status=draft oss_url=rules/contract.entrust/v8/rules.yaml sha=cae719d588766213e251b141d4b910388046883e135b173dae2c71888ebe4a0f deleted_at=None +-- rule_version id=319 rule_set_id=22 version_no=v9 status=draft oss_url=rules/contract.entrust/v9/rules.yaml sha=11f41652de07617382eaa68b401cdaa2f7ff2a46ac61a903e8b44cf240768784 deleted_at=None +-- rule_version id=320 rule_set_id=22 version_no=2.0 status=published oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=None +-- rule_version id=321 rule_set_id=23 version_no=0.1 status=published oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=f2159624c569d6f18f37afb8bf6fa7ea099cc2e019cbf8b3bf3a2ce25d54a727 deleted_at=None +-- rule_version id=322 rule_set_id=24 version_no=1.0 status=published oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=4f4c2b64cc84cf791be770b785986cc67f4ef864174f95d9ba7e4a84fdadc8cc deleted_at=None +-- rule_version id=323 rule_set_id=25 version_no=1.0 status=published oss_url=rules/contract.gift.general/1.0/rules.yaml sha=dd268a9c38d6143523f5afeddffeacaa3d396111d0eec987abe299ea9756df6b deleted_at=None +-- rule_version id=324 rule_set_id=26 version_no=2.0 status=published oss_url=rules/contract.lease/2.0/rules.yaml sha=5da7fe94f3fa7ead4c333fb7d273ba9e22ed7002ad5d7e9c6338418fcbad3c6b deleted_at=None +-- rule_version id=325 rule_set_id=27 version_no=1.0 status=published oss_url=rules/contract.loan.general/1.0/rules.yaml sha=426d0f66acaf1bac1f9bd18fda2187c2e2bffe5530aff8c76cc3cc4641892d23 deleted_at=None +-- rule_version id=326 rule_set_id=28 version_no=1.0 status=published oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=e60b679aa58958187d193d00097611c87d0bfd19f6b910f1d587fd95dd35826b deleted_at=None +-- rule_version id=327 rule_set_id=29 version_no=2.1 status=published oss_url=rules/contract.sale/2.1/rules.yaml sha=698b6df771cad864d3979ba4f5f134bf9bfe6af2f6b4aea85c8588c292ebbafc deleted_at=None +-- rule_version id=328 rule_set_id=30 version_no=1.0 status=published oss_url=rules/contract.tech/1.0/rules.yaml sha=c5ab06b6df9542715705aac6fbb0defcad35ae76ee9dea4125f57d0585b7d8ec deleted_at=None +-- rule_version id=329 rule_set_id=41 version_no=0.1 status=published oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=None +-- rule_version id=330 rule_set_id=31 version_no=1.0 status=published oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=f504ac3eccb0c148ca092d40b7bb2a87b95915b6e708395f4be00b8d337179de deleted_at=None +-- rule_version id=331 rule_set_id=32 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=None +-- rule_version id=332 rule_set_id=33 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=None +-- rule_version id=333 rule_set_id=34 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=None +-- rule_version id=334 rule_set_id=35 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=None +-- rule_version id=335 rule_set_id=36 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=None +-- rule_version id=336 rule_set_id=37 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=None +-- rule_version id=337 rule_set_id=38 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=None +-- rule_version id=338 rule_set_id=39 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=None +-- rule_version id=339 rule_set_id=40 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=None +-- rule_version id=340 rule_set_id=147 version_no=v2 status=draft oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=None +-- rule_version id=341 rule_set_id=147 version_no=v3 status=draft oss_url=rules/contract.construction.general/v3/rules.yaml sha=b8b03368e540e86bec8cff3dd3b07dacc3bded4dba0f07f25d9454b713f249b8 deleted_at=None +-- rule_version id=342 rule_set_id=147 version_no=1.2 status=published oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=None +-- rule_version id=343 rule_set_id=148 version_no=v2 status=draft oss_url=rules/contract.entrust/v2/rules.yaml sha=2d5490d16057697e60099d6cec33d51aaac2f748fc0038ae154ae2dd67f2d5a2 deleted_at=None +-- rule_version id=344 rule_set_id=148 version_no=v3 status=draft oss_url=rules/contract.entrust/v3/rules.yaml sha=18025aa0035f08f571775c2236d23ce0b6c23b0c32dd7f3ece8f97ffe81b4944 deleted_at=None +-- rule_version id=345 rule_set_id=148 version_no=v4 status=draft oss_url=rules/contract.entrust/v4/rules.yaml sha=212c1a3724b14dec4074c4e4ea62485c0b07ac5d772bce8e9a6108efe312ec05 deleted_at=None +-- rule_version id=346 rule_set_id=148 version_no=v5 status=draft oss_url=rules/contract.entrust/v5/rules.yaml sha=5424c16a3a35eb7c44131904a99e97203b41e02148b8eaba1df9782bc4309cfc deleted_at=None +-- rule_version id=347 rule_set_id=148 version_no=v6 status=draft oss_url=rules/contract.entrust/v6/rules.yaml sha=0ee30fc1e3fdaab53f47c4a8cc3479b89fabf1be821ee9b65cea9ea223a40d35 deleted_at=None +-- rule_version id=348 rule_set_id=148 version_no=v7 status=draft oss_url=rules/contract.entrust/v7/rules.yaml sha=75c817acc45e7661aab5171a987ffc9e602e79353488c89bc12fcb2f749791df deleted_at=None +-- rule_version id=349 rule_set_id=148 version_no=v8 status=draft oss_url=rules/contract.entrust/v8/rules.yaml sha=cae719d588766213e251b141d4b910388046883e135b173dae2c71888ebe4a0f deleted_at=None +-- rule_version id=350 rule_set_id=148 version_no=v9 status=draft oss_url=rules/contract.entrust/v9/rules.yaml sha=11f41652de07617382eaa68b401cdaa2f7ff2a46ac61a903e8b44cf240768784 deleted_at=None +-- rule_version id=351 rule_set_id=148 version_no=2.0 status=published oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=None +-- rule_version id=352 rule_set_id=146 version_no=0.1 status=published oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=f2159624c569d6f18f37afb8bf6fa7ea099cc2e019cbf8b3bf3a2ce25d54a727 deleted_at=None +-- rule_version id=353 rule_set_id=129 version_no=1.0 status=published oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=4f4c2b64cc84cf791be770b785986cc67f4ef864174f95d9ba7e4a84fdadc8cc deleted_at=None +-- rule_version id=354 rule_set_id=130 version_no=1.0 status=published oss_url=rules/contract.gift.general/1.0/rules.yaml sha=dd268a9c38d6143523f5afeddffeacaa3d396111d0eec987abe299ea9756df6b deleted_at=None +-- rule_version id=355 rule_set_id=131 version_no=2.0 status=published oss_url=rules/contract.lease/2.0/rules.yaml sha=5da7fe94f3fa7ead4c333fb7d273ba9e22ed7002ad5d7e9c6338418fcbad3c6b deleted_at=None +-- rule_version id=356 rule_set_id=132 version_no=1.0 status=published oss_url=rules/contract.loan.general/1.0/rules.yaml sha=426d0f66acaf1bac1f9bd18fda2187c2e2bffe5530aff8c76cc3cc4641892d23 deleted_at=None +-- rule_version id=357 rule_set_id=133 version_no=1.0 status=published oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=e60b679aa58958187d193d00097611c87d0bfd19f6b910f1d587fd95dd35826b deleted_at=None +-- rule_version id=358 rule_set_id=134 version_no=2.1 status=published oss_url=rules/contract.sale/2.1/rules.yaml sha=698b6df771cad864d3979ba4f5f134bf9bfe6af2f6b4aea85c8588c292ebbafc deleted_at=None +-- rule_version id=359 rule_set_id=145 version_no=1.0 status=published oss_url=rules/contract.tech/1.0/rules.yaml sha=c5ab06b6df9542715705aac6fbb0defcad35ae76ee9dea4125f57d0585b7d8ec deleted_at=None +-- rule_version id=360 rule_set_id=128 version_no=0.1 status=published oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=None +-- rule_version id=361 rule_set_id=135 version_no=1.0 status=published oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=f504ac3eccb0c148ca092d40b7bb2a87b95915b6e708395f4be00b8d337179de deleted_at=None +-- rule_version id=362 rule_set_id=136 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=None +-- rule_version id=363 rule_set_id=137 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=None +-- rule_version id=364 rule_set_id=138 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=None +-- rule_version id=365 rule_set_id=139 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=None +-- rule_version id=366 rule_set_id=140 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=None +-- rule_version id=367 rule_set_id=141 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=None +-- rule_version id=368 rule_set_id=142 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=None +-- rule_version id=369 rule_set_id=143 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=None +-- rule_version id=370 rule_set_id=144 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=None +-- rule_version id=371 rule_set_id=190 version_no=1.0 status=published oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=4f4c2b64cc84cf791be770b785986cc67f4ef864174f95d9ba7e4a84fdadc8cc deleted_at=None +-- rule_version id=372 rule_set_id=191 version_no=0.1 status=published oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=None +-- rule_version id=373 rule_set_id=192 version_no=v2 status=draft oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=None +-- rule_version id=374 rule_set_id=192 version_no=v3 status=draft oss_url=rules/contract.construction.general/v3/rules.yaml sha=b8b03368e540e86bec8cff3dd3b07dacc3bded4dba0f07f25d9454b713f249b8 deleted_at=None +-- rule_version id=375 rule_set_id=192 version_no=1.2 status=published oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=None +-- rule_version id=376 rule_set_id=193 version_no=v2 status=draft oss_url=rules/contract.entrust/v2/rules.yaml sha=2d5490d16057697e60099d6cec33d51aaac2f748fc0038ae154ae2dd67f2d5a2 deleted_at=None +-- rule_version id=377 rule_set_id=193 version_no=v3 status=draft oss_url=rules/contract.entrust/v3/rules.yaml sha=18025aa0035f08f571775c2236d23ce0b6c23b0c32dd7f3ece8f97ffe81b4944 deleted_at=None +-- rule_version id=378 rule_set_id=193 version_no=v4 status=draft oss_url=rules/contract.entrust/v4/rules.yaml sha=212c1a3724b14dec4074c4e4ea62485c0b07ac5d772bce8e9a6108efe312ec05 deleted_at=None +-- rule_version id=379 rule_set_id=193 version_no=v5 status=draft oss_url=rules/contract.entrust/v5/rules.yaml sha=5424c16a3a35eb7c44131904a99e97203b41e02148b8eaba1df9782bc4309cfc deleted_at=None +-- rule_version id=380 rule_set_id=193 version_no=v6 status=draft oss_url=rules/contract.entrust/v6/rules.yaml sha=0ee30fc1e3fdaab53f47c4a8cc3479b89fabf1be821ee9b65cea9ea223a40d35 deleted_at=None +-- rule_version id=381 rule_set_id=193 version_no=v7 status=draft oss_url=rules/contract.entrust/v7/rules.yaml sha=75c817acc45e7661aab5171a987ffc9e602e79353488c89bc12fcb2f749791df deleted_at=None +-- rule_version id=382 rule_set_id=193 version_no=v8 status=draft oss_url=rules/contract.entrust/v8/rules.yaml sha=cae719d588766213e251b141d4b910388046883e135b173dae2c71888ebe4a0f deleted_at=None +-- rule_version id=383 rule_set_id=193 version_no=v9 status=draft oss_url=rules/contract.entrust/v9/rules.yaml sha=11f41652de07617382eaa68b401cdaa2f7ff2a46ac61a903e8b44cf240768784 deleted_at=None +-- rule_version id=384 rule_set_id=193 version_no=2.0 status=published oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=None +-- rule_version id=385 rule_set_id=194 version_no=0.1 status=published oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=f2159624c569d6f18f37afb8bf6fa7ea099cc2e019cbf8b3bf3a2ce25d54a727 deleted_at=None +-- rule_version id=386 rule_set_id=195 version_no=1.0 status=published oss_url=rules/contract.gift.general/1.0/rules.yaml sha=dd268a9c38d6143523f5afeddffeacaa3d396111d0eec987abe299ea9756df6b deleted_at=None +-- rule_version id=387 rule_set_id=196 version_no=2.0 status=published oss_url=rules/contract.lease/2.0/rules.yaml sha=5da7fe94f3fa7ead4c333fb7d273ba9e22ed7002ad5d7e9c6338418fcbad3c6b deleted_at=None +-- rule_version id=388 rule_set_id=197 version_no=1.0 status=published oss_url=rules/contract.loan.general/1.0/rules.yaml sha=426d0f66acaf1bac1f9bd18fda2187c2e2bffe5530aff8c76cc3cc4641892d23 deleted_at=None +-- rule_version id=389 rule_set_id=198 version_no=1.0 status=published oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=e60b679aa58958187d193d00097611c87d0bfd19f6b910f1d587fd95dd35826b deleted_at=None +-- rule_version id=390 rule_set_id=199 version_no=2.1 status=published oss_url=rules/contract.sale/2.1/rules.yaml sha=698b6df771cad864d3979ba4f5f134bf9bfe6af2f6b4aea85c8588c292ebbafc deleted_at=None +-- rule_version id=391 rule_set_id=200 version_no=1.0 status=published oss_url=rules/contract.tech/1.0/rules.yaml sha=c5ab06b6df9542715705aac6fbb0defcad35ae76ee9dea4125f57d0585b7d8ec deleted_at=None +-- rule_version id=392 rule_set_id=201 version_no=1.0 status=published oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=f504ac3eccb0c148ca092d40b7bb2a87b95915b6e708395f4be00b8d337179de deleted_at=None +-- rule_version id=393 rule_set_id=202 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=None +-- rule_version id=394 rule_set_id=203 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=None +-- rule_version id=395 rule_set_id=204 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=None +-- rule_version id=396 rule_set_id=205 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=None +-- rule_version id=397 rule_set_id=206 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=None +-- rule_version id=398 rule_set_id=207 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=None +-- rule_version id=399 rule_set_id=208 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=None +-- rule_version id=400 rule_set_id=209 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=None +-- rule_version id=401 rule_set_id=210 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=None +-- rule_version id=402 rule_set_id=211 version_no=1.0 status=published oss_url=rules/contract.gift.charity/1.0/rules.yaml sha=4f4c2b64cc84cf791be770b785986cc67f4ef864174f95d9ba7e4a84fdadc8cc deleted_at=None +-- rule_version id=403 rule_set_id=212 version_no=0.1 status=published oss_url=rules/govdoc.general/0.1/rules.yaml sha=cd8c93b16641aeda9bd403b0159d6443a5a68fb1f602a2adbcdd63a92e5d3687 deleted_at=None +-- rule_version id=404 rule_set_id=213 version_no=v2 status=draft oss_url=rules/contract.construction.general/v2/rules.yaml sha=ec77babbfe200bd63a6101d273e6318cc5eb492b86ed4829c239e6508ff91534 deleted_at=None +-- rule_version id=405 rule_set_id=213 version_no=v3 status=draft oss_url=rules/contract.construction.general/v3/rules.yaml sha=b8b03368e540e86bec8cff3dd3b07dacc3bded4dba0f07f25d9454b713f249b8 deleted_at=None +-- rule_version id=406 rule_set_id=213 version_no=1.2 status=published oss_url=rules/contract.construction.general/1.2/rules.yaml sha=56117d4aaf76837a9b913560dc21cbb7d5c2469883685a41b7e153b059ef4592 deleted_at=None +-- rule_version id=407 rule_set_id=214 version_no=v2 status=draft oss_url=rules/contract.entrust/v2/rules.yaml sha=2d5490d16057697e60099d6cec33d51aaac2f748fc0038ae154ae2dd67f2d5a2 deleted_at=None +-- rule_version id=408 rule_set_id=214 version_no=v3 status=draft oss_url=rules/contract.entrust/v3/rules.yaml sha=18025aa0035f08f571775c2236d23ce0b6c23b0c32dd7f3ece8f97ffe81b4944 deleted_at=None +-- rule_version id=409 rule_set_id=214 version_no=v4 status=draft oss_url=rules/contract.entrust/v4/rules.yaml sha=212c1a3724b14dec4074c4e4ea62485c0b07ac5d772bce8e9a6108efe312ec05 deleted_at=None +-- rule_version id=410 rule_set_id=214 version_no=v5 status=draft oss_url=rules/contract.entrust/v5/rules.yaml sha=5424c16a3a35eb7c44131904a99e97203b41e02148b8eaba1df9782bc4309cfc deleted_at=None +-- rule_version id=411 rule_set_id=214 version_no=v6 status=draft oss_url=rules/contract.entrust/v6/rules.yaml sha=0ee30fc1e3fdaab53f47c4a8cc3479b89fabf1be821ee9b65cea9ea223a40d35 deleted_at=None +-- rule_version id=412 rule_set_id=214 version_no=v7 status=draft oss_url=rules/contract.entrust/v7/rules.yaml sha=75c817acc45e7661aab5171a987ffc9e602e79353488c89bc12fcb2f749791df deleted_at=None +-- rule_version id=413 rule_set_id=214 version_no=v8 status=draft oss_url=rules/contract.entrust/v8/rules.yaml sha=cae719d588766213e251b141d4b910388046883e135b173dae2c71888ebe4a0f deleted_at=None +-- rule_version id=414 rule_set_id=214 version_no=v9 status=draft oss_url=rules/contract.entrust/v9/rules.yaml sha=11f41652de07617382eaa68b401cdaa2f7ff2a46ac61a903e8b44cf240768784 deleted_at=None +-- rule_version id=415 rule_set_id=214 version_no=2.0 status=published oss_url=rules/contract.entrust/2.0/rules.yaml sha=5b4653d245ef0c1897bc366baa4d18e0c5ae28badd1175c04c3841c0a4b06a17 deleted_at=None +-- rule_version id=416 rule_set_id=215 version_no=0.1 status=published oss_url=rules/contract.evaluation.delegation/0.1/rules.yaml sha=f2159624c569d6f18f37afb8bf6fa7ea099cc2e019cbf8b3bf3a2ce25d54a727 deleted_at=None +-- rule_version id=417 rule_set_id=216 version_no=1.0 status=published oss_url=rules/contract.gift.general/1.0/rules.yaml sha=dd268a9c38d6143523f5afeddffeacaa3d396111d0eec987abe299ea9756df6b deleted_at=None +-- rule_version id=418 rule_set_id=217 version_no=2.0 status=published oss_url=rules/contract.lease/2.0/rules.yaml sha=5da7fe94f3fa7ead4c333fb7d273ba9e22ed7002ad5d7e9c6338418fcbad3c6b deleted_at=None +-- rule_version id=419 rule_set_id=218 version_no=1.0 status=published oss_url=rules/contract.loan.general/1.0/rules.yaml sha=426d0f66acaf1bac1f9bd18fda2187c2e2bffe5530aff8c76cc3cc4641892d23 deleted_at=None +-- rule_version id=420 rule_set_id=219 version_no=1.0 status=published oss_url=rules/contract.purchase.general/1.0/rules.yaml sha=e60b679aa58958187d193d00097611c87d0bfd19f6b910f1d587fd95dd35826b deleted_at=None +-- rule_version id=421 rule_set_id=220 version_no=2.1 status=published oss_url=rules/contract.sale/2.1/rules.yaml sha=698b6df771cad864d3979ba4f5f134bf9bfe6af2f6b4aea85c8588c292ebbafc deleted_at=None +-- rule_version id=422 rule_set_id=221 version_no=1.0 status=published oss_url=rules/contract.tech/1.0/rules.yaml sha=c5ab06b6df9542715705aac6fbb0defcad35ae76ee9dea4125f57d0585b7d8ec deleted_at=None +-- rule_version id=423 rule_set_id=222 version_no=1.0 status=published oss_url=rules/行政卷宗.行政处罚/1.0/rules.yaml sha=f504ac3eccb0c148ca092d40b7bb2a87b95915b6e708395f4be00b8d337179de deleted_at=None +-- rule_version id=424 rule_set_id=223 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.停业/1.0/rules.yaml sha=b14ccd9f276943a6159355226f08934263bc95c6fa514d51a91a2d4ac1a02333 deleted_at=None +-- rule_version id=425 rule_set_id=224 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.变更/1.0/rules.yaml sha=44bf8354cbe556d03ea89e0568e0db955675fe7ade3cc1f712779195c26d67eb deleted_at=None +-- rule_version id=426 rule_set_id=225 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.延续/1.0/rules.yaml sha=2b1daa8b579f7ce8cb809dcda2035d46f82d75363357573a4cd3526ac20549fb deleted_at=None +-- rule_version id=427 rule_set_id=226 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.恢复营业/1.0/rules.yaml sha=f175e1fa7a4ec1b24991a8d628085bb1fbc40a60012a91d1c2364fd4638b8a4d deleted_at=None +-- rule_version id=428 rule_set_id=227 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.收回/1.0/rules.yaml sha=f0d1b780ef136356c51132d9d1da1591c89ad2a201a80d2dc533e76aebc34155 deleted_at=None +-- rule_version id=429 rule_set_id=228 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.新办/1.0/rules.yaml sha=89cdbcfd13549f3e5b8d7402d2a46bfcd05a8108b3193210ae176f3aeb3ab79e deleted_at=None +-- rule_version id=430 rule_set_id=229 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.歇业/1.0/rules.yaml sha=cb3f1ca8e5386176a2d9c334720dee0570626644a8fb5ebb281ebd92fc2b77a9 deleted_at=None +-- rule_version id=431 rule_set_id=230 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.注销/1.0/rules.yaml sha=3e07da60e91a20168ba39ca906b9d8b1b9d1fa1841c40d4a63563ef9660e7906 deleted_at=None +-- rule_version id=432 rule_set_id=231 version_no=1.0 status=published oss_url=rules/行政卷宗.行政许可.补办/1.0/rules.yaml sha=56af0893c1d458bbd42eb19b9e76d5394350429a580c9fecf79a66d2d78ca1ed deleted_at=None +-- rule_version id=433 rule_set_id=127 version_no=pytest-vm-1779371496 status=rollback oss_url=rules/contract.entrust/pytest-vm-1779371496/rules.yaml sha=6bb8ef6ffec27b920c9f713243e85fd19f858c3cb1b6a3216819a5c167a7bfaf deleted_at=None +-- rule_version id=434 rule_set_id=127 version_no=pytest-vm-1779371996 status=rollback oss_url=rules/contract.entrust/pytest-vm-1779371996/rules.yaml sha=4ba3e8f648ffd28d18a97d52c1c826d2ea6a6ef582c15e4e1f2bc1f1fb31e2e5 deleted_at=None +-- rule_version id=435 rule_set_id=127 version_no=pytest-vm-1779372327 status=rollback oss_url=rules/contract.entrust/pytest-vm-1779372327/rules.yaml sha=82efcd120f121d7ba6f9c6c3b310d3431ff69372e328de30c3ec73ad77b8d0cf deleted_at=None diff --git a/fastapi_admin/config/__init__.py b/fastapi_admin/config/__init__.py index 9ded1f5..9864259 100644 --- a/fastapi_admin/config/__init__.py +++ b/fastapi_admin/config/__init__.py @@ -13,7 +13,7 @@ from ._loader import load_config as _load_config # 优先加载 TOML → os.environ(必须在 Settings 实例化之前) _load_config() -from ._settings import app, jwt, db, redis, oss, llm, vlm, embedding, ocr, leaudit as _leaudit # noqa: E402 +from ._settings import app, jwt, db, redis, oss, llm, vlm, embedding, ocr, leaudit as _leaudit, qichacha # noqa: E402 def _export_settings(instance: object, prefix: str = "") -> dict[str, object]: @@ -51,6 +51,7 @@ _VLM = _export_settings(vlm) _EMBEDDING = _export_settings(embedding) _OCR = _export_settings(ocr) _LEAUDIT = _export_settings(_leaudit) +_QICHACHA = _export_settings(qichacha) # 将所有变量注入当前模块的全局命名空间 _ALL = {} @@ -64,6 +65,7 @@ _ALL.update(_VLM) _ALL.update(_EMBEDDING) _ALL.update(_OCR) _ALL.update(_LEAUDIT) +_ALL.update(_QICHACHA) globals().update(_ALL) diff --git a/fastapi_admin/config/_settings.py b/fastapi_admin/config/_settings.py index 06dcb47..c637439 100644 --- a/fastapi_admin/config/_settings.py +++ b/fastapi_admin/config/_settings.py @@ -130,6 +130,30 @@ class LeauditSettings(_Base): LEAUDIT_PAGE_QUALITY_QUEUE_URGENT: str = "leaudit.page_quality.urgent" +class QichachaSettings(_Base): + """企查查配置 [QICHACHA]。""" + + QICHACHA_APP_KEY: str = "" + QICHACHA_SECRET_KEY: str = "" + QICHACHA_BASE_URL: str = "https://api.qichacha.com" + QICHACHA_ENTERPRISE_PATH: str = "/ECIV4/GetBasicDetailsByName" + QICHACHA_DISHONESTY_PATH: str = "/ShixinCheck/GetList" + QICHACHA_TIMEOUT: int = 30 + QICHACHA_MAX_RETRIES: int = 3 + QICHACHA_RETRY_DELAY: float = 1.0 + QICHACHA_CACHE_DAYS: int = 30 + + @property + def QICHACHA_ENTERPRISE_URL(self) -> str: + """企查查工商信息接口完整 URL。""" + return f"{self.QICHACHA_BASE_URL.rstrip('/')}{self.QICHACHA_ENTERPRISE_PATH}" + + @property + def QICHACHA_DISHONESTY_URL(self) -> str: + """企查查失信核查接口完整 URL。""" + return f"{self.QICHACHA_BASE_URL.rstrip('/')}{self.QICHACHA_DISHONESTY_PATH}" + + # 实例化所有 Settings app = AppSettings() jwt = JwtSettings() @@ -141,3 +165,4 @@ vlm = VlmSettings() embedding = EmbeddingSettings() ocr = OcrSettings() leaudit = LeauditSettings() +qichacha = QichachaSettings() diff --git a/fastapi_common/fastapi_common_web/exception/QichachaException.py b/fastapi_common/fastapi_common_web/exception/QichachaException.py new file mode 100644 index 0000000..83b62d7 --- /dev/null +++ b/fastapi_common/fastapi_common_web/exception/QichachaException.py @@ -0,0 +1,7 @@ +"""企查查模块异常。""" + +from fastapi_common.fastapi_common_web.exception.Base.BusinessException import BusinessException + + +class QichachaException(BusinessException): + """企查查模块业务异常。""" diff --git a/fastapi_modules/fastapi_leaudit/controllers/crossReviewController.py b/fastapi_modules/fastapi_leaudit/controllers/crossReviewController.py index fa40042..29e231b 100644 --- a/fastapi_modules/fastapi_leaudit/controllers/crossReviewController.py +++ b/fastapi_modules/fastapi_leaudit/controllers/crossReviewController.py @@ -76,7 +76,9 @@ class CrossReviewController(BaseController): """查询当前用户参与的交叉评查任务。""" if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["task_read"]]): return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有查看交叉评查任务权限", "data": None}) - Data = await self.CrossReviewService.GetUserTasks(CurrentUserId=int(payload["user_id"]), Body=Body) + UserId = int(payload["user_id"]) + CanViewProgress = await self._check_permission(UserId, [self._PERMISSIONS["progress_view"]]) + Data = await self.CrossReviewService.GetUserTasks(CurrentUserId=UserId, Body=Body, CanViewProgress=CanViewProgress) return Result.success(data=Data) @self.router.get("/tasks/{TaskId}/progress", response_model=Result[CrossReviewTaskProgressVO]) diff --git a/fastapi_modules/fastapi_leaudit/controllers/documentController.py b/fastapi_modules/fastapi_leaudit/controllers/documentController.py index 82088fb..41e8d08 100644 --- a/fastapi_modules/fastapi_leaudit/controllers/documentController.py +++ b/fastapi_modules/fastapi_leaudit/controllers/documentController.py @@ -4,6 +4,7 @@ import json from typing import Any from fastapi import Depends, File, Form, Query, UploadFile +from fastapi.responses import JSONResponse from pydantic import BaseModel, Field from sqlalchemy import text @@ -34,6 +35,8 @@ from fastapi_modules.fastapi_leaudit.domian.vo.reviewPointVo import ( ) from fastapi_modules.fastapi_leaudit.services import IDocumentService from fastapi_modules.fastapi_leaudit.services.impl.documentServiceImpl import DocumentServiceImpl +from fastapi_modules.fastapi_leaudit.services.impl.permissionServiceImpl import PermissionServiceImpl +from fastapi_modules.fastapi_leaudit.services.permissionService import IPermissionService class QueueStatusVO(BaseModel): @@ -51,6 +54,8 @@ class ReviewPointAuditDTO(BaseModel): class DocumentController(BaseController): """文档控制器。""" + _CROSS_REVIEW_DOCUMENT_READ_PERMISSION = "cross_review:document:read" + @staticmethod def _tenant_context(payload: dict[str, Any]) -> dict[str, str | None]: return { @@ -61,6 +66,7 @@ class DocumentController(BaseController): def __init__(self): super().__init__(prefix="", tags=["文档"]) self.DocumentService: IDocumentService = DocumentServiceImpl() + self.PermissionService: IPermissionService = PermissionServiceImpl() @self.router.post("/upload", response_model=Result[DocumentUploadVO]) async def UploadDocument( @@ -69,6 +75,8 @@ class DocumentController(BaseController): typeId: int | None = Form(None, description="文档类型ID"), typeCode: str | None = Form(None, description="文档类型编码"), groupId: int | None = Form(None, description="二级分组ID"), + entryModuleId: int | None = Form(None, description="入口模块ID"), + entry_module_id: int | None = Form(None, description="入口模块ID,兼容蛇形字段"), region: str | None = Form(None, description="所属租户/地区"), tenant_code: str | None = Form(None, description="租户编码"), fileRole: str = Form("primary", description="文件角色"), @@ -97,6 +105,7 @@ class DocumentController(BaseController): TypeId=typeId, TypeCode=typeCode, GroupId=groupId, + EntryModuleId=entryModuleId or entry_module_id, Region=region, FileRole=fileRole, CreatedBy=int(payload["user_id"]), @@ -194,7 +203,11 @@ class DocumentController(BaseController): payload: dict[str, Any] = Depends(verify_access_token), ): """获取单个文档详情(带数据隔离校验)。""" - Data = await self.DocumentService.GetDocument(CurrentUserId=int(payload["user_id"]), Id=DocumentId) + userId = int(payload["user_id"]) + deniedResponse = await self._deny_cross_review_document_without_permission(userId, DocumentId) + if deniedResponse: + return deniedResponse + Data = await self.DocumentService.GetDocument(CurrentUserId=userId, Id=DocumentId) return Result.success(data=Data) @self.router.get("/v3/review-points/{DocumentId}", response_model=Result[ReviewPointsAggregateVO]) @@ -203,7 +216,11 @@ class DocumentController(BaseController): payload: dict[str, Any] = Depends(verify_access_token), ): """获取评查详情页聚合数据(带数据隔离校验)。""" - Data = await self.DocumentService.GetReviewPoints(CurrentUserId=int(payload["user_id"]), DocumentId=DocumentId) + userId = int(payload["user_id"]) + deniedResponse = await self._deny_cross_review_document_without_permission(userId, DocumentId) + if deniedResponse: + return deniedResponse + Data = await self.DocumentService.GetReviewPoints(CurrentUserId=userId, DocumentId=DocumentId) return Result.success(data=Data) @self.router.patch("/v3/review-points/{ReviewPointResultId}/audit", response_model=Result[ReviewPointAuditVO]) @@ -400,3 +417,17 @@ class DocumentController(BaseController): }, ) ) + + async def _deny_cross_review_document_without_permission(self, UserId: int, DocumentId: int) -> JSONResponse | None: + if not await self.DocumentService.IsCrossReviewDocument(DocumentId): + return None + hasPermission = await self.PermissionService.HasAnyPermission( + UserId=UserId, + PermissionKeys=[self._CROSS_REVIEW_DOCUMENT_READ_PERMISSION], + ) + if hasPermission: + return None + return JSONResponse( + status_code=403, + content={"code": 403, "msg": "当前用户没有查看交叉评查结果权限", "data": None}, + ) diff --git a/fastapi_modules/fastapi_leaudit/controllers/evaluationPointController.py b/fastapi_modules/fastapi_leaudit/controllers/evaluationPointController.py index 41d0d61..5adfa9f 100644 --- a/fastapi_modules/fastapi_leaudit/controllers/evaluationPointController.py +++ b/fastapi_modules/fastapi_leaudit/controllers/evaluationPointController.py @@ -1,155 +1,40 @@ -"""评查点控制器。""" +"""Legacy evaluation point API. + +The current rule configuration flow is backed by leaudit_evaluation_point_groups +and rule version tables. The old evaluation_points table is no longer present +in the active schema, so keep this route explicit and non-destructive. +""" -from fastapi import Depends, Query from fastapi.responses import JSONResponse -from fastapi_common.fastapi_common_security.security import verify_access_token from fastapi_common.fastapi_common_web.controller import BaseController -from fastapi_modules.fastapi_leaudit.domian.Dto.evaluationPointDto import ( - EvaluationPointCreateDTO, - EvaluationPointUpdateDTO, -) -from fastapi_modules.fastapi_leaudit.services.evaluationPointService import IEvaluationPointService -from fastapi_modules.fastapi_leaudit.services.impl.evaluationPointServiceImpl import EvaluationPointServiceImpl -from fastapi_modules.fastapi_leaudit.services.impl.permissionServiceImpl import PermissionServiceImpl -from fastapi_modules.fastapi_leaudit.services.permissionService import IPermissionService class EvaluationPointController(BaseController): - """评查点控制器。""" - - _PERMISSIONS = { - "list": "evaluation_point:list:read", - "detail": "evaluation_point:detail:read", - "create": "evaluation_point:create:write", - "update": "evaluation_point:update:write", - "delete": "evaluation_point:delete:delete", - } - - @staticmethod - def _tenant_context(payload: dict) -> dict[str, str | None]: - return { - "UserArea": payload.get("area"), - "UserRole": payload.get("user_role"), - "TenantCode": payload.get("tenant_code"), - "TenantName": payload.get("tenant_name"), - } + """Deprecated legacy evaluation point controller.""" def __init__(self): - super().__init__(prefix="/v3/evaluation-points", tags=["评查点"]) - self.PointService: IEvaluationPointService = EvaluationPointServiceImpl() - self.PermissionService: IPermissionService = PermissionServiceImpl() + super().__init__(prefix="/v3/evaluation-points", tags=["旧评查点接口"]) - @self.router.get("") - async def ListEvaluationPoints( - name: str | None = Query(None, description="名称模糊搜索"), - code: str | None = Query(None, description="编码模糊搜索"), - risk: str | None = Query(None, description="风险等级"), - is_enabled: bool | None = Query(None, description="是否启用"), - evaluation_point_groups_pid: int | None = Query(None, description="一级分组ID"), - evaluation_point_groups_id: int | None = Query(None, description="二级分组ID"), - document_attribute_type: str | None = Query(None, description="文档属性类型"), - area: str | None = Query(None, description="地区/兼容租户展示值"), - tenant_code: str | None = Query(None, description="租户编码"), - tenant_name: str | None = Query(None, description="租户名称(兼容筛选)"), - page: int = Query(1, ge=1, description="页码"), - page_size: int = Query(20, ge=1, le=500, description="分页大小"), - payload: dict = Depends(verify_access_token), - ): - if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["list"]]): - return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有评查点查看权限", "data": None}) - tenant_context = self._tenant_context(payload) - data = await self.PointService.ListPoints( - int(payload["user_id"]), - tenant_context["UserArea"], - tenant_context["UserRole"], - tenant_context["TenantCode"], - tenant_context["TenantName"], - name, - code, - risk, - is_enabled, - evaluation_point_groups_pid, - evaluation_point_groups_id, - document_attribute_type, - area, - tenant_code, - tenant_name, - page, - page_size, - ) - return JSONResponse(status_code=200, content=data.model_dump()) + @self.router.api_route("", methods=["GET", "POST"]) + async def LegacyEvaluationPointsRoot(): + return self._gone() - @self.router.get("/attribute-types") - async def GetEvaluationPointAttributeTypes(payload: dict = Depends(verify_access_token)): - if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["list"]]): - return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有评查点查看权限", "data": None}) - data = await self.PointService.GetAttributeTypes() - return JSONResponse(status_code=200, content=data.model_dump()) + @self.router.api_route("/attribute-types", methods=["GET"]) + async def LegacyEvaluationPointAttributeTypes(): + return self._gone() - @self.router.get("/{PointId}") - async def GetEvaluationPoint(PointId: int, payload: dict = Depends(verify_access_token)): - if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["detail"]]): - return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有评查点查看权限", "data": None}) - tenant_context = self._tenant_context(payload) - data = await self.PointService.GetPoint( - int(payload["user_id"]), - tenant_context["UserArea"], - tenant_context["UserRole"], - tenant_context["TenantCode"], - tenant_context["TenantName"], - PointId, - ) - return JSONResponse(status_code=200, content=data.model_dump()) + @self.router.api_route("/{PointId}", methods=["GET", "PUT", "DELETE"]) + async def LegacyEvaluationPointDetail(PointId: int): + return self._gone() - @self.router.post("") - async def CreateEvaluationPoint(body: EvaluationPointCreateDTO, payload: dict = Depends(verify_access_token)): - if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["create"]]): - return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有创建评查点权限", "data": None}) - tenant_context = self._tenant_context(payload) - data = await self.PointService.CreatePoint( - int(payload["user_id"]), - tenant_context["UserArea"], - tenant_context["UserRole"], - tenant_context["TenantCode"], - tenant_context["TenantName"], - body, - ) - return JSONResponse(status_code=200, content=data.model_dump()) - - @self.router.put("/{PointId}") - async def UpdateEvaluationPoint(PointId: int, body: EvaluationPointUpdateDTO, payload: dict = Depends(verify_access_token)): - if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["update"]]): - return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有更新评查点权限", "data": None}) - tenant_context = self._tenant_context(payload) - data = await self.PointService.UpdatePoint( - int(payload["user_id"]), - tenant_context["UserArea"], - tenant_context["UserRole"], - tenant_context["TenantCode"], - tenant_context["TenantName"], - PointId, - body, - ) - return JSONResponse(status_code=200, content=data.model_dump()) - - @self.router.delete("/{PointId}") - async def DeleteEvaluationPoint(PointId: int, payload: dict = Depends(verify_access_token)): - if not await self._check_permission(int(payload["user_id"]), [self._PERMISSIONS["delete"]]): - return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有删除评查点权限", "data": None}) - tenant_context = self._tenant_context(payload) - data = await self.PointService.DeletePoint( - int(payload["user_id"]), - tenant_context["UserArea"], - tenant_context["UserRole"], - tenant_context["TenantCode"], - tenant_context["TenantName"], - PointId, - ) - return JSONResponse(status_code=200, content=data.model_dump()) - - async def _check_permission(self, user_id: int, permission_keys: list[str]) -> bool: - for permission_key in permission_keys: - if await self.PermissionService.CheckPermission(user_id, permission_key): - return True - return False + @staticmethod + def _gone() -> JSONResponse: + return JSONResponse( + status_code=410, + content={ + "code": 410, + "msg": "旧评查点接口已下线,请使用规则组/规则配置接口", + "data": None, + }, + ) diff --git a/fastapi_modules/fastapi_leaudit/controllers/evaluationPointGroupController.py b/fastapi_modules/fastapi_leaudit/controllers/evaluationPointGroupController.py index c28eae0..c9605ed 100644 --- a/fastapi_modules/fastapi_leaudit/controllers/evaluationPointGroupController.py +++ b/fastapi_modules/fastapi_leaudit/controllers/evaluationPointGroupController.py @@ -35,24 +35,26 @@ class EvaluationPointGroupController(BaseController): code: str | None = Query(None, description="分组编码模糊搜索"), is_enabled: bool | None = Query(None, description="是否启用"), pid: int | None = Query(None, description="父分组ID,0 表示一级分组"), + entry_module_id: int | None = Query(None, description="入口模块ID"), page: int = Query(1, ge=1, description="页码"), page_size: int = Query(20, ge=1, le=500, description="分页大小"), payload: dict = Depends(verify_access_token), ): if not await self._check_permission(int(payload["user_id"]), ["evaluation_group:list:read", "rules:list:read"]): return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有评查点分组查看权限", "data": None}) - data = await self.GroupService.ListGroups(name, code, is_enabled, pid, page, page_size, int(payload["user_id"])) + data = await self.GroupService.ListGroups(name, code, is_enabled, pid, page, page_size, int(payload["user_id"]), entry_module_id) return JSONResponse(status_code=200, content=data.model_dump()) @self.router.get("/all") async def ListAllEvaluationPointGroups( include_disabled: bool = Query(False, description="是否包含禁用分组"), with_rule_count: bool = Query(True, description="是否返回评查点数"), + entry_module_id: int | None = Query(None, description="入口模块ID"), payload: dict = Depends(verify_access_token), ): if not await self._check_permission(int(payload["user_id"]), ["evaluation_group:list:read", "rules:list:read"]): return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有评查点分组查看权限", "data": None}) - data = await self.GroupService.ListAllGroups(include_disabled, with_rule_count, int(payload["user_id"])) + data = await self.GroupService.ListAllGroups(include_disabled, with_rule_count, int(payload["user_id"]), entry_module_id) return JSONResponse(status_code=200, content=[item.model_dump() for item in data]) @self.router.get("/by-document-types") diff --git a/fastapi_modules/fastapi_leaudit/controllers/govdocController.py b/fastapi_modules/fastapi_leaudit/controllers/govdocController.py index 15663c8..6b642ad 100644 --- a/fastapi_modules/fastapi_leaudit/controllers/govdocController.py +++ b/fastapi_modules/fastapi_leaudit/controllers/govdocController.py @@ -8,6 +8,7 @@ from __future__ import annotations from typing import Any from fastapi import Depends, File, Form, Query, UploadFile +from fastapi.responses import JSONResponse from fastapi_common.fastapi_common_security.security import verify_access_token from fastapi_common.fastapi_common_web.controller import BaseController @@ -15,6 +16,8 @@ from fastapi_common.fastapi_common_web.domain.responses import Result from fastapi_modules.fastapi_leaudit.services import IGovdocService from fastapi_modules.fastapi_leaudit.services.impl.govdocServiceImpl import GovdocServiceImpl +from fastapi_modules.fastapi_leaudit.services.impl.permissionServiceImpl import PermissionServiceImpl +from fastapi_modules.fastapi_leaudit.services.permissionService import IPermissionService class GovdocController(BaseController): @@ -23,6 +26,7 @@ class GovdocController(BaseController): def __init__(self): super().__init__(prefix="/govdoc", tags=["内部公文"]) self.GovdocService: IGovdocService = GovdocServiceImpl() + self.PermissionService: IPermissionService = PermissionServiceImpl() # ── 文档 ────────────────────────────────────────── @@ -30,6 +34,7 @@ class GovdocController(BaseController): async def UploadDocument( file: UploadFile = File(...), typeId: int | None = Form(default=None), + entry_module_id: int | None = Form(default=None, description="入口模块ID"), region: str | None = Form(default=None, description="兼容保留字段:租户展示值/旧地区"), tenant_code: str | None = Form(default=None, description="租户编码"), autoRun: bool = Form(default=True), @@ -41,9 +46,12 @@ class GovdocController(BaseController): 创建文档主档记录,engine_type 标记为 govdoc,可选自动触发审查。 """ + if not await self.PermissionService.CheckPermission(int(payload["user_id"]), "govdoc:document:create"): + return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有上传公文权限", "data": None}) result = await self.GovdocService.UploadDocument( file=file, typeId=typeId, + entryModuleId=entry_module_id, region=region, tenantCode=tenant_code, autoRun=autoRun, @@ -61,6 +69,9 @@ class GovdocController(BaseController): fileExt: str | None = Query(default=None), region: str | None = Query(default=None, description="兼容保留字段:租户展示值/旧地区"), tenant_code: str | None = Query(default=None, description="租户编码"), + entry_module_id: int | None = Query(default=None, description="按入口模块ID过滤"), + type_ids: str | None = Query(default=None, description="按文档类型ID列表过滤,逗号分隔"), + document_type_id: int | None = Query(default=None, description="按单个文档类型ID过滤"), status: str | None = Query(default=None), resultStatus: str | None = Query(default=None), createdBy: int | None = Query(default=None), @@ -72,6 +83,18 @@ class GovdocController(BaseController): 后端自动附加 engine_type='govdoc' 过滤条件。 """ + if not await self.PermissionService.CheckPermission(int(payload["user_id"]), "govdoc:document:read"): + return JSONResponse(status_code=403, content={"code": 403, "msg": "当前用户没有查看公文列表权限", "data": None}) + typeIdList: list[int] = [] + if type_ids: + typeIdList = [ + int(item.strip()) + for item in type_ids.split(",") + if item.strip().isdigit() and int(item.strip()) > 0 + ] + if document_type_id is not None and document_type_id > 0: + typeIdList.append(document_type_id) + result = await self.GovdocService.ListDocuments( page=page, pageSize=pageSize, @@ -79,6 +102,8 @@ class GovdocController(BaseController): fileExt=fileExt, region=region, tenantCode=tenant_code, + entryModuleId=entry_module_id, + typeIds=typeIdList or None, status=status, resultStatus=resultStatus, createdBy=createdBy, diff --git a/fastapi_modules/fastapi_leaudit/controllers/homeController.py b/fastapi_modules/fastapi_leaudit/controllers/homeController.py index 4b1ef42..4a2fe0b 100644 --- a/fastapi_modules/fastapi_leaudit/controllers/homeController.py +++ b/fastapi_modules/fastapi_leaudit/controllers/homeController.py @@ -2,13 +2,13 @@ from typing import Any -from fastapi import Depends +from fastapi import Depends, Query from fastapi_common.fastapi_common_security.security import verify_access_token from fastapi_common.fastapi_common_web.controller import BaseController from fastapi_common.fastapi_common_web.domain.responses import Result -from fastapi_modules.fastapi_leaudit.domian.vo.homeVo import HomeEntryModuleVO +from fastapi_modules.fastapi_leaudit.domian.vo.homeVo import HomeDashboardStatisticsVO, HomeEntryModuleVO from fastapi_modules.fastapi_leaudit.services import IHomeService from fastapi_modules.fastapi_leaudit.services.impl.homeServiceImpl import HomeServiceImpl @@ -25,3 +25,24 @@ class HomeController(BaseController): """获取当前用户可见的首页入口模块。""" Data = await self.HomeService.GetEntryModules(UserId=int(payload["user_id"])) return Result.success(data=Data) + + @self.router.get("/statistics/dashboard", response_model=Result[HomeDashboardStatisticsVO]) + async def GetDashboardStatistics( + today: str | None = Query(None, description="统计基准日期,格式 YYYY-MM-DD"), + type_ids: str | None = Query(None, description="文档类型ID,逗号分隔"), + entry_module_id: int | None = Query(None, description="入口模块ID"), + payload: dict[str, Any] = Depends(verify_access_token), + ): + """获取首页统计卡片数据。""" + typeIds = [ + int(item) + for item in str(type_ids or "").split(",") + if item.strip().isdigit() and int(item.strip()) > 0 + ] + Data = await self.HomeService.GetDashboardStatistics( + UserId=int(payload["user_id"]), + Today=today, + TypeIds=typeIds, + EntryModuleId=entry_module_id, + ) + return Result.success(data=Data) diff --git a/fastapi_modules/fastapi_leaudit/controllers/qichachaController.py b/fastapi_modules/fastapi_leaudit/controllers/qichachaController.py new file mode 100644 index 0000000..d2fe306 --- /dev/null +++ b/fastapi_modules/fastapi_leaudit/controllers/qichachaController.py @@ -0,0 +1,99 @@ +"""企查查控制器。""" + +from typing import Any + +from fastapi import Depends, Query +from fastapi.responses import JSONResponse + +from fastapi_common.fastapi_common_security.security import verify_access_token +from fastapi_common.fastapi_common_web.controller import BaseController +from fastapi_common.fastapi_common_web.domain.responses import Result +from fastapi_modules.fastapi_leaudit.domian.Dto.qichachaDto import QichachaBatchQueryDTO, QichachaCompanyQueryDTO +from fastapi_modules.fastapi_leaudit.domian.vo.qichachaVo import ( + QichachaBatchQueryVO, + QichachaCompanyQueryVO, + QichachaRecordStatusVO, +) +from fastapi_modules.fastapi_leaudit.services.impl.permissionServiceImpl import PermissionServiceImpl +from fastapi_modules.fastapi_leaudit.services.impl.qichachaServiceImpl import QichachaServiceImpl +from fastapi_modules.fastapi_leaudit.services.permissionService import IPermissionService +from fastapi_modules.fastapi_leaudit.services.qichachaService import IQichachaService + + +class QichachaController(BaseController): + """企查查控制器。""" + + def __init__(self): + super().__init__(prefix="/v2/qichacha", tags=["企查查"]) + self.QichachaService: IQichachaService = QichachaServiceImpl() + self.PermissionService: IPermissionService = PermissionServiceImpl() + + @self.router.post("/company", response_model=Result[QichachaCompanyQueryVO]) + async def QueryCompany(Body: QichachaCompanyQueryDTO, payload: dict[str, Any] = Depends(verify_access_token)): + """查询企业完整信息。""" + if not await self.PermissionService.CheckPermission(int(payload["user_id"]), "qichacha:company:query"): + return JSONResponse( + status_code=403, + content={"code": 403, "message": "当前用户没有查询企业信息权限", "data": None}, + ) + data = await self.QichachaService.QueryCompany( + Keyword=Body.keyword, + ForceRefresh=Body.forceRefresh, + ) + return Result.success(data=data) + + @self.router.post("/enterprise", response_model=Result[QichachaCompanyQueryVO]) + async def QueryEnterprise(Body: QichachaCompanyQueryDTO, payload: dict[str, Any] = Depends(verify_access_token)): + """仅查询企业工商信息。""" + if not await self.PermissionService.CheckPermission(int(payload["user_id"]), "qichacha:company:query"): + return JSONResponse( + status_code=403, + content={"code": 403, "message": "当前用户没有查询企业信息权限", "data": None}, + ) + data = await self.QichachaService.QueryEnterpriseOnly( + Keyword=Body.keyword, + ForceRefresh=Body.forceRefresh, + ) + return Result.success(data=data) + + @self.router.post("/dishonesty", response_model=Result[QichachaCompanyQueryVO]) + async def QueryDishonesty(Body: QichachaCompanyQueryDTO, payload: dict[str, Any] = Depends(verify_access_token)): + """仅查询企业失信信息。""" + if not await self.PermissionService.CheckPermission(int(payload["user_id"]), "qichacha:company:query"): + return JSONResponse( + status_code=403, + content={"code": 403, "message": "当前用户没有查询企业信息权限", "data": None}, + ) + data = await self.QichachaService.QueryDishonestyOnly( + Keyword=Body.keyword, + ForceRefresh=Body.forceRefresh, + ) + return Result.success(data=data) + + @self.router.post("/batch", response_model=Result[QichachaBatchQueryVO]) + async def BatchQuery(Body: QichachaBatchQueryDTO, payload: dict[str, Any] = Depends(verify_access_token)): + """批量查询企业信息。""" + if not await self.PermissionService.CheckPermission(int(payload["user_id"]), "qichacha:company:query"): + return JSONResponse( + status_code=403, + content={"code": 403, "message": "当前用户没有查询企业信息权限", "data": None}, + ) + data = await self.QichachaService.BatchQuery( + Keywords=Body.keywords, + ForceRefresh=Body.forceRefresh, + ) + return Result.success(data=data) + + @self.router.get("/status", response_model=Result[QichachaRecordStatusVO]) + async def GetRecordStatus( + keyword: str = Query(..., min_length=2, description="企业名称或统一社会信用代码"), + payload: dict[str, Any] = Depends(verify_access_token), + ): + """查询企业缓存状态。""" + if not await self.PermissionService.CheckPermission(int(payload["user_id"]), "qichacha:status:read"): + return JSONResponse( + status_code=403, + content={"code": 403, "message": "当前用户没有查看企业缓存状态权限", "data": None}, + ) + data = await self.QichachaService.GetRecordStatus(Keyword=keyword) + return Result.success(data=data) diff --git a/fastapi_modules/fastapi_leaudit/controllers/usageStatsController.py b/fastapi_modules/fastapi_leaudit/controllers/usageStatsController.py index 0a832db..0b7e9f6 100644 --- a/fastapi_modules/fastapi_leaudit/controllers/usageStatsController.py +++ b/fastapi_modules/fastapi_leaudit/controllers/usageStatsController.py @@ -5,6 +5,7 @@ from __future__ import annotations from typing import Any from fastapi import Depends, Query +from fastapi.responses import JSONResponse from fastapi_common.fastapi_common_security.security import verify_access_token from fastapi_common.fastapi_common_web.controller import BaseController @@ -18,7 +19,9 @@ from fastapi_modules.fastapi_leaudit.domian.vo.usageStatsVo import ( UsageStatsTrendVO, UsageStatsUserPageVO, ) +from fastapi_modules.fastapi_leaudit.services.impl.permissionServiceImpl import PermissionServiceImpl from fastapi_modules.fastapi_leaudit.services.impl.usageStatsServiceImpl import UsageStatsServiceImpl +from fastapi_modules.fastapi_leaudit.services.permissionService import IPermissionService from fastapi_modules.fastapi_leaudit.services.usageStatsService import IUsageStatsService @@ -28,6 +31,7 @@ class UsageStatsController(BaseController): def __init__(self): super().__init__(prefix="/v3/usage-stats", tags=["系统使用统计"]) self.UsageStatsService: IUsageStatsService = UsageStatsServiceImpl() + self.PermissionService: IPermissionService = PermissionServiceImpl() @self.router.get("/overview", response_model=Result[UsageStatsOverviewVO]) async def GetOverview( @@ -39,6 +43,9 @@ class UsageStatsController(BaseController): documentTypeId: int | None = None, payload: dict[str, Any] = Depends(verify_access_token), ): + """查询系统使用统计总览。""" + if not await self.PermissionService.CheckPermission(int(payload["user_id"]), "usage_stats:overview:read"): + return JSONResponse(status_code=403, content={"code": 403, "message": "当前用户没有查看统计总览权限", "data": None}) data = await self.UsageStatsService.GetOverview( CurrentUserId=int(payload["user_id"]), Filters={ @@ -64,6 +71,9 @@ class UsageStatsController(BaseController): documentTypeId: int | None = None, payload: dict[str, Any] = Depends(verify_access_token), ): + """查询系统使用统计趋势。""" + if not await self.PermissionService.CheckPermission(int(payload["user_id"]), "usage_stats:trends:read"): + return JSONResponse(status_code=403, content={"code": 403, "message": "当前用户没有查看统计趋势权限", "data": None}) data = await self.UsageStatsService.GetTrends( CurrentUserId=int(payload["user_id"]), Filters={ @@ -92,6 +102,9 @@ class UsageStatsController(BaseController): dateTo: str | None = None, payload: dict[str, Any] = Depends(verify_access_token), ): + """按用户维度查询系统使用统计。""" + if not await self.PermissionService.CheckPermission(int(payload["user_id"]), "usage_stats:users:read"): + return JSONResponse(status_code=403, content={"code": 403, "message": "当前用户没有查看用户统计权限", "data": None}) data = await self.UsageStatsService.GetUsers( CurrentUserId=int(payload["user_id"]), Filters={ @@ -119,6 +132,9 @@ class UsageStatsController(BaseController): dateTo: str | None = None, payload: dict[str, Any] = Depends(verify_access_token), ): + """按部门维度查询系统使用统计。""" + if not await self.PermissionService.CheckPermission(int(payload["user_id"]), "usage_stats:departments:read"): + return JSONResponse(status_code=403, content={"code": 403, "message": "当前用户没有查看部门统计权限", "data": None}) data = await self.UsageStatsService.GetDepartments( CurrentUserId=int(payload["user_id"]), Filters={ @@ -144,6 +160,9 @@ class UsageStatsController(BaseController): documentTypeId: int | None = None, payload: dict[str, Any] = Depends(verify_access_token), ): + """按地区维度查询系统使用统计。""" + if not await self.PermissionService.CheckPermission(int(payload["user_id"]), "usage_stats:areas:read"): + return JSONResponse(status_code=403, content={"code": 403, "message": "当前用户没有查看地区统计权限", "data": None}) data = await self.UsageStatsService.GetAreas( CurrentUserId=int(payload["user_id"]), Filters={ @@ -173,6 +192,9 @@ class UsageStatsController(BaseController): dateTo: str | None = None, payload: dict[str, Any] = Depends(verify_access_token), ): + """查询系统使用统计明细。""" + if not await self.PermissionService.CheckPermission(int(payload["user_id"]), "usage_stats:details:read"): + return JSONResponse(status_code=403, content={"code": 403, "message": "当前用户没有查看统计明细权限", "data": None}) data = await self.UsageStatsService.GetDetails( CurrentUserId=int(payload["user_id"]), Filters={ diff --git a/fastapi_modules/fastapi_leaudit/domian/Dto/entryModuleDto.py b/fastapi_modules/fastapi_leaudit/domian/Dto/entryModuleDto.py index 1452dba..9d83a4d 100644 --- a/fastapi_modules/fastapi_leaudit/domian/Dto/entryModuleDto.py +++ b/fastapi_modules/fastapi_leaudit/domian/Dto/entryModuleDto.py @@ -27,6 +27,8 @@ class EntryModuleCreateDTO(BaseModel): description: str | None = Field(None, description="模块描述") path: str | None = Field(None, description="前端路由路径") route_path: str | None = Field(None, description="前端跳转路径") + menu_profile: str | None = Field(None, description="菜单模板:document_review/contract/govdoc/custom") + features: list[str] | None = Field(None, description="启用功能编码列表") areas: list[EntryModuleAreaDTO] | None = Field(None, description="历史地区配置(兼容字段,建议改用 tenants)") tenants: list[EntryModuleTenantDTO] | None = Field(None, description="租户配置") @@ -38,5 +40,7 @@ class EntryModuleUpdateDTO(BaseModel): description: str | None = Field(None, description="模块描述") path: str | None = Field(None, description="前端路由路径") route_path: str | None = Field(None, description="前端跳转路径") + menu_profile: str | None = Field(None, description="菜单模板:document_review/contract/govdoc/custom") + features: list[str] | None = Field(None, description="启用功能编码列表") areas: list[EntryModuleAreaDTO] | None = Field(None, description="历史地区配置(兼容字段,建议改用 tenants)") tenants: list[EntryModuleTenantDTO] | None = Field(None, description="租户配置") diff --git a/fastapi_modules/fastapi_leaudit/domian/Dto/qichachaDto.py b/fastapi_modules/fastapi_leaudit/domian/Dto/qichachaDto.py new file mode 100644 index 0000000..4b4ef7e --- /dev/null +++ b/fastapi_modules/fastapi_leaudit/domian/Dto/qichachaDto.py @@ -0,0 +1,17 @@ +"""企查查 DTO。""" + +from pydantic import BaseModel, Field + + +class QichachaCompanyQueryDTO(BaseModel): + """企业查询请求。""" + + keyword: str = Field(..., min_length=2, max_length=200, description="企业名称或统一社会信用代码") + forceRefresh: bool = Field(False, description="是否强制刷新缓存") + + +class QichachaBatchQueryDTO(BaseModel): + """批量企业查询请求。""" + + keywords: list[str] = Field(..., min_length=1, max_length=10, description="企业名称或统一社会信用代码列表") + forceRefresh: bool = Field(False, description="是否强制刷新缓存") diff --git a/fastapi_modules/fastapi_leaudit/domian/vo/crossReviewVo.py b/fastapi_modules/fastapi_leaudit/domian/vo/crossReviewVo.py index 63c5374..eb53f7e 100644 --- a/fastapi_modules/fastapi_leaudit/domian/vo/crossReviewVo.py +++ b/fastapi_modules/fastapi_leaudit/domian/vo/crossReviewVo.py @@ -23,12 +23,14 @@ class CrossReviewTaskItemVO(BaseModel): docTypeId: int | None = Field(None, description="文档类型ID") docTypeCode: str | None = Field(None, description="文档类型编码") status: str = Field(..., description="任务状态") - progress: float = Field(0, description="进度百分比") - totalDocuments: int = Field(0, description="文档总数") - completedDocuments: int = Field(0, description="已完成文档数") + progress: float | None = Field(0, description="进度百分比") + totalDocuments: int | None = Field(0, description="文档总数") + completedDocuments: int | None = Field(0, description="已完成文档数") createdAt: datetime | None = Field(None, description="创建时间") evaluationTenants: list[CrossReviewTaskTenantVO] = Field(default_factory=list, description="评查租户列表") evaluationRegion: list[str] = Field(default_factory=list, description="评查租户/地区(兼容展示值)") + currentUserRole: str = Field("participant", description="当前用户在任务中的角色:assigner/principal/participant") + currentUserCanConfirm: bool = Field(False, description="当前用户是否可确认完成任务文档") class CrossReviewTaskPageVO(BaseModel): diff --git a/fastapi_modules/fastapi_leaudit/domian/vo/documentVo.py b/fastapi_modules/fastapi_leaudit/domian/vo/documentVo.py index 0ff0da6..04e6ade 100644 --- a/fastapi_modules/fastapi_leaudit/domian/vo/documentVo.py +++ b/fastapi_modules/fastapi_leaudit/domian/vo/documentVo.py @@ -20,6 +20,7 @@ class DocumentUploadVO(BaseModel): typeId: int = Field(..., description="文档类型ID") typeCode: str = Field(..., description="文档类型编码") groupId: int | None = Field(None, description="命中的二级分组ID") + entryModuleId: int | None = Field(None, description="所属入口模块ID") region: str = Field(..., description="所属地区") tenantCode: str | None = Field(None, description="所属租户编码") tenantName: str | None = Field(None, description="所属租户名称") @@ -109,6 +110,7 @@ class DocumentListItemVO(BaseModel): typeName: str | None = Field(None, description="文档类型名称") groupId: int | None = Field(None, description="命中的二级分组ID") groupName: str | None = Field(None, description="二级分组名称") + entryModuleId: int | None = Field(None, description="所属入口模块ID") region: str = Field(..., description="区域") tenantCode: str | None = Field(None, description="所属租户编码") tenantName: str | None = Field(None, description="所属租户名称") diff --git a/fastapi_modules/fastapi_leaudit/domian/vo/entryModuleAdminVo.py b/fastapi_modules/fastapi_leaudit/domian/vo/entryModuleAdminVo.py index d025356..bc34219 100644 --- a/fastapi_modules/fastapi_leaudit/domian/vo/entryModuleAdminVo.py +++ b/fastapi_modules/fastapi_leaudit/domian/vo/entryModuleAdminVo.py @@ -12,6 +12,14 @@ class EntryModuleTenantVO(BaseModel): sort_order: int = Field(0, description="排序号") +class EntryModuleBusinessScopeVO(BaseModel): + """入口模块业务范围摘要。""" + + category_count: int = Field(0, description="业务大类数量") + business_type_count: int = Field(0, description="业务类型数量") + categories: list[str] = Field(default_factory=list, description="业务大类名称列表") + + class EntryModuleVO(BaseModel): """入口模块详情。""" @@ -20,9 +28,12 @@ class EntryModuleVO(BaseModel): description: str | None = Field(None, description="模块描述") path: str | None = Field(None, description="图标路径") route_path: str | None = Field(None, description="前端跳转路径") + menu_profile: str = Field("document_review", description="菜单模板") + features: list[str] = Field(default_factory=list, description="启用功能编码列表") sort_order: int = Field(0, description="排序") is_enabled: bool = Field(True, description="是否启用") tenants: list[EntryModuleTenantVO] = Field(default_factory=list, description="租户配置") + business_scope: EntryModuleBusinessScopeVO = Field(default_factory=EntryModuleBusinessScopeVO, description="业务范围摘要") created_at: str | None = Field(None, description="创建时间") updated_at: str | None = Field(None, description="更新时间") diff --git a/fastapi_modules/fastapi_leaudit/domian/vo/homeVo.py b/fastapi_modules/fastapi_leaudit/domian/vo/homeVo.py index cdc6069..441c1d0 100644 --- a/fastapi_modules/fastapi_leaudit/domian/vo/homeVo.py +++ b/fastapi_modules/fastapi_leaudit/domian/vo/homeVo.py @@ -36,9 +36,31 @@ class HomeEntryModuleVO(BaseModel): description: str | None = Field(None, description="模块描述") targetPath: str | None = Field(None, description="点击后跳转路径") routePath: str | None = Field(None, description="用于 RBAC 校验的页面路径") + menuProfile: str = Field("document_review", description="菜单模板") + features: list[str] = Field(default_factory=list, description="启用功能编码列表") + tenantCode: str | None = Field(None, description="当前命中的租户编码") iconPath: str | None = Field(None, description="模块图标路径") sortOrder: int = Field(0, description="排序序号") requiresDocumentTypes: bool = Field(True, description="是否要求至少绑定一个文档类型") areas: list[HomeEntryAreaVO] = Field(default_factory=list, description="地区配置") tenants: list[HomeEntryTenantVO] = Field(default_factory=list, description="租户配置") documentTypes: list[HomeEntryDocumentTypeVO] = Field(default_factory=list, description="关联文档类型列表") + + +class HomeDashboardGrowthVO(BaseModel): + """首页统计环比数据。""" + + value: int = Field(0, description="环比百分比") + isUp: bool = Field(True, description="是否上升") + + +class HomeDashboardStatisticsVO(BaseModel): + """首页统计卡片数据。""" + + todayPendingFiles: int = Field(0, description="今日待审文件数") + monthlyReviewedFiles: int = Field(0, description="本月已审核文件数") + monthlyReviewGrowth: HomeDashboardGrowthVO = Field(default_factory=HomeDashboardGrowthVO, description="本月审核数环比") + monthlyPassRate: int = Field(0, description="本月审核通过率") + passRateGrowth: HomeDashboardGrowthVO = Field(default_factory=HomeDashboardGrowthVO, description="通过率环比") + issuesDetected: int = Field(0, description="本月问题检出数") + issuesGrowth: HomeDashboardGrowthVO = Field(default_factory=HomeDashboardGrowthVO, description="问题检出数环比") diff --git a/fastapi_modules/fastapi_leaudit/domian/vo/qichachaVo.py b/fastapi_modules/fastapi_leaudit/domian/vo/qichachaVo.py new file mode 100644 index 0000000..a97483b --- /dev/null +++ b/fastapi_modules/fastapi_leaudit/domian/vo/qichachaVo.py @@ -0,0 +1,55 @@ +"""企查查 VO。""" + +from __future__ import annotations + +from typing import Any + +from pydantic import BaseModel, Field + + +class QichachaCompanyInfoVO(BaseModel): + """企业信息响应。""" + + searchKey: str = Field(..., description="查询关键词") + creditCode: str | None = Field(None, description="统一社会信用代码") + companyName: str | None = Field(None, description="企业名称") + enterprise: dict[str, Any] | None = Field(None, description="工商信息") + dishonesty: dict[str, Any] | None = Field(None, description="失信信息") + hasDishonesty: bool = Field(False, description="是否存在失信记录") + dishonestyCount: int = Field(0, description="失信记录数量") + updatedAt: str | None = Field(None, description="更新时间") + + +class QichachaCompanyQueryVO(BaseModel): + """企业查询响应。""" + + success: bool = Field(..., description="是否成功") + message: str = Field(..., description="响应消息") + data: QichachaCompanyInfoVO | None = Field(None, description="企业信息") + errorCode: str | None = Field(None, description="错误码") + errorDetails: dict[str, Any] | None = Field(None, description="错误详情") + + +class QichachaBatchQueryVO(BaseModel): + """批量企业查询响应。""" + + success: bool = Field(..., description="是否全部成功") + total: int = Field(..., description="查询总数") + successCount: int = Field(..., description="成功数") + failedCount: int = Field(..., description="失败数") + results: list[QichachaCompanyQueryVO] = Field(default_factory=list, description="查询结果") + + +class QichachaRecordStatusVO(BaseModel): + """企业缓存记录状态响应。""" + + exists: bool = Field(..., description="是否存在缓存") + searchKey: str = Field(..., description="查询关键词") + creditCode: str | None = Field(None, description="统一社会信用代码") + companyName: str | None = Field(None, description="企业名称") + hasEnterprise: bool = Field(False, description="是否有工商信息") + hasDishonesty: bool = Field(False, description="是否有失信信息") + updatedAt: str | None = Field(None, description="更新时间") + ageDays: int | None = Field(None, description="缓存天数") + refreshThresholdDays: int = Field(..., description="刷新阈值") + needRefresh: bool = Field(False, description="是否需要刷新") diff --git a/fastapi_modules/fastapi_leaudit/models/__init__.py b/fastapi_modules/fastapi_leaudit/models/__init__.py index 800adcb..8369163 100644 --- a/fastapi_modules/fastapi_leaudit/models/__init__.py +++ b/fastapi_modules/fastapi_leaudit/models/__init__.py @@ -17,6 +17,7 @@ from fastapi_modules.fastapi_leaudit.models.usageLoginEvent import UsageLoginEve from fastapi_modules.fastapi_leaudit.models.govdocRun import GovdocRun from fastapi_modules.fastapi_leaudit.models.govdocRuleResult import GovdocRuleResult from fastapi_modules.fastapi_leaudit.models.govdocReportArtifact import GovdocReportArtifact +from fastapi_modules.fastapi_leaudit.models.qichachaCompanyInfo import QichachaCompanyInfo __all__ = [ "LeauditDocument", @@ -36,4 +37,5 @@ __all__ = [ "GovdocRun", "GovdocRuleResult", "GovdocReportArtifact", + "QichachaCompanyInfo", ] diff --git a/fastapi_modules/fastapi_leaudit/models/qichachaCompanyInfo.py b/fastapi_modules/fastapi_leaudit/models/qichachaCompanyInfo.py new file mode 100644 index 0000000..a055906 --- /dev/null +++ b/fastapi_modules/fastapi_leaudit/models/qichachaCompanyInfo.py @@ -0,0 +1,84 @@ +"""企查查企业信息缓存模型。""" + +from __future__ import annotations + +from datetime import UTC, datetime +from typing import Any + +from sqlalchemy import BigInteger, Index, String, or_, select +from sqlalchemy.dialects.postgresql import JSONB +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import Mapped, mapped_column + +from fastapi_common.fastapi_common_web.models import BaseModel + + +class QichachaCompanyInfo(BaseModel): + """企查查企业信息缓存表。""" + + __tablename__ = "qcc_company_info" + __table_args__ = ( + Index("idx_qcc_company_search_key", "search_key", unique=True), + Index("idx_qcc_company_credit_code", "credit_code"), + Index("idx_qcc_company_name", "company_name"), + ) + + Id: Mapped[int] = mapped_column("id", BigInteger, primary_key=True, autoincrement=True) + searchKey: Mapped[str] = mapped_column("search_key", String(200), nullable=False, comment="查询关键词") + creditCode: Mapped[str | None] = mapped_column("credit_code", String(64), comment="统一社会信用代码") + companyName: Mapped[str | None] = mapped_column("company_name", String(255), comment="企业名称") + enterprise: Mapped[dict[str, Any] | None] = mapped_column("enterprise", JSONB, comment="工商信息") + dishonesty: Mapped[dict[str, Any] | None] = mapped_column("dishonesty", JSONB, comment="失信信息") + + @classmethod + async def FindByKeyword(cls, session: AsyncSession, Keyword: str) -> "QichachaCompanyInfo | None": + """按查询词、信用代码或企业名查询缓存。""" + return await session.scalar( + select(cls).where( + cls.deleted_at.is_(None), + or_( + cls.searchKey == Keyword, + cls.creditCode == Keyword, + cls.companyName == Keyword, + ), + ) + ) + + @classmethod + async def Upsert( + cls, + session: AsyncSession, + SearchKey: str, + CreditCode: str | None, + CompanyName: str | None, + Enterprise: dict[str, Any] | None, + Dishonesty: dict[str, Any] | None, + ) -> "QichachaCompanyInfo": + """按查询关键词写入或更新缓存。""" + record = await cls.FindByKeyword(session, SearchKey) + if record is None: + record = cls( + searchKey=SearchKey, + creditCode=CreditCode, + companyName=CompanyName, + enterprise=Enterprise, + dishonesty=Dishonesty, + ) + session.add(record) + else: + record.searchKey = SearchKey + record.creditCode = CreditCode + record.companyName = CompanyName + record.enterprise = Enterprise + record.dishonesty = Dishonesty + record.updated_at = datetime.now(UTC) + await session.flush() + return record + + @classmethod + def GetAgeDays(cls, record: "QichachaCompanyInfo") -> int: + """计算缓存记录距今天数。""" + updated_at = record.updated_at + if updated_at.tzinfo is None: + updated_at = updated_at.replace(tzinfo=UTC) + return max((datetime.now(UTC) - updated_at).days, 0) diff --git a/fastapi_modules/fastapi_leaudit/services/__init__.py b/fastapi_modules/fastapi_leaudit/services/__init__.py index efedcef..067f8d2 100644 --- a/fastapi_modules/fastapi_leaudit/services/__init__.py +++ b/fastapi_modules/fastapi_leaudit/services/__init__.py @@ -20,6 +20,7 @@ from fastapi_modules.fastapi_leaudit.services.ragChatService import IRagChatServ from fastapi_modules.fastapi_leaudit.services.tenantService import ITenantService from fastapi_modules.fastapi_leaudit.services.govdocService import IGovdocService from fastapi_modules.fastapi_leaudit.services.usageStatsService import IUsageStatsService +from fastapi_modules.fastapi_leaudit.services.qichachaService import IQichachaService __all__ = [ "IAuditService", @@ -42,4 +43,5 @@ __all__ = [ "ITenantService", "IUsageStatsService", "IGovdocService", + "IQichachaService", ] diff --git a/fastapi_modules/fastapi_leaudit/services/crossReviewService.py b/fastapi_modules/fastapi_leaudit/services/crossReviewService.py index dbea998..84d9c20 100644 --- a/fastapi_modules/fastapi_leaudit/services/crossReviewService.py +++ b/fastapi_modules/fastapi_leaudit/services/crossReviewService.py @@ -35,7 +35,12 @@ class ICrossReviewService(ABC): ... @abstractmethod - async def GetUserTasks(self, CurrentUserId: int, Body: CrossReviewTaskQueryDTO) -> CrossReviewTaskPageVO: + async def GetUserTasks( + self, + CurrentUserId: int, + Body: CrossReviewTaskQueryDTO, + CanViewProgress: bool = True, + ) -> CrossReviewTaskPageVO: """查询当前用户参与的交叉评查任务。""" ... diff --git a/fastapi_modules/fastapi_leaudit/services/documentService.py b/fastapi_modules/fastapi_leaudit/services/documentService.py index 6605508..fccdbd5 100644 --- a/fastapi_modules/fastapi_leaudit/services/documentService.py +++ b/fastapi_modules/fastapi_leaudit/services/documentService.py @@ -81,6 +81,11 @@ class IDocumentService(ABC): """获取评查详情页所需的聚合数据。""" ... + @abstractmethod + async def IsCrossReviewDocument(self, DocumentId: int) -> bool: + """判断文档是否属于交叉评查范围。""" + ... + @abstractmethod async def AuditReviewPoint( self, diff --git a/fastapi_modules/fastapi_leaudit/services/evaluationPointGroupService.py b/fastapi_modules/fastapi_leaudit/services/evaluationPointGroupService.py index db7f978..9419566 100755 --- a/fastapi_modules/fastapi_leaudit/services/evaluationPointGroupService.py +++ b/fastapi_modules/fastapi_leaudit/services/evaluationPointGroupService.py @@ -36,11 +36,18 @@ class IEvaluationPointGroupService(ABC): Page: int, PageSize: int, CurrentUserId: int, + EntryModuleId: int | None = None, ) -> EvaluationPointGroupListVO: ... @abstractmethod - async def ListAllGroups(self, IncludeDisabled: bool, WithRuleCount: bool, CurrentUserId: int) -> list[EvaluationPointGroupVO]: + async def ListAllGroups( + self, + IncludeDisabled: bool, + WithRuleCount: bool, + CurrentUserId: int, + EntryModuleId: int | None = None, + ) -> list[EvaluationPointGroupVO]: ... @abstractmethod diff --git a/fastapi_modules/fastapi_leaudit/services/govdocService.py b/fastapi_modules/fastapi_leaudit/services/govdocService.py index dbccd66..8f8e3c8 100644 --- a/fastapi_modules/fastapi_leaudit/services/govdocService.py +++ b/fastapi_modules/fastapi_leaudit/services/govdocService.py @@ -16,6 +16,7 @@ class IGovdocService(ABC): self, file: UploadFile, typeId: int | None = None, + entryModuleId: int | None = None, region: str | None = None, tenantCode: str | None = None, autoRun: bool = False, @@ -35,6 +36,8 @@ class IGovdocService(ABC): fileExt: str | None = None, region: str | None = None, tenantCode: str | None = None, + entryModuleId: int | None = None, + typeIds: list[int] | None = None, status: str | None = None, resultStatus: str | None = None, createdBy: int | None = None, diff --git a/fastapi_modules/fastapi_leaudit/services/homeService.py b/fastapi_modules/fastapi_leaudit/services/homeService.py index 6849c44..e138e67 100644 --- a/fastapi_modules/fastapi_leaudit/services/homeService.py +++ b/fastapi_modules/fastapi_leaudit/services/homeService.py @@ -2,7 +2,7 @@ from abc import ABC, abstractmethod -from fastapi_modules.fastapi_leaudit.domian.vo.homeVo import HomeEntryModuleVO +from fastapi_modules.fastapi_leaudit.domian.vo.homeVo import HomeDashboardStatisticsVO, HomeEntryModuleVO class IHomeService(ABC): @@ -12,3 +12,14 @@ class IHomeService(ABC): async def GetEntryModules(self, UserId: int) -> list[HomeEntryModuleVO]: """获取当前用户可见的首页入口模块。""" ... + + @abstractmethod + async def GetDashboardStatistics( + self, + UserId: int, + Today: str | None = None, + TypeIds: list[int] | None = None, + EntryModuleId: int | None = None, + ) -> HomeDashboardStatisticsVO: + """获取当前业务入口的首页统计卡片数据。""" + ... diff --git a/fastapi_modules/fastapi_leaudit/services/impl/crossReviewServiceImpl.py b/fastapi_modules/fastapi_leaudit/services/impl/crossReviewServiceImpl.py index f4c41c0..9ed3326 100644 --- a/fastapi_modules/fastapi_leaudit/services/impl/crossReviewServiceImpl.py +++ b/fastapi_modules/fastapi_leaudit/services/impl/crossReviewServiceImpl.py @@ -200,7 +200,7 @@ class CrossReviewServiceImpl(ICrossReviewService): await self._ensure_tables_ready(session) memberUserIds = self._unique_int_list(Body.memberUserIds + [CurrentUserId]) - principalUserIds = self._unique_int_list(Body.principalUserIds) + principalUserIds = self._unique_int_list(Body.principalUserIds + [CurrentUserId]) documentIds = self._unique_int_list(Body.documentIds) await self._assert_task_scope_inputs(session, CurrentUserId, memberUserIds, documentIds) @@ -289,7 +289,12 @@ class CrossReviewServiceImpl(ICrossReviewService): documentCount=len(documentIds), ) - async def GetUserTasks(self, CurrentUserId: int, Body: CrossReviewTaskQueryDTO) -> CrossReviewTaskPageVO: + async def GetUserTasks( + self, + CurrentUserId: int, + Body: CrossReviewTaskQueryDTO, + CanViewProgress: bool = True, + ) -> CrossReviewTaskPageVO: """查询当前用户参与的交叉评查任务。""" async with GetAsyncSession() as session: await self._ensure_tables_ready(session) @@ -401,6 +406,15 @@ class CrossReviewServiceImpl(ICrossReviewService): t.doc_type_code, t.status, t.create_time, + CASE + WHEN t.assigner_id = :current_user_id THEN 'assigner' + ELSE COALESCE(MAX(tm.member_role), 'participant') + END AS current_user_role, + CASE + WHEN t.assigner_id = :current_user_id THEN TRUE + WHEN COALESCE(MAX(CASE WHEN tm.member_role = 'principal' THEN 1 ELSE 0 END), 0) = 1 THEN TRUE + ELSE FALSE + END AS current_user_can_confirm, COALESCE(ds.total_documents, 0) AS total_documents, COALESCE(ds.completed_documents, 0) AS completed_documents, COALESCE(tt.evaluation_tenants, '[]'::jsonb) AS evaluation_tenants, @@ -416,7 +430,7 @@ class CrossReviewServiceImpl(ICrossReviewService): ON tr.task_id = t.id WHERE {whereSql} GROUP BY - t.id, t.task_name, t.task_type, t.doc_type_id, t.doc_type_code, + t.id, t.task_name, t.task_type, t.doc_type_id, t.doc_type_code, t.assigner_id, t.status, t.create_time, ds.total_documents, ds.completed_documents, tt.evaluation_tenants, tr.evaluation_regions ORDER BY t.create_time DESC, t.id DESC @@ -429,35 +443,7 @@ class CrossReviewServiceImpl(ICrossReviewService): items = [] for row in rows: - totalDocuments = int(row["total_documents"] or 0) - completedDocuments = int(row["completed_documents"] or 0) - progress = round((completedDocuments / totalDocuments * 100) if totalDocuments > 0 else 0, 2) - evaluationTenants = self._parse_task_tenants(row.get("evaluation_tenants")) - rawRegions = row.get("evaluation_regions") - if rawRegions is None: - evaluationRegion: list[str] = [] - elif isinstance(rawRegions, list): - evaluationRegion = [str(r) for r in rawRegions] - else: - evaluationRegion = [str(rawRegions)] - if not evaluationRegion: - evaluationRegion = [tenant.tenantName for tenant in evaluationTenants if tenant.tenantName] - items.append( - CrossReviewTaskItemVO( - taskId=int(row["task_id"]), - taskName=str(row["task_name"]), - taskType=str(row["task_type"]), - docTypeId=self._to_int(row.get("doc_type_id")), - docTypeCode=row.get("doc_type_code"), - status=str(row["status"]), - progress=progress, - totalDocuments=totalDocuments, - completedDocuments=completedDocuments, - createdAt=row.get("create_time"), - evaluationTenants=evaluationTenants, - evaluationRegion=evaluationRegion, - ) - ) + items.append(self._build_task_item_vo(row=row, CanViewProgress=CanViewProgress)) return CrossReviewTaskPageVO(total=total, page=Body.page, pageSize=Body.pageSize, items=items) @@ -518,7 +504,32 @@ class CrossReviewServiceImpl(ICrossReviewService): baseWhereClauses.append("(d.normalized_name ILIKE :keyword OR CAST(d.biz_document_id AS TEXT) ILIKE :keyword)") params["keyword"] = f"%{Body.keyword.strip()}%" baseWhereSql = " AND ".join(baseWhereClauses) - latestWhereSql = f"{baseWhereSql} AND COALESCE(d.is_latest_version, false) = true" + taskDocumentsSql = f""" + SELECT + td.task_id, + td.document_id, + td.audit_status, + d.id, + d.normalized_name, + d.biz_document_id, + d.type_id, + d.processing_status, + d.version_no, + d.is_latest_version, + d.version_group_key, + d.root_version_id, + d.current_run_id, + d.created_at, + COALESCE(NULLIF(d.version_group_key, ''), CONCAT('root:', COALESCE(d.root_version_id, d.id)::text)) AS task_version_group_key, + ROW_NUMBER() OVER ( + PARTITION BY COALESCE(NULLIF(d.version_group_key, ''), CONCAT('root:', COALESCE(d.root_version_id, d.id)::text)) + ORDER BY d.version_no DESC, d.id DESC + ) AS task_version_rank + FROM leaudit_cross_review_task_documents td + JOIN leaudit_documents d + ON d.id = td.document_id + WHERE {baseWhereSql} + """ total = int( ( @@ -526,10 +537,8 @@ class CrossReviewServiceImpl(ICrossReviewService): text( f""" SELECT COUNT(*) - FROM leaudit_cross_review_task_documents td - JOIN leaudit_documents d - ON d.id = td.document_id - WHERE {latestWhereSql} + FROM ({taskDocumentsSql}) task_docs + WHERE task_docs.task_version_rank = 1 """ ), params, @@ -558,10 +567,10 @@ class CrossReviewServiceImpl(ICrossReviewService): ) AS processing_status, d.version_no, d.is_latest_version, - COALESCE(NULLIF(d.version_group_key, ''), CONCAT('root:', COALESCE(d.root_version_id, d.id)::text)) AS version_group_key, + d.task_version_group_key AS version_group_key, COALESCE(vc.total_versions, 1)::int AS total_versions, d.created_at, - td.audit_status, + d.audit_status, COALESCE(dt.name, '') AS type_name, COALESCE(df.file_size, 0) AS file_size, COALESCE(df.file_path, '') AS path, @@ -581,9 +590,7 @@ class CrossReviewServiceImpl(ICrossReviewService): COALESCE(es.full_score, 0) AS full_score, COALESCE(es.score_summary, '') AS score_summary, COALESCE(es.score_percent, 0) AS score_percent - FROM leaudit_cross_review_task_documents td - JOIN leaudit_documents d - ON d.id = td.document_id + FROM ({taskDocumentsSql}) d LEFT JOIN leaudit_audit_runs ar ON ar.id = d.current_run_id LEFT JOIN leaudit_document_types dt @@ -608,7 +615,7 @@ class CrossReviewServiceImpl(ICrossReviewService): WHERE d2.deleted_at IS NULL GROUP BY d2.version_group_key , COALESCE(d2.root_version_id, d2.id) - ) vc ON vc.version_group_key = COALESCE(NULLIF(d.version_group_key, ''), CONCAT('root:', COALESCE(d.root_version_id, d.id)::text)) + ) vc ON vc.version_group_key = d.task_version_group_key LEFT JOIN LATERAL ( SELECT COUNT(*)::int AS total_evaluation_points, @@ -661,7 +668,7 @@ class CrossReviewServiceImpl(ICrossReviewService): AND p.status = 'approved' AND p.delete_time IS NULL ) pd ON TRUE - WHERE {latestWhereSql} + WHERE d.task_version_rank = 1 ORDER BY d.created_at DESC, d.id DESC LIMIT :limit OFFSET :offset """ @@ -2099,13 +2106,15 @@ class CrossReviewServiceImpl(ICrossReviewService): if not member_tenant_code and not member_tenant_name: raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, f"成员用户未绑定有效租户: {user_id}") resolved_scopes.append({"tenant_code": member_tenant_code, "tenant_name": member_tenant_name}) + if is_global: + continue if current_tenant_code: if member_tenant_code: if member_tenant_code != current_tenant_code: raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, f"不能将其他租户用户加入交叉评查任务: {user_id}") elif member_tenant_name != current_tenant_name: raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, f"不能将其他租户用户加入交叉评查任务: {user_id}") - elif not is_global and member_tenant_name != current_tenant_name: + elif member_tenant_name != current_tenant_name: raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, f"不能将其他租户用户加入交叉评查任务: {user_id}") if document_ids: @@ -2141,13 +2150,15 @@ class CrossReviewServiceImpl(ICrossReviewService): if not document_tenant_code and not document_tenant_name: raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, f"任务文档未绑定有效租户: {document_id}") resolved_scopes.append({"tenant_code": document_tenant_code, "tenant_name": document_tenant_name}) + if is_global: + continue if current_tenant_code: if document_tenant_code: if document_tenant_code != current_tenant_code: raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, f"不能将其他租户文档加入交叉评查任务: {document_id}") elif document_tenant_name != current_tenant_name: raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, f"不能将其他租户文档加入交叉评查任务: {document_id}") - elif not is_global and document_tenant_name != current_tenant_name: + elif document_tenant_name != current_tenant_name: raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, f"不能将其他租户文档加入交叉评查任务: {document_id}") self._assert_single_task_scope(resolved_scopes) @@ -2283,13 +2294,15 @@ class CrossReviewServiceImpl(ICrossReviewService): result.append(intValue) return result - def _to_int(self, value) -> int | None: + @staticmethod + def _to_int(value) -> int | None: """安全转 int。""" if value is None: return None return int(value) - def _parse_text_array(self, value) -> list[str]: + @staticmethod + def _parse_text_array(value) -> list[str]: """安全解析 PostgreSQL text[] 为字符串列表。""" if value is None: return [] @@ -2297,7 +2310,8 @@ class CrossReviewServiceImpl(ICrossReviewService): return [str(v) for v in value] return [str(value)] - def _parse_task_tenants(self, value) -> list[CrossReviewTaskTenantVO]: + @staticmethod + def _parse_task_tenants(value) -> list[CrossReviewTaskTenantVO]: """安全解析任务租户 JSON 聚合结果。""" if value is None: return [] @@ -2319,6 +2333,40 @@ class CrossReviewServiceImpl(ICrossReviewService): result.append(CrossReviewTaskTenantVO(tenantCode=tenant_code, tenantName=tenant_name or tenant_code)) return result + @classmethod + def _build_task_item_vo(cls, row, CanViewProgress: bool = True) -> CrossReviewTaskItemVO: + """根据进度权限组装任务列表项。""" + totalDocuments = int(row["total_documents"] or 0) + completedDocuments = int(row["completed_documents"] or 0) + progress = round((completedDocuments / totalDocuments * 100) if totalDocuments > 0 else 0, 2) + evaluationTenants = cls._parse_task_tenants(row.get("evaluation_tenants")) + rawRegions = row.get("evaluation_regions") + if rawRegions is None: + evaluationRegion: list[str] = [] + elif isinstance(rawRegions, list): + evaluationRegion = [str(r) for r in rawRegions] + else: + evaluationRegion = [str(rawRegions)] + if not evaluationRegion: + evaluationRegion = [tenant.tenantName for tenant in evaluationTenants if tenant.tenantName] + + return CrossReviewTaskItemVO( + taskId=int(row["task_id"]), + taskName=str(row["task_name"]), + taskType=str(row["task_type"]), + docTypeId=cls._to_int(row.get("doc_type_id")), + docTypeCode=row.get("doc_type_code"), + status=str(row["status"]), + progress=progress if CanViewProgress else None, + totalDocuments=totalDocuments if CanViewProgress else None, + completedDocuments=completedDocuments if CanViewProgress else None, + createdAt=row.get("create_time"), + evaluationTenants=evaluationTenants, + evaluationRegion=evaluationRegion, + currentUserRole=str(row.get("current_user_role") or "participant"), + currentUserCanConfirm=bool(row.get("current_user_can_confirm")), + ) + def _build_score_summary(self, finalScore: float, fullScore: float) -> str: if fullScore <= 0: return "0/0" diff --git a/fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py b/fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py index 0c8ad15..9581ae0 100644 --- a/fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py +++ b/fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py @@ -88,6 +88,7 @@ class DocumentServiceImpl(IDocumentService): TypeId: int | None = None, TypeCode: str | None = None, GroupId: int | None = None, + EntryModuleId: int | None = None, Region: str | None = None, FileRole: str = "primary", CreatedBy: int | None = None, @@ -136,6 +137,7 @@ class DocumentServiceImpl(IDocumentService): async with GetAsyncSession() as Session: await self._ensureDocumentGroupColumn(Session) + normalizedEntryModuleId = int(EntryModuleId) if EntryModuleId is not None and int(EntryModuleId) > 0 else None if TypeId is not None and TypeCode is not None: typeResult = await Session.execute( text( @@ -182,7 +184,13 @@ class DocumentServiceImpl(IDocumentService): resolvedTypeId = int(typeRow["id"]) resolvedTypeCode = str(typeRow["code"]) - resolvedGroupId = await self._resolveDocumentGroupId(Session, resolvedTypeId, GroupId) + await self._assertDocumentTypeEntryModule(Session, resolvedTypeId, normalizedEntryModuleId) + resolvedGroupId = await self._resolveDocumentGroupId( + Session, + resolvedTypeId, + GroupId, + normalizedEntryModuleId, + ) resolvedRootGroupId = await self._resolveDocumentRootGroupId(Session, resolvedTypeId, resolvedGroupId) duplicateUpload = False previousVersionId: int | None = None @@ -236,6 +244,21 @@ class DocumentServiceImpl(IDocumentService): else: rootVersionId = document.rootVersionId + if normalizedEntryModuleId is not None: + await Session.execute( + text( + """ + UPDATE leaudit_documents + SET entry_module_id = :entry_module_id + WHERE id = :document_id + """ + ), + { + "entry_module_id": normalizedEntryModuleId, + "document_id": document.Id, + }, + ) + versionLabel = f"v{document.versionNo}" objectKey = OssPathUtils.BuildBusinessDocKey( Region=normalizedRegion, @@ -324,6 +347,7 @@ class DocumentServiceImpl(IDocumentService): typeId=resolvedTypeId, typeCode=resolvedTypeCode, groupId=resolvedGroupId, + entryModuleId=normalizedEntryModuleId, region=normalizedRegion, tenantCode=resolvedTenant.tenant_code, tenantName=resolvedTenant.tenant_name or normalizedRegion, @@ -463,6 +487,7 @@ class DocumentServiceImpl(IDocumentService): optionalSelects = [ f"{resolvedGroupIdExpr} AS group_id", + "d.entry_module_id AS entry_module_id" if "entry_module_id" in documentColumns else "NULL::bigint AS entry_module_id", "d.document_number AS document_number" if "document_number" in documentColumns else "NULL::text AS document_number", f"{persistedOrDerivedAuditStatusExpr} AS audit_status", "d.is_test_document AS is_test_document" if "is_test_document" in documentColumns else "FALSE AS is_test_document", @@ -662,6 +687,7 @@ class DocumentServiceImpl(IDocumentService): typeName=row["type_name"], groupId=int(row["group_id"]) if row["group_id"] is not None else None, groupName=row["group_name"], + entryModuleId=int(row["entry_module_id"]) if row["entry_module_id"] is not None else None, region=row["region"], tenantCode=row["tenant_code"], tenantName=row["tenant_name"], @@ -762,6 +788,26 @@ class DocumentServiceImpl(IDocumentService): scoring_proposals=scoringProposals, ) + async def IsCrossReviewDocument(self, DocumentId: int) -> bool: + """判断文档是否属于交叉评查范围。""" + async with GetAsyncSession() as Session: + row = ( + await Session.execute( + text( + """ + SELECT 1 + FROM leaudit_documents d + WHERE d.id = :document_id + AND d.deleted_at IS NULL + AND COALESCE(d.review_scope, 'standard') = 'cross_review' + LIMIT 1 + """ + ), + {"document_id": DocumentId}, + ) + ).first() + return bool(row) + async def AuditReviewPoint( self, CurrentUserId: int, @@ -2705,6 +2751,15 @@ class DocumentServiceImpl(IDocumentService): """ ) ) + await Session.execute( + text( + """ + ALTER TABLE leaudit_documents + ADD COLUMN IF NOT EXISTS entry_module_id BIGINT NULL + REFERENCES leaudit_entry_modules(id) + """ + ) + ) await Session.execute( text( """ @@ -2717,23 +2772,71 @@ class DocumentServiceImpl(IDocumentService): await Session.execute( text("CREATE INDEX IF NOT EXISTS idx_leaudit_documents_group_id ON leaudit_documents(group_id)") ) + await Session.execute( + text("CREATE INDEX IF NOT EXISTS idx_leaudit_documents_entry_module_id ON leaudit_documents(entry_module_id)") + ) await Session.execute( text("CREATE INDEX IF NOT EXISTS idx_leaudit_documents_tenant_code ON leaudit_documents(tenant_code)") ) - async def _resolveDocumentGroupId(self, Session, TypeId: int, GroupId: int | None) -> int | None: - """校验上传时选择的二级分组是否属于当前文档类型。""" - if GroupId is None: - return None + async def _assertDocumentTypeEntryModule( + self, + Session, + TypeId: int, + EntryModuleId: int | None, + ) -> None: + if EntryModuleId is None: + return row = ( await Session.execute( text( """ - SELECT id, document_type_id, name - FROM leaudit_evaluation_point_groups - WHERE id = :group_id + SELECT id, name, entry_module_id + FROM leaudit_document_types + WHERE id = :type_id AND deleted_at IS NULL - AND COALESCE(pid, 0) <> 0 + LIMIT 1 + """ + ), + {"type_id": TypeId}, + ) + ).mappings().first() + if not row: + raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "文档类型不存在或已停用") + if int(row.get("entry_module_id") or 0) != EntryModuleId: + raise LeauditException( + StatusCodeEnum.HTTP_400_BAD_REQUEST, + f"文档类型「{row['name']}」不属于当前入口模块,无法上传", + ) + + async def _resolveDocumentGroupId( + self, + Session, + TypeId: int, + GroupId: int | None, + EntryModuleId: int | None = None, + ) -> int | None: + """校验上传时选择的二级分组是否属于当前文档类型。""" + if GroupId is None: + return await self._resolveUniqueDocumentGroupId(Session, TypeId, EntryModuleId) + row = ( + await Session.execute( + text( + """ + SELECT + child.id, + child.document_type_id, + child.name, + COALESCE(child.entry_module_id, parent.entry_module_id, dt.entry_module_id) AS entry_module_id + FROM leaudit_evaluation_point_groups child + LEFT JOIN leaudit_evaluation_point_groups parent + ON parent.id = child.pid + LEFT JOIN leaudit_document_types dt + ON dt.id = child.document_type_id + WHERE child.id = :group_id + AND child.deleted_at IS NULL + AND COALESCE(child.pid, 0) <> 0 + AND child.is_enabled = true LIMIT 1 """ ), @@ -2748,8 +2851,63 @@ class DocumentServiceImpl(IDocumentService): TypeId, GroupId, row["document_type_id"], row["name"], ) raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, f"当前子类型「{row['name']}」(id={GroupId}) 属于文档类型 {row['document_type_id']},与所选文档类型 {TypeId} 不匹配,无法上传") + if EntryModuleId is not None and int(row.get("entry_module_id") or 0) != EntryModuleId: + raise LeauditException( + StatusCodeEnum.HTTP_400_BAD_REQUEST, + f"当前子类型「{row['name']}」不属于当前入口模块,无法上传", + ) return int(row["id"]) + async def _resolveUniqueDocumentGroupId( + self, + Session, + TypeId: int, + EntryModuleId: int | None = None, + ) -> int | None: + params: dict[str, int] = {"type_id": TypeId} + entry_module_filter = "" + if EntryModuleId is not None: + entry_module_filter = """ + AND COALESCE(child.entry_module_id, parent.entry_module_id, dt.entry_module_id) = :entry_module_id + """ + params["entry_module_id"] = int(EntryModuleId) + rows = ( + await Session.execute( + text( + f""" + SELECT + child.id, + child.name, + COALESCE(child.entry_module_id, parent.entry_module_id, dt.entry_module_id) AS entry_module_id + FROM leaudit_evaluation_point_groups child + LEFT JOIN leaudit_evaluation_point_groups parent + ON parent.id = child.pid + LEFT JOIN leaudit_document_types dt + ON dt.id = child.document_type_id + WHERE child.document_type_id = :type_id + AND child.deleted_at IS NULL + AND COALESCE(child.pid, 0) <> 0 + AND child.is_enabled = true + {entry_module_filter} + ORDER BY COALESCE(child.sort_order, 0) ASC, child.id ASC + LIMIT 2 + """ + ), + params, + ) + ).mappings().all() + if len(rows) == 1: + return int(rows[0]["id"]) + if len(rows) > 1: + raise LeauditException( + StatusCodeEnum.HTTP_400_BAD_REQUEST, + "当前文档类型存在多个子类型,请选择具体子类型后再上传", + ) + raise LeauditException( + StatusCodeEnum.HTTP_400_BAD_REQUEST, + "当前文档类型未配置可用子类型,请先在评查点分组管理中配置二级分组", + ) + async def _resolveDocumentRootGroupId(self, Session, TypeId: int, GroupId: int | None) -> int | None: """解析上传命中的一级分组,用于跨二级类型做版本归档。""" if GroupId is not None: @@ -2942,6 +3100,7 @@ class DocumentServiceImpl(IDocumentService): optionalSelects = [ f"{resolvedGroupIdExpr} AS group_id", + "d.entry_module_id AS entry_module_id" if "entry_module_id" in DocumentColumns else "NULL::bigint AS entry_module_id", "d.document_number AS document_number" if "document_number" in DocumentColumns else "NULL::text AS document_number", "d.remark AS remark" if "remark" in DocumentColumns else "NULL::text AS remark", "d.is_test_document AS is_test_document" if "is_test_document" in DocumentColumns else "FALSE AS is_test_document", @@ -3149,6 +3308,7 @@ class DocumentServiceImpl(IDocumentService): typeName=detailRow["type_name"], groupId=int(detailRow["group_id"]) if detailRow["group_id"] is not None else None, groupName=detailRow["group_name"], + entryModuleId=int(detailRow["entry_module_id"]) if detailRow["entry_module_id"] is not None else None, region=str(detailRow["region"] or ""), tenantCode=detailRow["tenant_code"], tenantName=detailRow["tenant_name"], @@ -3562,6 +3722,9 @@ class DocumentServiceImpl(IDocumentService): pageCount = int(metricRow["page_count"]) if metricRow and metricRow["page_count"] is not None else 0 ocrResultPayload = await self._buildReviewOcrPayload(Session, Detail.documentId, RunRow, pageCount) + pageQualityResults = [] + if Detail.pageQualityRunId is not None: + pageQualityResults = await self._loadPageQualityIssueResults(Session, int(Detail.pageQualityRunId)) return { "id": Detail.documentId, @@ -3593,8 +3756,66 @@ class DocumentServiceImpl(IDocumentService): "page_count": pageCount, "ocrResult": ocrResultPayload, "attachments": [item.model_dump() for item in Detail.attachments], + **self._buildReviewPageQualityPayload(Detail, pageQualityResults), } + @staticmethod + def _buildReviewPageQualityPayload( + Detail: DocumentDetailVO, + PageQualityResults: list[dict[str, Any]] | None = None, + ) -> dict[str, Any]: + """构建评查详情页需要的图片质量摘要字段。""" + page_quality_summary = Detail.pageQualitySummary.model_dump() if Detail.pageQualitySummary else None + status_order = {"reject": 0, "review": 1} + page_quality_results = sorted( + PageQualityResults or [], + key=lambda item: ( + status_order.get(str(item.get("qualityStatus") or ""), 9), + int(item.get("pageNum") or 0), + ), + ) + return { + "pageQualityRunId": Detail.pageQualityRunId, + "pageQualityRunStatus": Detail.pageQualityRunStatus, + "pageQualitySummaryStatus": Detail.pageQualitySummaryStatus, + "pageQualityIssueCount": Detail.pageQualityIssueCount, + "pageQualityWarningText": Detail.pageQualityWarningText, + "pageQualitySummary": page_quality_summary, + "pageQualityResults": page_quality_results, + } + + async def _loadPageQualityIssueResults(self, Session, RunId: int) -> list[dict[str, Any]]: + """加载页级图片质量问题明细。""" + if not await self._tableExists(Session, "leaudit_page_quality_results"): + return [] + rows = ( + await Session.execute( + text( + """ + SELECT page_num, quality_status, quality_score, reason_text + FROM leaudit_page_quality_results + WHERE run_id = :run_id + AND quality_status IN ('review', 'reject') + ORDER BY + CASE quality_status WHEN 'reject' THEN 0 WHEN 'review' THEN 1 ELSE 9 END, + page_num ASC, + id ASC + """ + ), + {"run_id": RunId}, + ) + ).mappings().all() + return [ + { + "pageNum": int(row["page_num"]), + "qualityStatus": str(row["quality_status"] or "review"), + "qualityScore": float(row["quality_score"]) if row["quality_score"] is not None else None, + "reasonText": str(row["reason_text"] or "") or None, + } + for row in rows + if row["page_num"] is not None + ] + async def _buildReviewOcrPayload( self, Session, diff --git a/fastapi_modules/fastapi_leaudit/services/impl/entryModuleAdminServiceImpl.py b/fastapi_modules/fastapi_leaudit/services/impl/entryModuleAdminServiceImpl.py index ea926f9..56cefb8 100644 --- a/fastapi_modules/fastapi_leaudit/services/impl/entryModuleAdminServiceImpl.py +++ b/fastapi_modules/fastapi_leaudit/services/impl/entryModuleAdminServiceImpl.py @@ -21,6 +21,7 @@ from fastapi_modules.fastapi_leaudit.domian.Dto.entryModuleDto import ( EntryModuleUpdateDTO, ) from fastapi_modules.fastapi_leaudit.domian.vo.entryModuleAdminVo import ( + EntryModuleBusinessScopeVO, EntryModuleImageUploadVO, EntryModuleListVO, EntryModuleTenantVO, @@ -31,6 +32,31 @@ from fastapi_modules.fastapi_leaudit.services.impl.ossServiceImpl import OssServ from fastapi_modules.fastapi_leaudit.services.impl.tenantResolver import TenantResolution, TenantResolver +_ALLOWED_MENU_PROFILES = {"document_review", "contract", "govdoc", "cross_checking", "custom"} +_ALLOWED_FEATURES = { + "home", + "documents", + "upload", + "rules", + "rule_groups", + "contract_template_search", + "contract_template_list", + "govdoc_audits", + "govdoc_upload", + "cross_checking", + "cross_checking_upload", + "cross_checking_list", + "usage_stats", +} +_DEFAULT_FEATURES_BY_PROFILE = { + "document_review": ["home", "documents", "upload", "rules", "rule_groups"], + "contract": ["home", "documents", "upload", "rules", "contract_template_search", "contract_template_list"], + "govdoc": ["home", "govdoc_audits", "govdoc_upload", "rule_groups"], + "cross_checking": ["cross_checking", "cross_checking_upload", "cross_checking_list"], + "custom": ["home", "documents"], +} + + class EntryModuleAdminServiceImpl(IEntryModuleAdminService): """入口模块管理服务实现。""" @@ -39,6 +65,7 @@ class EntryModuleAdminServiceImpl(IEntryModuleAdminService): self.TenantResolver = TenantResolver() self._tenant_table_exists_cache: bool | None = None self._entry_module_tenant_table_exists_cache: bool | None = None + self._entry_module_menu_columns_exist_cache: bool | None = None async def ListModules( self, @@ -128,6 +155,8 @@ class EntryModuleAdminServiceImpl(IEntryModuleAdminService): if has_tenant_mapping_table else "'[]'::jsonb AS tenants" ) + menu_select_sql = await self._entry_module_menu_select_sql() + business_scope_select_sql = self._entry_module_business_scope_select_sql() async with GetAsyncSession() as session: total = int( @@ -154,6 +183,8 @@ class EntryModuleAdminServiceImpl(IEntryModuleAdminService): em.is_enabled, em.created_at, em.updated_at, + {menu_select_sql}, + {business_scope_select_sql}, {tenant_select_sql} FROM leaudit_entry_modules em WHERE {where_clause} @@ -182,29 +213,37 @@ class EntryModuleAdminServiceImpl(IEntryModuleAdminService): ) self._ensureTenantAssignments(normalized_tenants) legacy_areas_json = self._legacyAreasJson(normalized_tenants) + menu_profile = self._normalizeMenuProfile(Body.menu_profile) + features = self._normalizeFeatures(Body.features, menu_profile) + has_menu_columns = await self._entry_module_menu_columns_exist() async with GetAsyncSession() as session: try: + menu_insert_columns = ", menu_profile, features" if has_menu_columns else "" + menu_insert_values = ", :menu_profile, CAST(:features AS jsonb)" if has_menu_columns else "" + params = { + "name": Body.name.strip(), + "description": (Body.description or "").strip() or None, + "route_path": route_path, + "icon_path": None, + "areas": legacy_areas_json, + "sort_order": await self._nextSortOrder(session), + "menu_profile": menu_profile, + "features": json.dumps(features, ensure_ascii=False), + } row = ( await session.execute( text( - """ + f""" INSERT INTO leaudit_entry_modules ( - name, description, path, icon_path, areas, sort_order, is_enabled, created_at, updated_at, deleted_at + name, description, path, icon_path, areas, sort_order, is_enabled, created_at, updated_at, deleted_at{menu_insert_columns} ) VALUES ( - :name, :description, :route_path, :icon_path, CAST(:areas AS jsonb), :sort_order, TRUE, NOW(), NOW(), NULL + :name, :description, :route_path, :icon_path, CAST(:areas AS jsonb), :sort_order, TRUE, NOW(), NOW(), NULL{menu_insert_values} ) RETURNING id """ ), - { - "name": Body.name.strip(), - "description": (Body.description or "").strip() or None, - "route_path": route_path, - "icon_path": None, - "areas": legacy_areas_json, - "sort_order": await self._nextSortOrder(session), - }, + params, ) ).mappings().one() module_id = int(row["id"]) @@ -226,12 +265,30 @@ class EntryModuleAdminServiceImpl(IEntryModuleAdminService): else: normalized_tenants = await self._extractTenantsFromRow(current) self._ensureTenantAssignments(normalized_tenants) + current_menu_profile = self._safeMenuProfile(current.get("menu_profile")) + menu_profile = self._normalizeMenuProfile(Body.menu_profile) if Body.menu_profile is not None else current_menu_profile + features = ( + self._normalizeFeatures(Body.features, menu_profile) + if Body.features is not None + else self._parseFeatures(current.get("features"), menu_profile) + ) + has_menu_columns = await self._entry_module_menu_columns_exist() async with GetAsyncSession() as session: + menu_update_sql = ", menu_profile = :menu_profile, features = CAST(:features AS jsonb)" if has_menu_columns else "" + params = { + "module_id": ModuleId, + "name": Body.name.strip() if Body.name is not None else current["name"], + "description": Body.description.strip() if Body.description is not None else current.get("description"), + "route_path": incoming_route_path.strip() if incoming_route_path is not None else current.get("path"), + "areas": self._legacyAreasJson(normalized_tenants), + "menu_profile": menu_profile, + "features": json.dumps(features, ensure_ascii=False), + } row = ( await session.execute( text( - """ + f""" UPDATE leaudit_entry_modules SET name = :name, @@ -239,18 +296,13 @@ class EntryModuleAdminServiceImpl(IEntryModuleAdminService): path = :route_path, areas = CAST(:areas AS jsonb), updated_at = NOW() + {menu_update_sql} WHERE id = :module_id AND deleted_at IS NULL RETURNING id """ ), - { - "module_id": ModuleId, - "name": Body.name.strip() if Body.name is not None else current["name"], - "description": Body.description.strip() if Body.description is not None else current.get("description"), - "route_path": incoming_route_path.strip() if incoming_route_path is not None else current.get("path"), - "areas": self._legacyAreasJson(normalized_tenants), - }, + params, ) ).mappings().first() if not row: @@ -341,6 +393,8 @@ class EntryModuleAdminServiceImpl(IEntryModuleAdminService): if has_tenant_mapping_table else "'[]'::jsonb AS tenants" ) + menu_select_sql = await self._entry_module_menu_select_sql() + business_scope_select_sql = self._entry_module_business_scope_select_sql() async with GetAsyncSession() as session: row = ( await session.execute( @@ -357,6 +411,8 @@ class EntryModuleAdminServiceImpl(IEntryModuleAdminService): em.is_enabled, em.created_at, em.updated_at, + {menu_select_sql}, + {business_scope_select_sql}, {tenant_select_sql} FROM leaudit_entry_modules em WHERE em.id = :module_id @@ -386,9 +442,12 @@ class EntryModuleAdminServiceImpl(IEntryModuleAdminService): description=Row.get("description"), path=Row.get("icon_path"), route_path=Row.get("path"), + menu_profile=self._safeMenuProfile(Row.get("menu_profile")), + features=self._parseFeatures(Row.get("features"), Row.get("menu_profile")), sort_order=int(Row.get("sort_order") or 0), is_enabled=bool(Row.get("is_enabled", True)), tenants=tenants, + business_scope=self._parseBusinessScope(Row.get("business_scope")), created_at=self._toIso(Row.get("created_at")), updated_at=self._toIso(Row.get("updated_at")), ) @@ -568,6 +627,102 @@ class EntryModuleAdminServiceImpl(IEntryModuleAdminService): self._entry_module_tenant_table_exists_cache = exists return exists + async def _entry_module_menu_columns_exist(self) -> bool: + if self._entry_module_menu_columns_exist_cache is not None: + return self._entry_module_menu_columns_exist_cache + async with GetAsyncSession() as session: + count = int( + ( + await session.execute( + text( + """ + SELECT COUNT(*) + FROM information_schema.columns + WHERE table_schema = current_schema() + AND table_name = 'leaudit_entry_modules' + AND column_name IN ('menu_profile', 'features') + """ + ) + ) + ).scalar_one() + ) + self._entry_module_menu_columns_exist_cache = count == 2 + return self._entry_module_menu_columns_exist_cache + + async def _entry_module_menu_select_sql(self) -> str: + if await self._entry_module_menu_columns_exist(): + return "em.menu_profile, em.features" + return "'document_review'::varchar AS menu_profile, '[]'::jsonb AS features" + + def _entry_module_business_scope_select_sql(self) -> str: + return """ + COALESCE( + ( + SELECT jsonb_build_object( + 'category_count', COUNT(DISTINCT dt.id), + 'business_type_count', GREATEST( + COUNT(DISTINCT child_by_type.id), + COALESCE( + ( + SELECT COUNT(DISTINCT child_by_entry.id) + FROM leaudit_evaluation_point_groups root_by_entry + JOIN leaudit_evaluation_point_groups child_by_entry + ON child_by_entry.pid = root_by_entry.id + AND child_by_entry.deleted_at IS NULL + WHERE root_by_entry.entry_module_id = em.id + AND root_by_entry.pid = 0 + AND root_by_entry.deleted_at IS NULL + ), + 0 + ) + ), + 'categories', COALESCE( + ( + SELECT jsonb_agg(category_name ORDER BY category_name) + FROM ( + SELECT DISTINCT dt2.name AS category_name + FROM leaudit_document_types dt2 + WHERE dt2.entry_module_id = em.id + AND dt2.deleted_at IS NULL + AND dt2.is_enabled = TRUE + ) category_rows + ), + '[]'::jsonb + ) + ) + FROM leaudit_document_types dt + LEFT JOIN leaudit_evaluation_point_groups root + ON root.document_type_id = dt.id + AND root.pid = 0 + AND root.deleted_at IS NULL + LEFT JOIN leaudit_evaluation_point_groups child_by_type + ON child_by_type.pid = root.id + AND child_by_type.deleted_at IS NULL + WHERE dt.entry_module_id = em.id + AND dt.deleted_at IS NULL + AND dt.is_enabled = TRUE + ), + jsonb_build_object('category_count', 0, 'business_type_count', 0, 'categories', '[]'::jsonb) + ) AS business_scope + """ + + def _parseBusinessScope(self, RawValue: object) -> EntryModuleBusinessScopeVO: + if isinstance(RawValue, str): + try: + RawValue = json.loads(RawValue) + except json.JSONDecodeError: + RawValue = {} + if not isinstance(RawValue, dict): + RawValue = {} + + categories_raw = RawValue.get("categories") or [] + categories = [str(item).strip() for item in categories_raw if str(item or "").strip()] if isinstance(categories_raw, list) else [] + return EntryModuleBusinessScopeVO( + category_count=int(RawValue.get("category_count") or len(categories)), + business_type_count=int(RawValue.get("business_type_count") or 0), + categories=categories, + ) + async def _resolveLegacyTenantValue(self, *, RawValue: str, Source: str) -> TenantResolution: resolution = await self.TenantResolver.Resolve( RawValue=RawValue, @@ -705,6 +860,65 @@ class EntryModuleAdminServiceImpl(IEntryModuleAdminService): "入口模块至少需要配置一个适用租户", ) + @staticmethod + def _normalizeMenuProfile(MenuProfile: str | None) -> str: + value = str(MenuProfile or "document_review").strip() or "document_review" + if value not in _ALLOWED_MENU_PROFILES: + raise LeauditException( + StatusCodeEnum.HTTP_400_BAD_REQUEST, + f"不支持的菜单模板: {value}", + ) + return value + + @staticmethod + def _safeMenuProfile(MenuProfile: str | None) -> str: + value = str(MenuProfile or "document_review").strip() or "document_review" + return value if value in _ALLOWED_MENU_PROFILES else "document_review" + + @staticmethod + def _normalizeFeatures(Features: list[str] | None, MenuProfile: str) -> list[str]: + raw_features = Features if Features else _DEFAULT_FEATURES_BY_PROFILE.get(MenuProfile, _DEFAULT_FEATURES_BY_PROFILE["document_review"]) + normalized: list[str] = [] + invalid: list[str] = [] + for item in raw_features: + feature = str(item or "").strip() + if not feature: + continue + if feature not in _ALLOWED_FEATURES: + invalid.append(feature) + continue + if feature not in normalized: + normalized.append(feature) + if invalid: + raise LeauditException( + StatusCodeEnum.HTTP_400_BAD_REQUEST, + f"不支持的功能编码: {', '.join(invalid)}", + ) + return normalized or list(_DEFAULT_FEATURES_BY_PROFILE.get(MenuProfile, _DEFAULT_FEATURES_BY_PROFILE["document_review"])) + + @classmethod + def _parseFeatures(cls, RawFeatures: Any, MenuProfile: str | None) -> list[str]: + menu_profile = cls._safeMenuProfile(MenuProfile) + if isinstance(RawFeatures, list): + return cls._filterFeatures([str(item) for item in RawFeatures], menu_profile) + if isinstance(RawFeatures, str) and RawFeatures.strip(): + try: + parsed = json.loads(RawFeatures) + except json.JSONDecodeError: + parsed = [] + if isinstance(parsed, list): + return cls._filterFeatures([str(item) for item in parsed], menu_profile) + return list(_DEFAULT_FEATURES_BY_PROFILE.get(menu_profile, _DEFAULT_FEATURES_BY_PROFILE["document_review"])) + + @staticmethod + def _filterFeatures(Features: list[str], MenuProfile: str) -> list[str]: + normalized: list[str] = [] + for item in Features: + feature = str(item or "").strip() + if feature in _ALLOWED_FEATURES and feature not in normalized: + normalized.append(feature) + return normalized or list(_DEFAULT_FEATURES_BY_PROFILE.get(MenuProfile, _DEFAULT_FEATURES_BY_PROFILE["document_review"])) + @staticmethod def _toIso(Value) -> str | None: """时间转 ISO 字符串。""" diff --git a/fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py b/fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py index b896bfe..6a33d87 100644 --- a/fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py +++ b/fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py @@ -62,6 +62,7 @@ class EvaluationPointGroupServiceImpl(IEvaluationPointGroupService): Page: int, PageSize: int, CurrentUserId: int, + EntryModuleId: int | None = None, ) -> EvaluationPointGroupListVO: async with GetAsyncSession() as session: await self._ensure_ready(session) @@ -97,6 +98,10 @@ class EvaluationPointGroupServiceImpl(IEvaluationPointGroupService): if Pid is not None: filters.append("COALESCE(g.pid, 0) = :pid") params["pid"] = self._normalize_pid(Pid) + if EntryModuleId is not None: + await self._assert_entry_module_access(session, EntryModuleId, current_user) + filters.append("COALESCE(g.entry_module_id, parent.entry_module_id, dt.entry_module_id) = :entry_module_id") + params["entry_module_id"] = int(EntryModuleId) where_clause = " AND ".join(filters) total = int( @@ -149,7 +154,13 @@ class EvaluationPointGroupServiceImpl(IEvaluationPointGroupService): page_size=PageSize, ) - async def ListAllGroups(self, IncludeDisabled: bool, WithRuleCount: bool, CurrentUserId: int) -> list[EvaluationPointGroupVO]: + async def ListAllGroups( + self, + IncludeDisabled: bool, + WithRuleCount: bool, + CurrentUserId: int, + EntryModuleId: int | None = None, + ) -> list[EvaluationPointGroupVO]: async with GetAsyncSession() as session: await self._ensure_ready(session) current_user = await self._get_current_user_context(session, CurrentUserId) @@ -166,6 +177,10 @@ class EvaluationPointGroupServiceImpl(IEvaluationPointGroupService): ) if not IncludeDisabled: filters.append("g.is_enabled = TRUE") + if EntryModuleId is not None: + await self._assert_entry_module_access(session, EntryModuleId, current_user) + filters.append("COALESCE(g.entry_module_id, parent.entry_module_id, dt.entry_module_id) = :entry_module_id") + params["entry_module_id"] = int(EntryModuleId) rows = ( await session.execute( text( @@ -233,6 +248,7 @@ class EvaluationPointGroupServiceImpl(IEvaluationPointGroupService): IncludeDisabled=IncludeDisabled, WithRuleCount=WithRuleCount, CurrentUserId=CurrentUserId, + EntryModuleId=None, ) result: list[EvaluationPointGroupVO] = [] for root in roots: @@ -262,6 +278,7 @@ class EvaluationPointGroupServiceImpl(IEvaluationPointGroupService): Page=Page, PageSize=PageSize, CurrentUserId=CurrentUserId, + EntryModuleId=None, ) async def CreateGroup(self, Body: EvaluationPointGroupCreateDTO, CurrentUserId: int) -> EvaluationPointGroupVO: @@ -760,7 +777,7 @@ class EvaluationPointGroupServiceImpl(IEvaluationPointGroupService): row = ( await session.execute( text( - """ + f""" SELECT rgb.id, rgb.group_id, @@ -798,30 +815,43 @@ class EvaluationPointGroupServiceImpl(IEvaluationPointGroupService): group_ids = [int(item) for item in group_ids if item] if not group_ids: return {} + params: dict[str, Any] = {"group_ids": group_ids} + tenant_filter = "" + if current_user is not None and not current_user.get("is_global"): + visible_tenant_codes = ["PUBLIC", "PROVINCIAL"] + tenant_code = normalize_scoped_tenant_code(str(current_user.get("tenant_code") or ""), default="") + if tenant_code: + visible_tenant_codes.append(tenant_code) + tenant_filter = """ + AND COALESCE(NULLIF(BTRIM(tenant_code), ''), 'PROVINCIAL') IN :visible_tenant_codes + """ + params["visible_tenant_codes"] = sorted(set(visible_tenant_codes)) + query = text( + f""" + SELECT + id, + group_id, + rule_set_id, + rule_type_binding_id, + COALESCE(NULLIF(BTRIM(tenant_code), ''), 'PROVINCIAL') AS tenant_code, + COALESCE(NULLIF(BTRIM(scope_type), ''), 'PROVINCIAL') AS scope_type, + tenant_name_snapshot, + priority, + is_active, + note, + created_at, + updated_at + FROM leaudit_rule_group_bindings + WHERE group_id IN :group_ids + AND deleted_at IS NULL + {tenant_filter} + ORDER BY priority DESC, id ASC + """ + ).bindparams(bindparam("group_ids", expanding=True)) + if "visible_tenant_codes" in params: + query = query.bindparams(bindparam("visible_tenant_codes", expanding=True)) rows = ( - await session.execute( - text( - """ - SELECT - id, - group_id, - rule_set_id, - rule_type_binding_id, - COALESCE(NULLIF(BTRIM(tenant_code), ''), 'PROVINCIAL') AS tenant_code, - COALESCE(NULLIF(BTRIM(scope_type), ''), 'PROVINCIAL') AS scope_type, - tenant_name_snapshot, - priority, - is_active, - note, - created_at, - updated_at - FROM leaudit_rule_group_bindings - WHERE group_id IN :group_ids AND deleted_at IS NULL - ORDER BY priority DESC, id ASC - """ - ).bindparams(bindparam("group_ids", expanding=True)), - {"group_ids": group_ids}, - ) + await session.execute(query, params) ).mappings().all() result: dict[int, list[RuleGroupBindingVO]] = {} for row in rows: diff --git a/fastapi_modules/fastapi_leaudit/services/impl/govdocServiceImpl.py b/fastapi_modules/fastapi_leaudit/services/impl/govdocServiceImpl.py index 069dde4..10b18f8 100644 --- a/fastapi_modules/fastapi_leaudit/services/impl/govdocServiceImpl.py +++ b/fastapi_modules/fastapi_leaudit/services/impl/govdocServiceImpl.py @@ -101,6 +101,7 @@ class GovdocServiceImpl(IGovdocService): self, file: UploadFile, typeId: int | None = None, + entryModuleId: int | None = None, region: str | None = None, tenantCode: str | None = None, autoRun: bool = True, @@ -229,11 +230,15 @@ class GovdocServiceImpl(IGovdocService): text( """ UPDATE leaudit_documents - SET engine_type = 'govdoc' + SET engine_type = 'govdoc', + entry_module_id = COALESCE(:entry_module_id, entry_module_id) WHERE id = :document_id """ ), - {"document_id": document.Id}, + { + "document_id": document.Id, + "entry_module_id": int(entryModuleId) if entryModuleId is not None and int(entryModuleId) > 0 else None, + }, ) await session.commit() @@ -273,6 +278,8 @@ class GovdocServiceImpl(IGovdocService): fileExt: str | None = None, region: str | None = None, tenantCode: str | None = None, + entryModuleId: int | None = None, + typeIds: list[int] | None = None, status: str | None = None, resultStatus: str | None = None, createdBy: int | None = None, @@ -327,6 +334,13 @@ class GovdocServiceImpl(IGovdocService): if normalizedExt: filters.append("LOWER(COALESCE(f.file_ext, '')) = :file_ext") params["file_ext"] = normalizedExt + normalizedTypeIds = [int(typeId) for typeId in (typeIds or []) if int(typeId) > 0] + if normalizedTypeIds: + filters.append("d.type_id = ANY(:type_ids)") + params["type_ids"] = normalizedTypeIds + if entryModuleId is not None and int(entryModuleId) > 0: + filters.append("COALESCE(d.entry_module_id, dt.entry_module_id) = :entry_module_id") + params["entry_module_id"] = int(entryModuleId) if status: filters.append("COALESCE(d.processing_status, '') = :status") params["status"] = status.strip() @@ -420,6 +434,8 @@ class GovdocServiceImpl(IGovdocService): AND f.deleted_at IS NULL LEFT JOIN govdoc_runs gr ON gr.id = d.current_run_id + LEFT JOIN leaudit_document_types dt + ON dt.id = d.type_id LEFT JOIN ( SELECT run_id, @@ -1236,6 +1252,10 @@ class GovdocServiceImpl(IGovdocService): """, """ ALTER TABLE leaudit_documents + ADD COLUMN IF NOT EXISTS entry_module_id BIGINT + """, + """ + ALTER TABLE leaudit_documents ADD COLUMN IF NOT EXISTS engine_type VARCHAR(32) NOT NULL DEFAULT 'leaudit' """, """ diff --git a/fastapi_modules/fastapi_leaudit/services/impl/homeServiceImpl.py b/fastapi_modules/fastapi_leaudit/services/impl/homeServiceImpl.py index 89d9f01..9e79fa5 100644 --- a/fastapi_modules/fastapi_leaudit/services/impl/homeServiceImpl.py +++ b/fastapi_modules/fastapi_leaudit/services/impl/homeServiceImpl.py @@ -2,6 +2,10 @@ from __future__ import annotations +from datetime import date, datetime +import json +from typing import Any + from sqlalchemy import text from fastapi_common.fastapi_common_sqlalchemy.database import GetAsyncSession @@ -9,12 +13,16 @@ from fastapi_common.fastapi_common_web.domain.responses import StatusCodeEnum from fastapi_common.fastapi_common_web.exception.LeauditException import LeauditException from fastapi_modules.fastapi_leaudit.domian.vo.homeVo import ( + HomeDashboardGrowthVO, + HomeDashboardStatisticsVO, HomeEntryAreaVO, HomeEntryDocumentTypeVO, HomeEntryModuleVO, HomeEntryTenantVO, ) +from fastapi_modules.fastapi_leaudit.services import IDocumentService from fastapi_modules.fastapi_leaudit.services.homeService import IHomeService +from fastapi_modules.fastapi_leaudit.services.impl.documentServiceImpl import DocumentServiceImpl from fastapi_modules.fastapi_leaudit.services.impl.rbacServiceImpl import RbacServiceImpl from fastapi_modules.fastapi_leaudit.services.impl.ssoUserCompat import SsoUserCompat from fastapi_modules.fastapi_leaudit.services.impl.tenantResolver import TenantResolution, TenantResolver @@ -29,17 +37,34 @@ class HomeServiceImpl(IHomeService): "/documents", "/chat-with-llm/chat", "/cross-checking", + "/govdoc", + "/govdoc/home", + "/govdoc/audits", + "/govdoc/upload", + "/govdoc-audit", + "/govdoc-audit/home", + "/govdoc-audit/audits", + "/govdoc-audit/upload", ) _DOCUMENT_ENTRY_TARGETS: tuple[str, ...] = ( "/files/upload", "/documents", "/documents/list", ) + _DEFAULT_FEATURES_BY_PROFILE: dict[str, list[str]] = { + "document_review": ["home", "documents", "upload", "rules", "rule_groups"], + "contract": ["home", "documents", "upload", "rules", "contract_template_search", "contract_template_list"], + "govdoc": ["home", "govdoc_audits", "govdoc_upload", "rule_groups"], + "cross_checking": ["cross_checking", "cross_checking_upload", "cross_checking_list"], + "custom": ["home", "documents"], + } - def __init__(self) -> None: + def __init__(self, DocumentService: IDocumentService | None = None) -> None: self.RbacService = RbacServiceImpl() self.TenantResolver = TenantResolver() + self.DocumentService = DocumentService or DocumentServiceImpl() self._entry_module_tenant_table_exists_cache: bool | None = None + self._entry_module_menu_columns_exist_cache: bool | None = None async def GetEntryModules(self, UserId: int) -> list[HomeEntryModuleVO]: """获取当前用户可见的首页入口模块。""" @@ -125,6 +150,9 @@ class HomeServiceImpl(IHomeService): if has_tenant_mapping_table else "'[]'::jsonb AS tenants" ) + has_menu_columns = await self._entry_module_menu_columns_exist() + menu_select_sql = self._entry_module_menu_select_sql(has_menu_columns) + menu_group_by_sql = ",\n em.menu_profile,\n em.features" if has_menu_columns else "" tenant_scope_filter_sql = ( """ ( @@ -195,6 +223,7 @@ class HomeServiceImpl(IHomeService): em.icon_path, em.areas, em.sort_order, + {menu_select_sql}, {tenant_select_sql}, COALESCE( json_agg( @@ -225,6 +254,7 @@ class HomeServiceImpl(IHomeService): em.icon_path, em.areas, em.sort_order + {menu_group_by_sql} ORDER BY em.sort_order ASC, em.id ASC """ ), @@ -311,6 +341,9 @@ class HomeServiceImpl(IHomeService): description=row["description"], targetPath=target_path, routePath=target_path, + menuProfile=self._normalizeMenuProfile(row.get("menu_profile")), + features=self._parseFeatures(row.get("features"), row.get("menu_profile")), + tenantCode=effective_tenant_code or None, iconPath=row["icon_path"], sortOrder=int(row["sort_order"] or 0), requiresDocumentTypes=requires_document_types, @@ -322,6 +355,95 @@ class HomeServiceImpl(IHomeService): return modules + async def GetDashboardStatistics( + self, + UserId: int, + Today: str | None = None, + TypeIds: list[int] | None = None, + EntryModuleId: int | None = None, + ) -> HomeDashboardStatisticsVO: + """获取当前业务入口的首页统计卡片数据。""" + today = date.fromisoformat(Today) if Today else date.today() + current_month_start = today.replace(day=1) + previous_month_end = current_month_start.fromordinal(current_month_start.toordinal() - 1) + previous_month_start = previous_month_end.replace(day=1) + normalized_type_ids = [int(typeId) for typeId in (TypeIds or []) if int(typeId) > 0] + normalized_entry_module_id = int(EntryModuleId) if EntryModuleId and int(EntryModuleId) > 0 else None + + async def load_documents() -> list[Any]: + page = 1 + documents: list[Any] = [] + while True: + result = await self.DocumentService.ListDocuments( + CurrentUserId=UserId, + Page=page, + PageSize=100, + TypeIds=normalized_type_ids or None, + EntryModuleId=normalized_entry_module_id, + ) + documents.extend(result.documents) + total_pages = max(1, int(result.totalPages or 1)) + if page >= total_pages: + return documents + page += 1 + + def issue_count(Documents: list[Any]) -> int: + return sum(max(0, int(document.failedCount or 0)) for document in Documents) + + def pass_rate(Documents: list[Any]) -> int: + if not Documents: + return 0 + passed_count = len([document for document in Documents if int(document.failedCount or 0) == 0]) + return round((passed_count / len(Documents)) * 100) + + def growth(Current: int, Previous: int) -> HomeDashboardGrowthVO: + if Previous <= 0: + return HomeDashboardGrowthVO(value=100 if Current > 0 else 0, isUp=Current >= Previous) + return HomeDashboardGrowthVO( + value=round(abs(((Current - Previous) / Previous) * 100)), + isUp=Current >= Previous, + ) + + def document_date(Document: Any) -> date | None: + raw_value = str(getattr(Document, "updatedAt", "") or "").strip() + if not raw_value: + return None + normalized = raw_value.replace("Z", "+00:00") + try: + return datetime.fromisoformat(normalized).date() + except ValueError: + try: + return datetime.strptime(raw_value, "%Y-%m-%d %H:%M:%S").date() + except ValueError: + return None + + def in_range(Document: Any, DateFrom: date, DateTo: date) -> bool: + current_date = document_date(Document) + return current_date is not None and DateFrom <= current_date <= DateTo + + documents = await load_documents() + today_documents = [document for document in documents if in_range(document, today, today)] + current_month_documents = [document for document in documents if in_range(document, current_month_start, today)] + previous_month_documents = [ + document for document in documents if in_range(document, previous_month_start, previous_month_end) + ] + current_reviewed_documents = [document for document in current_month_documents if int(document.auditStatus or 0) == 1] + previous_reviewed_documents = [document for document in previous_month_documents if int(document.auditStatus or 0) == 1] + current_pass_rate = pass_rate(current_reviewed_documents) + previous_pass_rate = pass_rate(previous_reviewed_documents) + current_issue_count = issue_count(current_reviewed_documents) + previous_issue_count = issue_count(previous_reviewed_documents) + + return HomeDashboardStatisticsVO( + todayPendingFiles=len([document for document in today_documents if int(document.auditStatus or 0) != 1]), + monthlyReviewedFiles=len(current_reviewed_documents), + monthlyReviewGrowth=growth(len(current_reviewed_documents), len(previous_reviewed_documents)), + monthlyPassRate=current_pass_rate, + passRateGrowth=growth(current_pass_rate, previous_pass_rate), + issuesDetected=current_issue_count, + issuesGrowth=growth(current_issue_count, previous_issue_count), + ) + async def _entry_module_tenant_table_exists(self) -> bool: if self._entry_module_tenant_table_exists_cache is not None: return self._entry_module_tenant_table_exists_cache @@ -345,6 +467,75 @@ class HomeServiceImpl(IHomeService): self._entry_module_tenant_table_exists_cache = exists return exists + async def _entry_module_menu_columns_exist(self) -> bool: + if self._entry_module_menu_columns_exist_cache is not None: + return self._entry_module_menu_columns_exist_cache + async with GetAsyncSession() as session: + count = int( + ( + await session.execute( + text( + """ + SELECT COUNT(*) + FROM information_schema.columns + WHERE table_schema = current_schema() + AND table_name = 'leaudit_entry_modules' + AND column_name IN ('menu_profile', 'features') + """ + ) + ) + ).scalar_one() + ) + self._entry_module_menu_columns_exist_cache = count == 2 + return self._entry_module_menu_columns_exist_cache + + @staticmethod + def _entry_module_menu_select_sql(HasMenuColumns: bool) -> str: + if HasMenuColumns: + return "em.menu_profile, em.features" + return "'document_review'::varchar AS menu_profile, '[]'::jsonb AS features" + + @classmethod + def _normalizeMenuProfile(cls, MenuProfile: str | None) -> str: + value = str(MenuProfile or "document_review").strip() or "document_review" + if value not in cls._DEFAULT_FEATURES_BY_PROFILE: + return "document_review" + return value + + @classmethod + def _parseFeatures(cls, RawFeatures: Any, MenuProfile: str | None) -> list[str]: + menu_profile = cls._normalizeMenuProfile(MenuProfile) + allowed_features = { + "home", + "documents", + "upload", + "rules", + "rule_groups", + "contract_template_search", + "contract_template_list", + "govdoc_audits", + "govdoc_upload", + "cross_checking", + "cross_checking_upload", + "cross_checking_list", + "usage_stats", + } + if isinstance(RawFeatures, list): + parsed = RawFeatures + elif isinstance(RawFeatures, str) and RawFeatures.strip(): + try: + parsed = json.loads(RawFeatures) + except json.JSONDecodeError: + parsed = [] + else: + parsed = [] + normalized: list[str] = [] + for item in parsed: + feature = str(item or "").strip() + if feature in allowed_features and feature not in normalized: + normalized.append(feature) + return normalized or list(cls._DEFAULT_FEATURES_BY_PROFILE[menu_profile]) + async def _resolveLegacyTenantValue(self, *, RawValue: str, Source: str) -> TenantResolution: resolution = await self.TenantResolver.Resolve( RawValue=RawValue, diff --git a/fastapi_modules/fastapi_leaudit/services/impl/qichachaClient.py b/fastapi_modules/fastapi_leaudit/services/impl/qichachaClient.py new file mode 100644 index 0000000..d58ce92 --- /dev/null +++ b/fastapi_modules/fastapi_leaudit/services/impl/qichachaClient.py @@ -0,0 +1,119 @@ +"""企查查 HTTP 客户端。""" + +from __future__ import annotations + +import asyncio +import hashlib +import time +from typing import Any + +import httpx + +from fastapi_admin.config import ( + QICHACHA_APP_KEY, + QICHACHA_BASE_URL, + QICHACHA_DISHONESTY_PATH, + QICHACHA_ENTERPRISE_PATH, + QICHACHA_MAX_RETRIES, + QICHACHA_RETRY_DELAY, + QICHACHA_SECRET_KEY, + QICHACHA_TIMEOUT, +) +from fastapi_common.fastapi_common_logger import logger +from fastapi_common.fastapi_common_web.domain.responses import StatusCodeEnum +from fastapi_common.fastapi_common_web.exception.QichachaException import QichachaException + + +class QichachaClient: + """企查查 HTTP 客户端。""" + + def __init__( + self, + AppKey: str | None = None, + SecretKey: str | None = None, + BaseUrl: str | None = None, + EnterprisePath: str | None = None, + DishonestyPath: str | None = None, + Timeout: int | None = None, + MaxRetries: int | None = None, + RetryDelay: float | None = None, + ) -> None: + """初始化客户端配置。""" + self.AppKey = AppKey if AppKey is not None else str(QICHACHA_APP_KEY) + self.SecretKey = SecretKey if SecretKey is not None else str(QICHACHA_SECRET_KEY) + self.BaseUrl = (BaseUrl if BaseUrl is not None else str(QICHACHA_BASE_URL)).rstrip("/") + self.EnterprisePath = EnterprisePath if EnterprisePath is not None else str(QICHACHA_ENTERPRISE_PATH) + self.DishonestyPath = DishonestyPath if DishonestyPath is not None else str(QICHACHA_DISHONESTY_PATH) + self.Timeout = Timeout if Timeout is not None else int(QICHACHA_TIMEOUT) + self.MaxRetries = MaxRetries if MaxRetries is not None else int(QICHACHA_MAX_RETRIES) + self.RetryDelay = RetryDelay if RetryDelay is not None else float(QICHACHA_RETRY_DELAY) + + def BuildHeaders(self) -> dict[str, str]: + """生成企查查鉴权请求头。""" + timespan = str(int(time.time())) + token_source = f"{self.AppKey}{timespan}{self.SecretKey}" + token = hashlib.md5(token_source.encode("utf-8")).hexdigest().upper() + return {"Token": token, "Timespan": timespan} + + async def Request(self, Url: str, Params: dict[str, str]) -> dict[str, Any]: + """发送企查查 GET 请求并返回 JSON。""" + if not self.AppKey: + raise QichachaException(StatusCodeEnum.HTTP_500_INTERNAL_SERVER_ERROR, "企查查 APP_KEY 未配置") + if not self.SecretKey: + raise QichachaException(StatusCodeEnum.HTTP_500_INTERNAL_SERVER_ERROR, "企查查 SECRET_KEY 未配置") + + last_error: Exception | None = None + for attempt in range(max(self.MaxRetries, 1)): + try: + async with httpx.AsyncClient(timeout=self.Timeout) as client: + response = await client.get(Url, params=Params, headers=self.BuildHeaders()) + response.raise_for_status() + data = response.json() + if not isinstance(data, dict): + raise QichachaException(StatusCodeEnum.HTTP_500_INTERNAL_SERVER_ERROR, "企查查响应格式错误") + status = str(data.get("Status") or "") + if status and status not in {"200", "201"}: + message = str(data.get("Message") or "企查查查询失败") + raise QichachaException(StatusCodeEnum.HTTP_400_BAD_REQUEST, message) + return data + except QichachaException: + raise + except Exception as exc: + last_error = exc + if attempt < max(self.MaxRetries, 1) - 1: + await asyncio.sleep(self.RetryDelay) + + logger.error(f"企查查请求失败: url={Url}, error={last_error}") + raise QichachaException(StatusCodeEnum.HTTP_500_INTERNAL_SERVER_ERROR, f"企查查请求失败: {last_error}") + + async def GetEnterpriseInfo(self, Keyword: str) -> dict[str, Any] | None: + """查询企业工商信息。""" + data = await self.Request( + Url=f"{self.BaseUrl}{self.EnterprisePath}", + Params={"key": self.AppKey, "keyword": Keyword}, + ) + result = data.get("Result") + return result if isinstance(result, dict) else None + + async def GetDishonestyInfo(self, Keyword: str) -> dict[str, Any] | None: + """查询企业失信信息。""" + data = await self.Request( + Url=f"{self.BaseUrl}{self.DishonestyPath}", + Params={"key": self.AppKey, "searchKey": Keyword}, + ) + result = data.get("Result") + return result if isinstance(result, dict) else None + + async def QueryCompany(self, Keyword: str) -> tuple[dict[str, Any] | None, dict[str, Any] | None, str | None, str | None]: + """并发查询工商信息与失信信息。""" + enterprise_result, dishonesty_result = await asyncio.gather( + self.GetEnterpriseInfo(Keyword), + self.GetDishonestyInfo(Keyword), + ) + credit_code = ( + str(enterprise_result.get("CreditCode")) + if enterprise_result and enterprise_result.get("CreditCode") + else None + ) + company_name = str(enterprise_result.get("Name")) if enterprise_result and enterprise_result.get("Name") else None + return enterprise_result, dishonesty_result, credit_code, company_name diff --git a/fastapi_modules/fastapi_leaudit/services/impl/qichachaServiceImpl.py b/fastapi_modules/fastapi_leaudit/services/impl/qichachaServiceImpl.py new file mode 100644 index 0000000..8769733 --- /dev/null +++ b/fastapi_modules/fastapi_leaudit/services/impl/qichachaServiceImpl.py @@ -0,0 +1,174 @@ +"""企查查服务实现。""" + +from __future__ import annotations + +from fastapi_admin.config import QICHACHA_CACHE_DAYS +from fastapi_common.fastapi_common_logger import logger +from fastapi_common.fastapi_common_sqlalchemy.database import GetAsyncSession +from fastapi_common.fastapi_common_web.domain.responses import StatusCodeEnum +from fastapi_common.fastapi_common_web.exception.QichachaException import QichachaException +from fastapi_modules.fastapi_leaudit.domian.vo.qichachaVo import ( + QichachaBatchQueryVO, + QichachaCompanyQueryVO, + QichachaRecordStatusVO, +) +from fastapi_modules.fastapi_leaudit.models.qichachaCompanyInfo import QichachaCompanyInfo +from fastapi_modules.fastapi_leaudit.services.impl.qichachaClient import QichachaClient +from fastapi_modules.fastapi_leaudit.services.impl.qichachaVoAssembler import QichachaVoAssembler +from fastapi_modules.fastapi_leaudit.services.qichachaService import IQichachaService + + +class QichachaServiceImpl(IQichachaService): + """企查查服务实现。""" + + def __init__(self, Client: QichachaClient | None = None, CacheDays: int | None = None) -> None: + """初始化企查查服务。""" + self.Client = Client if Client is not None else QichachaClient() + self.CacheDays = CacheDays if CacheDays is not None else int(QICHACHA_CACHE_DAYS) + + async def QueryCompany(self, Keyword: str, ForceRefresh: bool = False) -> QichachaCompanyQueryVO: + """查询企业完整信息。""" + keyword = Keyword.strip() + if not keyword: + raise QichachaException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "查询关键词不能为空") + + async with GetAsyncSession() as session: + record = await QichachaCompanyInfo.FindByKeyword(session, keyword) + if record is not None and not ForceRefresh and QichachaCompanyInfo.GetAgeDays(record) <= self.CacheDays: + return QichachaCompanyQueryVO( + success=True, + message="查询成功", + data=QichachaVoAssembler.BuildCompanyInfo(record), + ) + + enterprise, dishonesty, credit_code, company_name = await self.Client.QueryCompany(keyword) + record = await QichachaCompanyInfo.Upsert( + session, + SearchKey=keyword, + CreditCode=credit_code, + CompanyName=company_name, + Enterprise=enterprise, + Dishonesty=dishonesty, + ) + logger.info(f"企查查企业信息已更新: {keyword}") + return QichachaCompanyQueryVO( + success=True, + message="查询成功", + data=QichachaVoAssembler.BuildCompanyInfo(record), + ) + + async def QueryEnterpriseOnly(self, Keyword: str, ForceRefresh: bool = False) -> QichachaCompanyQueryVO: + """仅查询企业工商信息。""" + keyword = Keyword.strip() + async with GetAsyncSession() as session: + record = await QichachaCompanyInfo.FindByKeyword(session, keyword) + if ( + record is not None + and record.enterprise is not None + and not ForceRefresh + and QichachaCompanyInfo.GetAgeDays(record) <= self.CacheDays + ): + return QichachaCompanyQueryVO( + success=True, + message="查询成功", + data=QichachaVoAssembler.BuildCompanyInfo(record), + ) + + enterprise = await self.Client.GetEnterpriseInfo(keyword) + credit_code = str(enterprise.get("CreditCode")) if enterprise and enterprise.get("CreditCode") else None + company_name = str(enterprise.get("Name")) if enterprise and enterprise.get("Name") else None + record = await QichachaCompanyInfo.Upsert( + session, + SearchKey=keyword, + CreditCode=credit_code, + CompanyName=company_name, + Enterprise=enterprise, + Dishonesty=record.dishonesty if record is not None else None, + ) + return QichachaCompanyQueryVO( + success=True, + message="查询成功", + data=QichachaVoAssembler.BuildCompanyInfo(record), + ) + + async def QueryDishonestyOnly(self, Keyword: str, ForceRefresh: bool = False) -> QichachaCompanyQueryVO: + """仅查询企业失信信息。""" + keyword = Keyword.strip() + async with GetAsyncSession() as session: + record = await QichachaCompanyInfo.FindByKeyword(session, keyword) + if ( + record is not None + and record.dishonesty is not None + and not ForceRefresh + and QichachaCompanyInfo.GetAgeDays(record) <= self.CacheDays + ): + return QichachaCompanyQueryVO( + success=True, + message="查询成功", + data=QichachaVoAssembler.BuildCompanyInfo(record), + ) + + dishonesty = await self.Client.GetDishonestyInfo(keyword) + record = await QichachaCompanyInfo.Upsert( + session, + SearchKey=keyword, + CreditCode=record.creditCode if record is not None else None, + CompanyName=record.companyName if record is not None else keyword, + Enterprise=record.enterprise if record is not None else None, + Dishonesty=dishonesty, + ) + return QichachaCompanyQueryVO( + success=True, + message="查询成功", + data=QichachaVoAssembler.BuildCompanyInfo(record), + ) + + async def BatchQuery(self, Keywords: list[str], ForceRefresh: bool = False) -> QichachaBatchQueryVO: + """批量查询企业信息。""" + results: list[QichachaCompanyQueryVO] = [] + for keyword in Keywords: + try: + results.append(await self.QueryCompany(keyword, ForceRefresh)) + except Exception as exc: + results.append( + QichachaCompanyQueryVO( + success=False, + message=str(exc), + data=None, + errorCode="QICHACHA_QUERY_FAILED", + ) + ) + success_count = len([item for item in results if item.success]) + return QichachaBatchQueryVO( + success=success_count == len(results), + total=len(results), + successCount=success_count, + failedCount=len(results) - success_count, + results=results, + ) + + async def GetRecordStatus(self, Keyword: str) -> QichachaRecordStatusVO: + """查询企业缓存状态。""" + keyword = Keyword.strip() + async with GetAsyncSession() as session: + record = await QichachaCompanyInfo.FindByKeyword(session, keyword) + if record is None: + return QichachaRecordStatusVO( + exists=False, + searchKey=keyword, + refreshThresholdDays=self.CacheDays, + needRefresh=True, + ) + age_days = QichachaCompanyInfo.GetAgeDays(record) + return QichachaRecordStatusVO( + exists=True, + searchKey=record.searchKey, + creditCode=record.creditCode, + companyName=record.companyName, + hasEnterprise=record.enterprise is not None, + hasDishonesty=record.dishonesty is not None, + updatedAt=QichachaVoAssembler.FormatDatetime(record.updated_at), + ageDays=age_days, + refreshThresholdDays=self.CacheDays, + needRefresh=age_days > self.CacheDays, + ) diff --git a/fastapi_modules/fastapi_leaudit/services/impl/qichachaVoAssembler.py b/fastapi_modules/fastapi_leaudit/services/impl/qichachaVoAssembler.py new file mode 100644 index 0000000..70d237d --- /dev/null +++ b/fastapi_modules/fastapi_leaudit/services/impl/qichachaVoAssembler.py @@ -0,0 +1,40 @@ +"""企查查 VO 组装器。""" + +from __future__ import annotations + +from datetime import UTC +from typing import Any + +from fastapi_modules.fastapi_leaudit.domian.vo.qichachaVo import QichachaCompanyInfoVO +from fastapi_modules.fastapi_leaudit.models.qichachaCompanyInfo import QichachaCompanyInfo + + +class QichachaVoAssembler: + """企查查 VO 组装器。""" + + @classmethod + def BuildCompanyInfo(cls, record: QichachaCompanyInfo) -> QichachaCompanyInfoVO: + """组装企业信息 VO。""" + dishonesty = record.dishonesty if isinstance(record.dishonesty, dict) else None + data_list = dishonesty.get("Data") if dishonesty else [] + has_dishonesty = bool(dishonesty and int(dishonesty.get("VerifyResult") or 0) == 1) + dishonesty_count = len(data_list) if isinstance(data_list, list) else 0 + return QichachaCompanyInfoVO( + searchKey=record.searchKey, + creditCode=record.creditCode, + companyName=record.companyName, + enterprise=record.enterprise if isinstance(record.enterprise, dict) else None, + dishonesty=dishonesty, + hasDishonesty=has_dishonesty, + dishonestyCount=dishonesty_count, + updatedAt=cls.FormatDatetime(record.updated_at), + ) + + @classmethod + def FormatDatetime(cls, value: Any) -> str | None: + """格式化时间。""" + if value is None: + return None + if getattr(value, "tzinfo", None) is None: + value = value.replace(tzinfo=UTC) + return value.isoformat() diff --git a/fastapi_modules/fastapi_leaudit/services/impl/rbacAdminServiceImpl.py b/fastapi_modules/fastapi_leaudit/services/impl/rbacAdminServiceImpl.py index 06461c4..8130d5e 100644 --- a/fastapi_modules/fastapi_leaudit/services/impl/rbacAdminServiceImpl.py +++ b/fastapi_modules/fastapi_leaudit/services/impl/rbacAdminServiceImpl.py @@ -135,6 +135,41 @@ class RbacAdminServiceImpl(IRbacAdminService): "is_cache": True, "meta": {"group": "cross-review"}, }, + { + "route_path": "/govdoc", + "route_name": "govdoc", + "component": "govdoc", + "route_title": "内部公文处理", + "icon": "ri-file-paper-2-line", + "sort_order": 65, + "is_hidden": False, + "is_cache": True, + "meta": {"group": "govdoc"}, + }, + { + "route_path": "/govdoc/audits", + "route_name": "govdoc-audits", + "component": "govdoc.audits", + "route_title": "公文列表", + "icon": "ri-file-list-3-line", + "sort_order": 1, + "parent_path": "/govdoc", + "is_hidden": False, + "is_cache": True, + "meta": {"group": "govdoc"}, + }, + { + "route_path": "/govdoc/upload", + "route_name": "govdoc-upload", + "component": "govdoc.upload", + "route_title": "公文上传", + "icon": "ri-upload-cloud-line", + "sort_order": 2, + "parent_path": "/govdoc", + "is_hidden": False, + "is_cache": True, + "meta": {"group": "govdoc"}, + }, { "route_path": "/contract-template", "route_name": "contract-template", @@ -183,9 +218,9 @@ class RbacAdminServiceImpl(IRbacAdminService): "meta": {"group": "cross-review"}, }, { - "route_path": "/cross-checking/result", - "route_name": "cross-checking-result", - "component": "cross-checking.result", + "route_path": "/cross-checking/list", + "route_name": "cross-checking-list", + "component": "cross-checking.list", "route_title": "评查任务列表", "icon": "ri-file-list-3-line", "sort_order": 2, @@ -194,6 +229,18 @@ class RbacAdminServiceImpl(IRbacAdminService): "is_cache": True, "meta": {"group": "cross-review"}, }, + { + "route_path": "/cross-checking/result", + "route_name": "cross-checking-result", + "component": "cross-checking.result", + "route_title": "评查结果详情", + "icon": "ri-file-search-line", + "sort_order": 3, + "parent_path": "/cross-checking", + "is_hidden": True, + "is_cache": True, + "meta": {"group": "cross-review"}, + }, { "route_path": "/rules", "route_name": "rule-management", @@ -294,6 +341,16 @@ class RbacAdminServiceImpl(IRbacAdminService): {"permission_key": "rbac:tenants:create", "display_name": "创建租户", "module": "rbac", "resource": "tenants", "action": "create", "api_method": "POST", "api_path": "/api/v3/tenants", "route_path": "/tenants"}, {"permission_key": "rbac:tenants:update", "display_name": "更新租户", "module": "rbac", "resource": "tenants", "action": "update", "api_method": "PUT", "api_path": "/api/v3/tenants/{tenant_code}", "route_path": "/tenants"}, {"permission_key": "rbac:tenants:status", "display_name": "启停租户", "module": "rbac", "resource": "tenants", "action": "status", "api_method": "PATCH", "api_path": "/api/v3/tenants/{tenant_code}/status", "route_path": "/tenants"}, + {"permission_key": "govdoc:module:read", "display_name": "查看内部公文处理模块", "module": "govdoc", "resource": "module", "action": "read", "api_method": "GET", "api_path": "/api/govdoc", "route_path": "/govdoc"}, + {"permission_key": "govdoc:document:create", "display_name": "上传公文", "module": "govdoc", "resource": "document", "action": "create", "api_method": "POST", "api_path": "/api/govdoc/documents", "route_path": "/govdoc/upload"}, + {"permission_key": "govdoc:document:read", "display_name": "查看公文列表与详情", "module": "govdoc", "resource": "document", "action": "read", "api_method": "GET", "api_path": "/api/govdoc/documents", "route_path": "/govdoc/audits"}, + {"permission_key": "govdoc:document:update", "display_name": "编辑公文", "module": "govdoc", "resource": "document", "action": "update", "api_method": "PATCH", "api_path": "/api/govdoc/documents/{document_id}", "route_path": "/govdoc/audits"}, + {"permission_key": "govdoc:document:delete", "display_name": "删除公文", "module": "govdoc", "resource": "document", "action": "delete", "api_method": "DELETE", "api_path": "/api/govdoc/documents/{document_id}", "route_path": "/govdoc/audits"}, + {"permission_key": "govdoc:run:create", "display_name": "发起公文格式审查", "module": "govdoc", "resource": "run", "action": "create", "api_method": "POST", "api_path": "/api/govdoc/runs", "route_path": "/govdoc/audits"}, + {"permission_key": "govdoc:run:read", "display_name": "查看公文审查状态", "module": "govdoc", "resource": "run", "action": "read", "api_method": "GET", "api_path": "/api/govdoc/runs/{run_id}", "route_path": "/govdoc/audits"}, + {"permission_key": "govdoc:report:read", "display_name": "下载公文审查报告", "module": "govdoc", "resource": "report", "action": "read", "api_method": "GET", "api_path": "/api/govdoc/runs/{run_id}/report", "route_path": "/govdoc/audits"}, + {"permission_key": "govdoc:result:read", "display_name": "查看公文审查结果", "module": "govdoc", "resource": "result", "action": "read", "api_method": "GET", "api_path": "/api/govdoc/runs/{run_id}/result", "route_path": "/govdoc/audits"}, + {"permission_key": "govdoc:rule:read", "display_name": "查看公文规则", "module": "govdoc", "resource": "rule", "action": "read", "api_method": "GET", "api_path": "/api/govdoc/rules", "route_path": "/rules"}, {"permission_key": "usage_stats:overview:read", "display_name": "查看统计总览", "module": "usage_stats", "resource": "overview", "action": "read", "api_method": "GET", "api_path": "/api/v3/usage-stats/overview", "route_path": "/usage-stats"}, {"permission_key": "usage_stats:trends:read", "display_name": "查看统计趋势", "module": "usage_stats", "resource": "trends", "action": "read", "api_method": "GET", "api_path": "/api/v3/usage-stats/trends", "route_path": "/usage-stats"}, {"permission_key": "usage_stats:users:read", "display_name": "查看用户统计", "module": "usage_stats", "resource": "users", "action": "read", "api_method": "GET", "api_path": "/api/v3/usage-stats/by-users", "route_path": "/usage-stats"}, @@ -323,14 +380,9 @@ class RbacAdminServiceImpl(IRbacAdminService): {"permission_key": "rules:binding_create:write", "display_name": "创建规则绑定", "module": "rules", "resource": "binding_create", "action": "write", "api_method": "POST", "api_path": "/api/rule-sets/{rule_type}/bindings", "route_path": "/rules"}, {"permission_key": "rules:binding_update:write", "display_name": "更新规则绑定", "module": "rules", "resource": "binding_update", "action": "write", "api_method": "PUT", "api_path": "/api/rule-sets/bindings/{binding_id}", "route_path": "/rules"}, {"permission_key": "rules:binding_delete:delete", "display_name": "删除规则绑定", "module": "rules", "resource": "binding_delete", "action": "delete", "api_method": "DELETE", "api_path": "/api/rule-sets/bindings/{binding_id}", "route_path": "/rules"}, - {"permission_key": "evaluation_point:list:read", "display_name": "评查点列表", "module": "evaluation_point", "resource": "list", "action": "read", "api_method": "GET", "api_path": "/api/v3/evaluation-points", "route_path": "/rules"}, - {"permission_key": "evaluation_point:detail:read", "display_name": "评查点详情", "module": "evaluation_point", "resource": "detail", "action": "read", "api_method": "GET", "api_path": "/api/v3/evaluation-points/{id}", "route_path": "/rules"}, - {"permission_key": "evaluation_point:create:write", "display_name": "创建评查点", "module": "evaluation_point", "resource": "create", "action": "write", "api_method": "POST", "api_path": "/api/v3/evaluation-points", "route_path": "/rules"}, - {"permission_key": "evaluation_point:update:write", "display_name": "更新评查点", "module": "evaluation_point", "resource": "update", "action": "write", "api_method": "PUT", "api_path": "/api/v3/evaluation-points/{id}", "route_path": "/rules"}, - {"permission_key": "evaluation_point:delete:delete", "display_name": "删除评查点", "module": "evaluation_point", "resource": "delete", "action": "delete", "api_method": "DELETE", "api_path": "/api/v3/evaluation-points/{id}", "route_path": "/rules"}, {"permission_key": "cross_review:task:create", "display_name": "创建交叉评查任务", "module": "cross_review", "resource": "task", "action": "create", "api_method": "POST", "api_path": "/api/v3/cross-review/tasks", "route_path": "/cross-checking/upload"}, - {"permission_key": "cross_review:task:read", "display_name": "查看交叉评查任务", "module": "cross_review", "resource": "task", "action": "read", "api_method": "POST", "api_path": "/api/v3/cross-review/tasks/query", "route_path": "/cross-checking"}, - {"permission_key": "cross_review:progress:view", "display_name": "查看交叉评查任务进度", "module": "cross_review", "resource": "progress", "action": "view", "api_method": "GET", "api_path": "/api/v3/cross-review/tasks/{task_id}/progress", "route_path": "/cross-checking"}, + {"permission_key": "cross_review:task:read", "display_name": "查看交叉评查任务", "module": "cross_review", "resource": "task", "action": "read", "api_method": "POST", "api_path": "/api/v3/cross-review/tasks/query", "route_path": "/cross-checking/list"}, + {"permission_key": "cross_review:progress:view", "display_name": "查看交叉评查任务进度", "module": "cross_review", "resource": "progress", "action": "view", "api_method": "GET", "api_path": "/api/v3/cross-review/tasks/{task_id}/progress", "route_path": "/cross-checking/list"}, {"permission_key": "cross_review:document:read", "display_name": "查看交叉评查任务文档", "module": "cross_review", "resource": "document", "action": "read", "api_method": "GET", "api_path": "/api/v3/cross-review/tasks/{task_id}/documents", "route_path": "/cross-checking/result"}, {"permission_key": "cross_review:document:complete", "display_name": "确认交叉评查文档完成", "module": "cross_review", "resource": "document", "action": "complete", "api_method": "GET", "api_path": "/api/v3/cross-review/tasks/{task_id}/can-confirm", "route_path": "/cross-checking/result"}, {"permission_key": "cross_review:proposal:create", "display_name": "创建交叉评查提案", "module": "cross_review", "resource": "proposal", "action": "create", "api_method": "POST", "api_path": "/api/v3/cross-review/proposals", "route_path": "/cross-checking/result"}, @@ -357,12 +409,14 @@ class RbacAdminServiceImpl(IRbacAdminService): {"permission_key": "rag:dataset:create", "display_name": "创建知识库", "module": "rag", "resource": "dataset", "action": "create", "api_method": "POST", "api_path": "/api/v3/rag/datasets/admin", "route_path": "/chat-with-llm"}, {"permission_key": "rag:dataset:update", "display_name": "更新知识库与文档", "module": "rag", "resource": "dataset", "action": "update", "api_method": "PATCH", "api_path": "/api/v3/rag/datasets/{DatasetId}", "route_path": "/chat-with-llm"}, {"permission_key": "rag:dataset:delete", "display_name": "删除知识库与文档", "module": "rag", "resource": "dataset", "action": "delete", "api_method": "DELETE", "api_path": "/api/v3/rag/datasets/admin/{DatasetId}", "route_path": "/chat-with-llm"}, + {"permission_key": "qichacha:company:query", "display_name": "查询企业主体信息", "module": "qichacha", "resource": "company", "action": "query", "api_method": "POST", "api_path": "/api/v2/qichacha/company", "route_path": "/documents"}, + {"permission_key": "qichacha:status:read", "display_name": "查看企业主体缓存状态", "module": "qichacha", "resource": "status", "action": "read", "api_method": "GET", "api_path": "/api/v2/qichacha/status", "route_path": "/documents"}, ] _CORE_ROLE_AUTO_GRANTS: dict[str, tuple[str, ...]] = { - "super_admin": ("rbac:user_tenant:update", "rbac:tenants:read", "rbac:tenants:create", "rbac:tenants:update", "rbac:tenants:status"), - "provincial_admin": ("rbac:user_tenant:update", "rbac:tenants:read", "rbac:tenants:create", "rbac:tenants:update", "rbac:tenants:status"), - "admin": ("rbac:user_tenant:update", "rbac:tenants:read", "rbac:tenants:create", "rbac:tenants:update", "rbac:tenants:status"), + "super_admin": ("rbac:user_tenant:update", "rbac:tenants:read", "rbac:tenants:create", "rbac:tenants:update", "rbac:tenants:status", "qichacha:company:query", "qichacha:status:read"), + "provincial_admin": ("rbac:user_tenant:update", "rbac:tenants:read", "rbac:tenants:create", "rbac:tenants:update", "rbac:tenants:status", "qichacha:company:query", "qichacha:status:read"), + "admin": ("rbac:user_tenant:update", "rbac:tenants:read", "rbac:tenants:create", "rbac:tenants:update", "rbac:tenants:status", "qichacha:company:query"), } async def ListRoles(self, CurrentUserId: int, Page: int, PageSize: int, RoleKey: str | None, RoleName: str | None, IncludeSystem: bool) -> RoleListVO: @@ -1393,28 +1447,15 @@ class RbacAdminServiceImpl(IRbacAdminService): if context["is_super_admin"] or not permissionKeys: return context - async with GetAsyncSession() as Session: - grantedRows = ( - await Session.execute( - text( - """ - SELECT DISTINCT p.permission_key - FROM role_permissions rp - JOIN permissions p ON p.id = rp.permission_id - JOIN user_role ur ON ur.role_id = rp.role_id - WHERE ur.user_id = :user_id - AND p.permission_key = ANY(:permission_keys) - AND rp.grant_type = 'GRANT' - """ - ).bindparams(permission_keys=permissionKeys), - {"user_id": CurrentUserId}, - ) - ).mappings().all() - granted = {str(row["permission_key"] or "") for row in grantedRows} - missing = [key for key in permissionKeys if key not in granted] - if not missing: - return context + deniedKeys = [] + permissionService = PermissionServiceImpl() + for permissionKey in permissionKeys: + if not await permissionService.CheckPermission(CurrentUserId, permissionKey): + deniedKeys.append(permissionKey) + if not deniedKeys: + return context + async with GetAsyncSession() as Session: displayRows = ( await Session.execute( text( @@ -1423,11 +1464,11 @@ class RbacAdminServiceImpl(IRbacAdminService): FROM permissions WHERE permission_key = ANY(:permission_keys) """ - ).bindparams(permission_keys=missing) + ).bindparams(permission_keys=deniedKeys) ) ).mappings().all() displayByKey = {str(row["permission_key"] or ""): str(row["display_name"] or "") for row in displayRows} - displayName = displayByKey.get(missing[0]) or missing[0] + displayName = displayByKey.get(deniedKeys[0]) or deniedKeys[0] raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, f"缺少「{displayName}」权限") async def _assertManageAndPermission(self, CurrentUserId: int, PermissionKey: str) -> dict[str, Any]: diff --git a/fastapi_modules/fastapi_leaudit/services/impl/rbacServiceImpl.py b/fastapi_modules/fastapi_leaudit/services/impl/rbacServiceImpl.py index c2b082c..0565b0e 100644 --- a/fastapi_modules/fastapi_leaudit/services/impl/rbacServiceImpl.py +++ b/fastapi_modules/fastapi_leaudit/services/impl/rbacServiceImpl.py @@ -33,6 +33,8 @@ class RbacServiceImpl(IRbacService): "/document-types", "/tenants", "/usage-stats", + "/govdoc", + "/govdoc-audit", ) _COMPAT_ROUTE_BLUEPRINTS: dict[str, list[dict[str, Any]]] = { @@ -110,24 +112,67 @@ class RbacServiceImpl(IRbacService): }, { "id": 1006, + "route_path": "/govdoc", + "route_name": "govdoc", + "component": "govdoc", + "parent_id": None, + "route_title": "内部公文处理", + "icon": "ri-file-paper-2-line", + "sort_order": 4, + "is_hidden": False, + "is_cache": True, + "meta": {"group": "govdoc"}, + "children": [ + { + "id": 1023, + "route_path": "/govdoc/audits", + "route_name": "govdoc-audits", + "component": "govdoc.audits", + "parent_id": 1006, + "route_title": "公文列表", + "icon": "ri-file-list-3-line", + "sort_order": 1, + "is_hidden": False, + "is_cache": True, + "meta": {"group": "govdoc"}, + "children": None, + }, + { + "id": 1024, + "route_path": "/govdoc/upload", + "route_name": "govdoc-upload", + "component": "govdoc.upload", + "parent_id": 1006, + "route_title": "公文上传", + "icon": "ri-upload-cloud-line", + "sort_order": 2, + "is_hidden": False, + "is_cache": True, + "meta": {"group": "govdoc"}, + "children": None, + }, + ], + }, + { + "id": 1007, "route_path": "/rules", "route_name": "rule-management", "component": "rules", "parent_id": None, "route_title": "规则管理", "icon": "ri-book-3-line", - "sort_order": 4, + "sort_order": 5, "is_hidden": False, "is_cache": True, "meta": {"group": "rules"}, "children": [ { "id": 1008, - "route_path": "/rules/list", + "route_path": "/rules", "route_name": "rules-list", - "component": "rules.list", - "parent_id": 1006, - "route_title": "评查点列表", + "component": "rules", + "parent_id": 1007, + "route_title": "规则配置列表", "icon": "ri-list-check-3", "sort_order": 2, "is_hidden": False, @@ -140,7 +185,7 @@ class RbacServiceImpl(IRbacService): "route_path": "/rules-files", "route_name": "rules-file", "component": "rules-files", - "parent_id": 1006, + "parent_id": 1007, "route_title": "评查文件列表", "icon": "ri-list-check-2", "sort_order": 3, @@ -159,7 +204,7 @@ class RbacServiceImpl(IRbacService): "parent_id": None, "route_title": "合同管理", "icon": "ri-file-search-line", - "sort_order": 5, + "sort_order": 6, "is_hidden": False, "is_cache": True, "meta": {"group": "contract"}, @@ -202,7 +247,7 @@ class RbacServiceImpl(IRbacService): "parent_id": None, "route_title": "系统设置", "icon": "ri-settings-4-line", - "sort_order": 6, + "sort_order": 7, "is_hidden": False, "is_cache": True, "meta": {"group": "settings"}, @@ -287,17 +332,17 @@ class RbacServiceImpl(IRbacService): "parent_id": None, "route_title": "交叉评查", "icon": "ri-color-filter-line", - "sort_order": 7, + "sort_order": 8, "is_hidden": False, "is_cache": True, "meta": {"group": "cross-review"}, "children": [ { - "id": 1019, + "id": 1020, "route_path": "/cross-checking/upload", "route_name": "cross-checking-upload", "component": "cross-checking.upload", - "parent_id": 1018, + "parent_id": 1019, "route_title": "创建任务", "icon": "ri-upload-cloud-line", "sort_order": 1, @@ -307,11 +352,11 @@ class RbacServiceImpl(IRbacService): "children": None, }, { - "id": 1020, - "route_path": "/cross-checking/result", - "route_name": "cross-checking-result", - "component": "cross-checking.result", - "parent_id": 1018, + "id": 1021, + "route_path": "/cross-checking/list", + "route_name": "cross-checking-list", + "component": "cross-checking.list", + "parent_id": 1019, "route_title": "评查任务列表", "icon": "ri-file-list-3-line", "sort_order": 2, @@ -320,6 +365,20 @@ class RbacServiceImpl(IRbacService): "meta": {"group": "cross-review"}, "children": None, }, + { + "id": 1022, + "route_path": "/cross-checking/result", + "route_name": "cross-checking-result", + "component": "cross-checking.result", + "parent_id": 1019, + "route_title": "评查结果详情", + "icon": "ri-file-search-line", + "sort_order": 3, + "is_hidden": True, + "is_cache": True, + "meta": {"group": "cross-review"}, + "children": None, + }, ], }, ], @@ -383,24 +442,67 @@ class RbacServiceImpl(IRbacService): }, { "id": 2005, + "route_path": "/govdoc", + "route_name": "govdoc", + "component": "govdoc", + "parent_id": None, + "route_title": "内部公文处理", + "icon": "ri-file-paper-2-line", + "sort_order": 4, + "is_hidden": False, + "is_cache": True, + "meta": {"group": "govdoc"}, + "children": [ + { + "id": 2019, + "route_path": "/govdoc/audits", + "route_name": "govdoc-audits", + "component": "govdoc.audits", + "parent_id": 2005, + "route_title": "公文列表", + "icon": "ri-file-list-3-line", + "sort_order": 1, + "is_hidden": False, + "is_cache": True, + "meta": {"group": "govdoc"}, + "children": None, + }, + { + "id": 2020, + "route_path": "/govdoc/upload", + "route_name": "govdoc-upload", + "component": "govdoc.upload", + "parent_id": 2005, + "route_title": "公文上传", + "icon": "ri-upload-cloud-line", + "sort_order": 2, + "is_hidden": False, + "is_cache": True, + "meta": {"group": "govdoc"}, + "children": None, + }, + ], + }, + { + "id": 2006, "route_path": "/rules", "route_name": "rule-management", "component": "rules", "parent_id": None, "route_title": "规则管理", "icon": "ri-book-3-line", - "sort_order": 4, + "sort_order": 5, "is_hidden": False, "is_cache": True, "meta": {"group": "rules"}, "children": [ { "id": 2007, - "route_path": "/rules/list", + "route_path": "/rules", "route_name": "rules-list", - "component": "rules.list", - "parent_id": 2005, - "route_title": "评查点列表", + "component": "rules", + "parent_id": 2006, + "route_title": "规则配置列表", "icon": "ri-list-check-3", "sort_order": 2, "is_hidden": False, @@ -413,7 +515,7 @@ class RbacServiceImpl(IRbacService): "route_path": "/rules-files", "route_name": "rules-file", "component": "rules-files", - "parent_id": 2005, + "parent_id": 2006, "route_title": "评查文件列表", "icon": "ri-list-check-2", "sort_order": 3, @@ -432,7 +534,7 @@ class RbacServiceImpl(IRbacService): "parent_id": None, "route_title": "合同管理", "icon": "ri-file-search-line", - "sort_order": 5, + "sort_order": 6, "is_hidden": False, "is_cache": True, "meta": {"group": "contract"}, @@ -475,7 +577,7 @@ class RbacServiceImpl(IRbacService): "parent_id": None, "route_title": "系统设置", "icon": "ri-settings-4-line", - "sort_order": 6, + "sort_order": 7, "is_hidden": False, "is_cache": True, "meta": {"group": "settings"}, @@ -518,7 +620,7 @@ class RbacServiceImpl(IRbacService): "parent_id": None, "route_title": "交叉评查", "icon": "ri-color-filter-line", - "sort_order": 7, + "sort_order": 8, "is_hidden": False, "is_cache": True, "meta": {"group": "cross-review"}, @@ -528,7 +630,7 @@ class RbacServiceImpl(IRbacService): "route_path": "/cross-checking/upload", "route_name": "cross-checking-upload", "component": "cross-checking.upload", - "parent_id": 2012, + "parent_id": 2015, "route_title": "创建任务", "icon": "ri-upload-cloud-line", "sort_order": 1, @@ -539,10 +641,10 @@ class RbacServiceImpl(IRbacService): }, { "id": 2017, - "route_path": "/cross-checking/result", - "route_name": "cross-checking-result", - "component": "cross-checking.result", - "parent_id": 2012, + "route_path": "/cross-checking/list", + "route_name": "cross-checking-list", + "component": "cross-checking.list", + "parent_id": 2015, "route_title": "评查任务列表", "icon": "ri-file-list-3-line", "sort_order": 2, @@ -551,6 +653,20 @@ class RbacServiceImpl(IRbacService): "meta": {"group": "cross-review"}, "children": None, }, + { + "id": 2018, + "route_path": "/cross-checking/result", + "route_name": "cross-checking-result", + "component": "cross-checking.result", + "parent_id": 2015, + "route_title": "评查结果详情", + "icon": "ri-file-search-line", + "sort_order": 3, + "is_hidden": True, + "is_cache": True, + "meta": {"group": "cross-review"}, + "children": None, + }, ], }, ], @@ -561,6 +677,9 @@ class RbacServiceImpl(IRbacService): "/files": ["documents:"], "/files/upload": ["documents:upload:"], "/documents": ["documents:"], + "/govdoc": ["govdoc:"], + "/govdoc/audits": ["govdoc:document:read"], + "/govdoc/upload": ["govdoc:document:create"], "/settings": ["entry_module:", "rbac:", "doc_type:"], "/entry-modules": ["entry_module:"], "/role-permissions": ["rbac:"], @@ -568,8 +687,14 @@ class RbacServiceImpl(IRbacService): "/tenants": ["rbac:tenants:"], "/usage-stats": ["usage_stats:"], "/rules": ["rules:", "evaluation_point:", "evaluation_group:"], - "/rules/list": ["rules:", "evaluation_point:"], "/rules-files": ["rules:"], + "/cross-checking": ["cross_review:"], + "/cross-checking/list": ["cross_review:task:", "cross_review:progress:"], + "/cross-checking/upload": ["cross_review:task:create"], + "/cross-checking/result": [ + "cross_review:document:", + "cross_review:proposal:", + ], } async def GetCurrentUserRoutes(self, UserId: int) -> RbacUserRoutesVO: diff --git a/fastapi_modules/fastapi_leaudit/services/impl/ruleServiceImpl.py b/fastapi_modules/fastapi_leaudit/services/impl/ruleServiceImpl.py index fa881b3..3dbed7b 100644 --- a/fastapi_modules/fastapi_leaudit/services/impl/ruleServiceImpl.py +++ b/fastapi_modules/fastapi_leaudit/services/impl/ruleServiceImpl.py @@ -793,6 +793,7 @@ class RuleServiceImpl(IRuleService): await self._assert_document_type_access(Session, DocTypeId, current_user) GroupId = await self._resolve_unique_accessible_child_group_id(Session, DocTypeId, current_user) if GroupId is not None: + binding_scope = self._build_group_binding_scope_payload(current_user) ExistingGroupBinding = await Session.execute( text( """ @@ -800,11 +801,16 @@ class RuleServiceImpl(IRuleService): FROM leaudit_rule_group_bindings WHERE group_id = :group_id AND rule_set_id = :rule_set_id + AND COALESCE(NULLIF(BTRIM(tenant_code), ''), 'PROVINCIAL') = :tenant_code AND deleted_at IS NULL LIMIT 1 """ ), - {"group_id": GroupId, "rule_set_id": RuleSetId}, + { + "group_id": GroupId, + "rule_set_id": RuleSetId, + "tenant_code": binding_scope["tenant_code"], + }, ) if ExistingGroupBinding.mappings().first(): raise LeauditException(StatusCodeEnum.HTTP_409_CONFLICT, "该文档类型对应子组已绑定此规则集") @@ -815,6 +821,9 @@ class RuleServiceImpl(IRuleService): INSERT INTO leaudit_rule_group_bindings ( group_id, rule_set_id, + tenant_code, + scope_type, + tenant_name_snapshot, priority, is_active, note, @@ -823,6 +832,9 @@ class RuleServiceImpl(IRuleService): ) VALUES ( :group_id, :rule_set_id, + :tenant_code, + :scope_type, + :tenant_name_snapshot, :priority, true, :note, @@ -835,6 +847,9 @@ class RuleServiceImpl(IRuleService): { "group_id": GroupId, "rule_set_id": RuleSetId, + "tenant_code": binding_scope["tenant_code"], + "scope_type": binding_scope["scope_type"], + "tenant_name_snapshot": binding_scope["tenant_name_snapshot"], "priority": Priority, "note": Note, }, @@ -1177,6 +1192,23 @@ class RuleServiceImpl(IRuleService): "note": "由租户规则集派生自动补绑", } + def _build_group_binding_scope_payload(self, current_user: dict[str, object] | None) -> dict[str, object | None]: + if current_user and not current_user.get("is_global"): + tenant_code = normalize_scoped_tenant_code(str(current_user.get("tenant_code") or ""), default="") + if not tenant_code: + raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "当前租户上下文缺失,不能绑定规则集") + return { + "tenant_code": tenant_code, + "scope_type": "TENANT", + "tenant_name_snapshot": str(current_user.get("tenant_name") or "").strip() or None, + } + + return { + "tenant_code": "PROVINCIAL", + "scope_type": "PROVINCIAL", + "tenant_name_snapshot": None, + } + async def _load_source_group_binding_ids(self, Session, source_rule_set_id: int) -> list[int]: if not await self._column_exists(Session, "leaudit_rule_group_bindings", "tenant_code"): return [] diff --git a/fastapi_modules/fastapi_leaudit/services/impl/usageStatsServiceImpl.py b/fastapi_modules/fastapi_leaudit/services/impl/usageStatsServiceImpl.py index 3dce801..286da83 100644 --- a/fastapi_modules/fastapi_leaudit/services/impl/usageStatsServiceImpl.py +++ b/fastapi_modules/fastapi_leaudit/services/impl/usageStatsServiceImpl.py @@ -893,9 +893,7 @@ class UsageStatsServiceImpl(IUsageStatsService): } def _assert_stats_access(self, context: dict[str, Any]) -> None: - if context["is_global"] or context["can_manage"]: - return - raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "仅管理员可查看系统使用统计") + return def _build_user_scope_condition(self, context: dict[str, Any], filters: dict[str, Any], *, user_alias: str) -> tuple[str, dict[str, Any]]: conditions = ["1 = 1"] diff --git a/fastapi_modules/fastapi_leaudit/services/qichachaService.py b/fastapi_modules/fastapi_leaudit/services/qichachaService.py new file mode 100644 index 0000000..23d7176 --- /dev/null +++ b/fastapi_modules/fastapi_leaudit/services/qichachaService.py @@ -0,0 +1,40 @@ +"""企查查服务接口。""" + +from __future__ import annotations + +from abc import ABC, abstractmethod + +from fastapi_modules.fastapi_leaudit.domian.vo.qichachaVo import ( + QichachaBatchQueryVO, + QichachaCompanyQueryVO, + QichachaRecordStatusVO, +) + + +class IQichachaService(ABC): + """企查查服务接口。""" + + @abstractmethod + async def QueryCompany(self, Keyword: str, ForceRefresh: bool = False) -> QichachaCompanyQueryVO: + """查询企业完整信息。""" + ... + + @abstractmethod + async def QueryEnterpriseOnly(self, Keyword: str, ForceRefresh: bool = False) -> QichachaCompanyQueryVO: + """仅查询企业工商信息。""" + ... + + @abstractmethod + async def QueryDishonestyOnly(self, Keyword: str, ForceRefresh: bool = False) -> QichachaCompanyQueryVO: + """仅查询企业失信信息。""" + ... + + @abstractmethod + async def BatchQuery(self, Keywords: list[str], ForceRefresh: bool = False) -> QichachaBatchQueryVO: + """批量查询企业信息。""" + ... + + @abstractmethod + async def GetRecordStatus(self, Keyword: str) -> QichachaRecordStatusVO: + """查询企业缓存状态。""" + ... diff --git a/legal-platform-frontend b/legal-platform-frontend index 1abbbe6..40ef991 160000 --- a/legal-platform-frontend +++ b/legal-platform-frontend @@ -1 +1 @@ -Subproject commit 1abbbe6b4eaff963339bc3c1e22f71c7da728e77 +Subproject commit 40ef991434f4890382f32ed6146e0ef076cf53fa diff --git a/scripts/创建sql/entry_module_menu_profile_migration.sql b/scripts/创建sql/entry_module_menu_profile_migration.sql new file mode 100644 index 0000000..da8fc03 --- /dev/null +++ b/scripts/创建sql/entry_module_menu_profile_migration.sql @@ -0,0 +1,67 @@ +-- 入口模块菜单模板、功能清单、文档入口归属迁移脚本 +-- 目标: +-- 1. 入口模块用 menu_profile/features 控制左侧菜单,不再靠名称包含“合同/公文”判断。 +-- 2. 文档记录补 entry_module_id,后续列表、上传、统计、质量校验可以按入口模块过滤。 +-- 3. 二级分组增加父级内文档类型唯一约束,避免规则命中不稳定。 + +BEGIN; + +ALTER TABLE leaudit_entry_modules +ADD COLUMN IF NOT EXISTS menu_profile VARCHAR(64) NOT NULL DEFAULT 'document_review', +ADD COLUMN IF NOT EXISTS features JSONB NOT NULL DEFAULT '[]'::jsonb; + +ALTER TABLE leaudit_documents +ADD COLUMN IF NOT EXISTS entry_module_id BIGINT NULL REFERENCES leaudit_entry_modules(id); + +CREATE INDEX IF NOT EXISTS idx_leaudit_entry_modules_menu_profile +ON leaudit_entry_modules(menu_profile) +WHERE deleted_at IS NULL; + +CREATE INDEX IF NOT EXISTS idx_leaudit_documents_entry_module_id +ON leaudit_documents(entry_module_id); + +CREATE UNIQUE INDEX IF NOT EXISTS uq_leaudit_ep_groups_parent_doc_type_active +ON leaudit_evaluation_point_groups(pid, document_type_id) +WHERE deleted_at IS NULL + AND COALESCE(pid, 0) <> 0 + AND document_type_id IS NOT NULL; + +UPDATE leaudit_entry_modules +SET + menu_profile = CASE + WHEN path IN ('/govdoc/audits', '/govdoc', '/govdoc-audit') THEN 'govdoc' + WHEN path IN ('/contract-template', '/contract-template/list', '/contract-template/search') THEN 'contract' + ELSE COALESCE(NULLIF(menu_profile, ''), 'document_review') + END, + features = CASE + WHEN path IN ('/govdoc/audits', '/govdoc', '/govdoc-audit') + THEN '["home","govdoc_audits","govdoc_upload","rule_groups"]'::jsonb + WHEN path IN ('/contract-template', '/contract-template/list', '/contract-template/search') + THEN '["home","documents","upload","rules","contract_template_search","contract_template_list"]'::jsonb + WHEN features IS NULL OR features = '[]'::jsonb + THEN '["home","documents","upload","rules","rule_groups"]'::jsonb + ELSE features + END +WHERE deleted_at IS NULL; + +UPDATE leaudit_documents d +SET entry_module_id = COALESCE(g.entry_module_id, parent.entry_module_id, dt.entry_module_id) +FROM leaudit_document_types dt +LEFT JOIN leaudit_evaluation_point_groups g ON g.id = d.group_id +LEFT JOIN leaudit_evaluation_point_groups parent ON parent.id = g.pid +WHERE d.type_id = dt.id + AND d.entry_module_id IS NULL; + +COMMIT; + +-- 验证入口模块菜单字段: +-- SELECT id, name, path, menu_profile, features +-- FROM leaudit_entry_modules +-- WHERE deleted_at IS NULL +-- ORDER BY sort_order, id; + +-- 验证仍未回填入口模块归属的文档: +-- SELECT COUNT(*) AS documents_without_entry_module +-- FROM leaudit_documents +-- WHERE deleted_at IS NULL +-- AND entry_module_id IS NULL; diff --git a/scripts/创建sql/rbac_entry_module_super_admin_only.sql b/scripts/创建sql/rbac_entry_module_super_admin_only.sql new file mode 100644 index 0000000..6ba20ff --- /dev/null +++ b/scripts/创建sql/rbac_entry_module_super_admin_only.sql @@ -0,0 +1,66 @@ +-- ============================================================================ +-- 入口模块管理权限收口:仅系统超级管理员维护入口模块 +-- 说明: +-- 1. 不在代码里硬编码角色名,运行时仍然只认 RBAC 权限点。 +-- 2. 本脚本只调整默认 RBAC 数据,把 admin / provincial_admin 的入口模块管理能力移除。 +-- 3. 若后续确需给某个角色开放入口模块管理,请通过角色权限页面重新分配。 +-- 4. 可重复执行。 +-- ============================================================================ + +BEGIN; + +-- 保证系统超级管理员拥有入口模块路由。 +INSERT INTO role_route (role_id, route_id, permission, status, created_at, updated_at) +SELECT r.id, sr.id, 'RW', 1, NOW(), NOW() +FROM roles r +JOIN sys_routes sr ON sr.route_path = '/entry-modules' AND sr.deleted_at IS NULL +WHERE r.role_key = 'super_admin' +ON CONFLICT (role_id, route_id) DO UPDATE SET + permission = EXCLUDED.permission, + status = EXCLUDED.status, + updated_at = NOW(); + +-- 保证系统超级管理员拥有入口模块全部权限点。 +INSERT INTO role_permissions (role_id, permission_id, grant_type, data_scope, created_at, updated_at) +SELECT r.id, p.id, 'GRANT', 'ALL', NOW(), NOW() +FROM roles r +JOIN permissions p ON p.permission_key LIKE 'entry_module:%' +WHERE r.role_key = 'super_admin' +ON CONFLICT (role_id, permission_id) DO UPDATE SET + grant_type = EXCLUDED.grant_type, + data_scope = EXCLUDED.data_scope, + updated_at = NOW(); + +-- 移除地区管理员 / 旧省级管理员的入口模块权限点。 +DELETE FROM role_permissions rp +USING roles r, permissions p +WHERE rp.role_id = r.id + AND rp.permission_id = p.id + AND r.role_key IN ('admin', 'provincial_admin') + AND p.permission_key LIKE 'entry_module:%'; + +-- 移除地区管理员 / 旧省级管理员的入口模块管理菜单。 +DELETE FROM role_route rr +USING roles r, sys_routes sr +WHERE rr.role_id = r.id + AND rr.route_id = sr.id + AND r.role_key IN ('admin', 'provincial_admin') + AND sr.route_path = '/entry-modules'; + +COMMIT; + +-- 验证结果:应只看到 super_admin 拥有入口模块权限。 +SELECT r.role_key, p.permission_key +FROM roles r +JOIN role_permissions rp ON rp.role_id = r.id +JOIN permissions p ON p.id = rp.permission_id +WHERE p.permission_key LIKE 'entry_module:%' +ORDER BY r.role_key, p.permission_key; + +-- 验证结果:admin / provincial_admin 不应再拥有 /entry-modules 路由。 +SELECT r.role_key, sr.route_path, rr.permission, rr.status +FROM roles r +JOIN role_route rr ON rr.role_id = r.id +JOIN sys_routes sr ON sr.id = rr.route_id +WHERE sr.route_path = '/entry-modules' +ORDER BY r.role_key; diff --git a/scripts/创建sql/repair_entry_module_scope_data_20260524.sql b/scripts/创建sql/repair_entry_module_scope_data_20260524.sql new file mode 100644 index 0000000..9bc55d2 --- /dev/null +++ b/scripts/创建sql/repair_entry_module_scope_data_20260524.sql @@ -0,0 +1,145 @@ +-- 修复入口模块归属历史数据 +-- 执行目标: +-- 1. 回填可通过业务类型/规则分组明确推导的 leaudit_documents.entry_module_id。 +-- 2. 回填可通过子业务类型明确推导的一级/二级业务分组 entry_module_id。 +-- 3. 软删除无文档、无规则绑定的 Playwright/test 残留规则分组。 +-- 4. 补齐正式案卷入口的默认功能菜单。 +-- +-- 安全边界: +-- 1. 不处理无法推导的旧公文示例。 +-- 2. 不把数据强行塞到 entry_module_id=1。 +-- 3. 不物理删除数据,只做软删除或确定性回填。 + +BEGIN; + +-- 1. 回填历史文档 entry_module_id:只处理能推导到“未删除入口模块”的文档。 +WITH resolved_documents AS ( + SELECT + d.id, + COALESCE(g.entry_module_id, parent.entry_module_id, dt.entry_module_id) AS resolved_entry_module_id + FROM leaudit_documents d + LEFT JOIN leaudit_document_types dt + ON dt.id = d.type_id + LEFT JOIN leaudit_evaluation_point_groups g + ON g.id = d.group_id + LEFT JOIN leaudit_evaluation_point_groups parent + ON parent.id = g.pid + JOIN leaudit_entry_modules em + ON em.id = COALESCE(g.entry_module_id, parent.entry_module_id, dt.entry_module_id) + AND em.deleted_at IS NULL + WHERE d.deleted_at IS NULL + AND d.entry_module_id IS NULL + AND COALESCE(g.entry_module_id, parent.entry_module_id, dt.entry_module_id) IS NOT NULL +) +UPDATE leaudit_documents d +SET entry_module_id = rd.resolved_entry_module_id, + updated_at = NOW() +FROM resolved_documents rd +WHERE d.id = rd.id; + +-- 2. 回填一级业务大类 entry_module_id:只处理所有子业务类型都指向同一个未删除入口模块的一级分组。 +WITH resolvable_roots AS ( + SELECT + root.id AS root_id, + MIN(child_dt.entry_module_id) AS resolved_entry_module_id + FROM leaudit_evaluation_point_groups root + JOIN leaudit_evaluation_point_groups child + ON child.pid = root.id + AND child.deleted_at IS NULL + JOIN leaudit_document_types child_dt + ON child_dt.id = child.document_type_id + AND child_dt.deleted_at IS NULL + JOIN leaudit_entry_modules em + ON em.id = child_dt.entry_module_id + AND em.deleted_at IS NULL + WHERE root.deleted_at IS NULL + AND COALESCE(root.pid, 0) = 0 + AND root.entry_module_id IS NULL + GROUP BY root.id + HAVING COUNT(DISTINCT child_dt.entry_module_id) = 1 +) +UPDATE leaudit_evaluation_point_groups root +SET entry_module_id = rr.resolved_entry_module_id, + updated_at = NOW() +FROM resolvable_roots rr +WHERE root.id = rr.root_id; + +-- 3. 二级业务类型继承父级入口模块:只处理父级已明确归属、子级为空的记录。 +UPDATE leaudit_evaluation_point_groups child +SET entry_module_id = parent.entry_module_id, + updated_at = NOW() +FROM leaudit_evaluation_point_groups parent +WHERE child.deleted_at IS NULL + AND parent.deleted_at IS NULL + AND child.pid = parent.id + AND COALESCE(parent.pid, 0) = 0 + AND parent.entry_module_id IS NOT NULL + AND child.entry_module_id IS NULL; + +-- 4. 软删除无引用的测试残留规则分组。 +WITH candidate_groups AS ( + SELECT g.id + FROM leaudit_evaluation_point_groups g + WHERE g.deleted_at IS NULL + AND ( + g.code = 'testmzceshi' + OR g.code LIKE 'pw.%' + ) +), +safe_groups AS ( + SELECT cg.id + FROM candidate_groups cg + WHERE NOT EXISTS ( + SELECT 1 + FROM leaudit_documents d + WHERE d.deleted_at IS NULL + AND d.group_id = cg.id + ) + AND NOT EXISTS ( + SELECT 1 + FROM leaudit_rule_group_bindings b + WHERE b.deleted_at IS NULL + AND b.group_id = cg.id + ) + AND NOT EXISTS ( + SELECT 1 + FROM leaudit_evaluation_point_groups child + JOIN leaudit_documents d + ON d.group_id = child.id + AND d.deleted_at IS NULL + WHERE child.deleted_at IS NULL + AND child.pid = cg.id + ) + AND NOT EXISTS ( + SELECT 1 + FROM leaudit_evaluation_point_groups child + JOIN leaudit_rule_group_bindings b + ON b.group_id = child.id + AND b.deleted_at IS NULL + WHERE child.deleted_at IS NULL + AND child.pid = cg.id + ) +) +UPDATE leaudit_evaluation_point_groups g +SET deleted_at = NOW(), + updated_at = NOW() +FROM safe_groups sg +WHERE g.id = sg.id; + +-- 5. 补齐案卷智能评查的通用文档评查功能菜单。 +UPDATE leaudit_entry_modules +SET features = '["home", "documents", "upload", "rules", "rule_groups"]'::jsonb, + updated_at = NOW() +WHERE deleted_at IS NULL + AND id = 2 + AND name = '案卷智能评查' + AND ( + features IS NULL + OR jsonb_typeof(features) <> 'array' + OR jsonb_array_length(features) = 0 + ); + +COMMIT; + +-- 执行后建议立即运行: +-- psql "$DATABASE_URL" -f scripts/创建sql/verify_entry_module_menu_profile.sql diff --git a/scripts/创建sql/repair_govdoc_sample_entry_module_20260524.sql b/scripts/创建sql/repair_govdoc_sample_entry_module_20260524.sql new file mode 100644 index 0000000..30ea59c --- /dev/null +++ b/scripts/创建sql/repair_govdoc_sample_entry_module_20260524.sql @@ -0,0 +1,15 @@ +-- 回填旧公文示例文档入口归属 +-- 这 3 条是旧 govdoc 链路数据,只补 entry_module_id,不伪造 type_id/group_id。 + +BEGIN; + +UPDATE leaudit_documents +SET entry_module_id = 3, + updated_at = NOW() +WHERE id IN (46, 47, 48) + AND deleted_at IS NULL + AND entry_module_id IS NULL + AND engine_type = 'govdoc' + AND review_scope = 'govdoc'; + +COMMIT; diff --git a/scripts/创建sql/schema_qichacha_company_info.sql b/scripts/创建sql/schema_qichacha_company_info.sql new file mode 100644 index 0000000..5358076 --- /dev/null +++ b/scripts/创建sql/schema_qichacha_company_info.sql @@ -0,0 +1,36 @@ +-- 企查查企业主体信息缓存表。 + +CREATE TABLE IF NOT EXISTS qcc_company_info ( + id BIGSERIAL PRIMARY KEY, + search_key VARCHAR(200) NOT NULL, + credit_code VARCHAR(64), + company_name VARCHAR(255), + enterprise JSONB, + dishonesty JSONB, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMPTZ +); + +CREATE UNIQUE INDEX IF NOT EXISTS idx_qcc_company_search_key + ON qcc_company_info (search_key) + WHERE deleted_at IS NULL; + +CREATE INDEX IF NOT EXISTS idx_qcc_company_credit_code + ON qcc_company_info (credit_code) + WHERE deleted_at IS NULL; + +CREATE INDEX IF NOT EXISTS idx_qcc_company_name + ON qcc_company_info (company_name) + WHERE deleted_at IS NULL; + +CREATE INDEX IF NOT EXISTS idx_qcc_company_created_at + ON qcc_company_info (created_at) + WHERE deleted_at IS NULL; + +COMMENT ON TABLE qcc_company_info IS '企查查企业主体信息缓存表'; +COMMENT ON COLUMN qcc_company_info.search_key IS '原始查询关键词'; +COMMENT ON COLUMN qcc_company_info.credit_code IS '统一社会信用代码'; +COMMENT ON COLUMN qcc_company_info.company_name IS '企业名称'; +COMMENT ON COLUMN qcc_company_info.enterprise IS '企查查工商信息原始响应 Result'; +COMMENT ON COLUMN qcc_company_info.dishonesty IS '企查查失信核查原始响应 Result'; diff --git a/scripts/创建sql/seed_govdoc_entry_module.sql b/scripts/创建sql/seed_govdoc_entry_module.sql index 47f036f..858727f 100644 --- a/scripts/创建sql/seed_govdoc_entry_module.sql +++ b/scripts/创建sql/seed_govdoc_entry_module.sql @@ -6,7 +6,7 @@ -- 3. 兼容历史库中 entry_modules 时间字段命名差异 -- -- 说明: --- - 当前模块入口统一收口到 /govdoc/list,与模块路由 seed 保持一致。 +-- - 当前模块入口统一收口到 /govdoc/audits,与前端真实列表路由保持一致。 -- - 若后续前端改成其他入口页,请同步更新本脚本中的 v_target_path。 -- - 页面访问权限与左侧菜单路由仍依赖 sys_routes / role_route, -- 这部分建议由配套脚本 seed_govdoc_routes.sql 单独维护。 @@ -18,7 +18,7 @@ DO $$ DECLARE v_name text := '内部公文'; v_description text := '内部公文处理与格式审查入口'; - v_target_path text := '/govdoc/list'; + v_target_path text := '/govdoc/audits'; v_icon_path text := 'documents/mz/static/img/entry_module_3.png'; v_sort_order integer := 30; v_areas jsonb := '[ diff --git a/scripts/创建sql/seed_govdoc_routes.sql b/scripts/创建sql/seed_govdoc_routes.sql index 4727efd..494c0ba 100644 --- a/scripts/创建sql/seed_govdoc_routes.sql +++ b/scripts/创建sql/seed_govdoc_routes.sql @@ -103,16 +103,16 @@ VALUES NULL ), ( - '/govdoc/list', - 'govdoc.list', - 'govdoc.list', + '/govdoc/audits', + 'govdoc.audits', + 'govdoc.audits', (SELECT id FROM root_route), '公文列表', 'ri-file-list-3-line', 2, FALSE, TRUE, - '{"group":"govdoc","module":"govdoc","page":"list"}'::jsonb, + '{"group":"govdoc","module":"govdoc","page":"audits"}'::jsonb, 0, NOW(), NOW(), @@ -187,7 +187,7 @@ SET parent_id = root.id, route_title = CASE WHEN child.route_path = '/govdoc/upload' THEN '上传公文' - WHEN child.route_path = '/govdoc/list' THEN '公文列表' + WHEN child.route_path = '/govdoc/audits' THEN '公文列表' WHEN child.route_path = '/govdoc/detail' THEN '公文详情' WHEN child.route_path = '/govdoc/rules' THEN '规则配置' WHEN child.route_path = '/govdoc/settings' THEN '模块配置' @@ -204,7 +204,7 @@ WHERE child.deleted_at IS NULL AND root.route_path = '/govdoc' AND child.route_path IN ( '/govdoc/upload', - '/govdoc/list', + '/govdoc/audits', '/govdoc/detail', '/govdoc/rules', '/govdoc/settings' @@ -229,7 +229,7 @@ route_map AS ( AND route_path IN ( '/govdoc', '/govdoc/upload', - '/govdoc/list', + '/govdoc/audits', '/govdoc/detail', '/govdoc/rules', '/govdoc/settings' @@ -239,27 +239,27 @@ seed(role_key, route_path, permission, status) AS ( VALUES ('super_admin', '/govdoc', 'RW', 1), ('super_admin', '/govdoc/upload', 'RW', 1), - ('super_admin', '/govdoc/list', 'RW', 1), + ('super_admin', '/govdoc/audits', 'RW', 1), ('super_admin', '/govdoc/detail', 'RW', 1), ('super_admin', '/govdoc/rules', 'RW', 1), ('super_admin', '/govdoc/settings', 'RW', 1), ('provincial_admin', '/govdoc', 'RW', 1), ('provincial_admin', '/govdoc/upload', 'RW', 1), - ('provincial_admin', '/govdoc/list', 'RW', 1), + ('provincial_admin', '/govdoc/audits', 'RW', 1), ('provincial_admin', '/govdoc/detail', 'RW', 1), ('provincial_admin', '/govdoc/rules', 'RW', 1), ('provincial_admin', '/govdoc/settings', 'RW', 1), ('admin', '/govdoc', 'RW', 1), ('admin', '/govdoc/upload', 'RW', 1), - ('admin', '/govdoc/list', 'RW', 1), + ('admin', '/govdoc/audits', 'RW', 1), ('admin', '/govdoc/detail', 'RW', 1), ('admin', '/govdoc/rules', 'R', 1), ('common', '/govdoc', 'R', 1), ('common', '/govdoc/upload', 'R', 1), - ('common', '/govdoc/list', 'R', 1), + ('common', '/govdoc/audits', 'R', 1), ('common', '/govdoc/detail', 'R', 1), ('common', '/govdoc/rules', 'R', 1) ) diff --git a/scripts/创建sql/user_rbac_seed.sql b/scripts/创建sql/user_rbac_seed.sql index f989335..a6776dd 100644 --- a/scripts/创建sql/user_rbac_seed.sql +++ b/scripts/创建sql/user_rbac_seed.sql @@ -91,11 +91,6 @@ VALUES ('rules:binding_create:write', 'rules', 'binding_create', 'write', '创建规则绑定', '创建规则绑定', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 49, NULL, '/api/rule-sets/{rule_type}/bindings', 'POST', NULL), ('rules:binding_update:write', 'rules', 'binding_update', 'write', '更新规则绑定', '更新规则绑定', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 50, NULL, '/api/rule-sets/bindings/{binding_id}', 'PUT', NULL), ('rules:binding_delete:delete', 'rules', 'binding_delete', 'delete', '删除规则绑定', '删除规则绑定', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 51, NULL, '/api/rule-sets/bindings/{binding_id}', 'DELETE', NULL), - ('evaluation_point:list:read', 'evaluation_point', 'list', 'read', '查看评查点列表', '评查点列表', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 52, NULL, '/api/v3/evaluation-points', 'GET', NULL), - ('evaluation_point:detail:read', 'evaluation_point', 'detail', 'read', '查看评查点详情', '评查点详情', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 53, NULL, '/api/v3/evaluation-points/{id}', 'GET', NULL), - ('evaluation_point:create:write', 'evaluation_point', 'create', 'write', '创建评查点', '创建评查点', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 54, NULL, '/api/v3/evaluation-points', 'POST', NULL), - ('evaluation_point:update:write', 'evaluation_point', 'update', 'write', '更新评查点', '更新评查点', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 55, NULL, '/api/v3/evaluation-points/{id}', 'PUT', NULL), - ('evaluation_point:delete:delete', 'evaluation_point', 'delete', 'delete', '删除评查点', '删除评查点', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 56, NULL, '/api/v3/evaluation-points/{id}', 'DELETE', NULL), ('cross_review:task:create', 'cross_review', 'task', 'create', '创建交叉评查任务', '创建交叉评查任务', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 57, NULL, '/api/v3/cross-review/tasks', 'POST', NULL), ('cross_review:task:read', 'cross_review', 'task', 'read', '查看交叉评查任务', '查看交叉评查任务', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 58, NULL, '/api/v3/cross-review/tasks/query', 'POST', NULL), ('cross_review:progress:view', 'cross_review', 'progress', 'view', '查看交叉评查任务进度', '查看交叉评查任务进度', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 59, NULL, '/api/v3/cross-review/tasks/{task_id}/progress', 'GET', NULL), @@ -124,7 +119,9 @@ VALUES ('rag:conversation:delete', 'rag', 'conversation', 'delete', '删除 RAG 会话', '删除 RAG 会话', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 89, NULL, '/api/v3/rag/chat/conversations/{ConversationId}', 'DELETE', NULL), ('rag:message:feedback', 'rag', 'message', 'feedback', '反馈 RAG 消息', '反馈 RAG 消息', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 90, NULL, '/api/v3/rag/chat/messages/{MessageId}/feedback', 'POST', NULL), ('rag:dataset:read', 'rag', 'dataset', 'read', '查看 RAG 知识库', '查看 RAG 知识库', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 91, NULL, '/api/v3/rag/datasets/my', 'GET', NULL), - ('document:document:update', 'document', 'document', 'update', '修改文档基本信息', '修改文档', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 92, NULL, '/api/documents/edit', 'POST', NULL) + ('document:document:update', 'document', 'document', 'update', '修改文档基本信息', '修改文档', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 92, NULL, '/api/documents/edit', 'POST', NULL), + ('qichacha:company:query', 'qichacha', 'company', 'query', '查询企业主体信息', '查询企业主体信息', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 93, NULL, '/api/v2/qichacha/company', 'POST', NULL), + ('qichacha:status:read', 'qichacha', 'status', 'read', '查看企业主体缓存状态', '查看企业主体缓存状态', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 94, NULL, '/api/v2/qichacha/status', 'GET', NULL) ON CONFLICT (permission_key) DO UPDATE SET module = EXCLUDED.module, resource = EXCLUDED.resource, @@ -245,11 +242,6 @@ seed(role_key, permission_key, grant_type, data_scope) AS ( ('super_admin', 'rules:binding_create:write', 'GRANT', 'ALL'), ('super_admin', 'rules:binding_update:write', 'GRANT', 'ALL'), ('super_admin', 'rules:binding_delete:delete', 'GRANT', 'ALL'), - ('super_admin', 'evaluation_point:list:read', 'GRANT', 'ALL'), - ('super_admin', 'evaluation_point:detail:read', 'GRANT', 'ALL'), - ('super_admin', 'evaluation_point:create:write', 'GRANT', 'ALL'), - ('super_admin', 'evaluation_point:update:write', 'GRANT', 'ALL'), - ('super_admin', 'evaluation_point:delete:delete', 'GRANT', 'ALL'), ('super_admin', 'cross_review:task:create', 'GRANT', 'ALL'), ('super_admin', 'cross_review:task:read', 'GRANT', 'ALL'), ('super_admin', 'cross_review:progress:view', 'GRANT', 'ALL'), @@ -276,6 +268,8 @@ seed(role_key, permission_key, grant_type, data_scope) AS ( ('super_admin', 'rbac:permissions:read', 'GRANT', 'ALL'), ('super_admin', 'rbac:role_permissions:write', 'GRANT', 'ALL'), ('super_admin', 'rbac:role_routes:write', 'GRANT', 'ALL'), + ('super_admin', 'qichacha:company:query', 'GRANT', 'ALL'), + ('super_admin', 'qichacha:status:read', 'GRANT', 'ALL'), ('provincial_admin', 'auth:me:read', 'GRANT', 'ALL'), ('provincial_admin', 'documents:upload:write', 'GRANT', 'ALL'), @@ -298,11 +292,6 @@ seed(role_key, permission_key, grant_type, data_scope) AS ( ('provincial_admin', 'rules:binding_create:write', 'GRANT', 'ALL'), ('provincial_admin', 'rules:binding_update:write', 'GRANT', 'ALL'), ('provincial_admin', 'rules:binding_delete:delete', 'GRANT', 'ALL'), - ('provincial_admin', 'evaluation_point:list:read', 'GRANT', 'ALL'), - ('provincial_admin', 'evaluation_point:detail:read', 'GRANT', 'ALL'), - ('provincial_admin', 'evaluation_point:create:write', 'GRANT', 'ALL'), - ('provincial_admin', 'evaluation_point:update:write', 'GRANT', 'ALL'), - ('provincial_admin', 'evaluation_point:delete:delete', 'GRANT', 'ALL'), ('provincial_admin', 'cross_review:task:create', 'GRANT', 'ALL'), ('provincial_admin', 'cross_review:task:read', 'GRANT', 'ALL'), ('provincial_admin', 'cross_review:progress:view', 'GRANT', 'ALL'), @@ -329,6 +318,8 @@ seed(role_key, permission_key, grant_type, data_scope) AS ( ('provincial_admin', 'rbac:permissions:read', 'GRANT', 'ALL'), ('provincial_admin', 'rbac:role_permissions:write', 'GRANT', 'ALL'), ('provincial_admin', 'rbac:role_routes:write', 'GRANT', 'ALL'), + ('provincial_admin', 'qichacha:company:query', 'GRANT', 'ALL'), + ('provincial_admin', 'qichacha:status:read', 'GRANT', 'ALL'), ('admin', 'auth:me:read', 'GRANT', 'DEPT'), ('admin', 'documents:upload:write', 'GRANT', 'DEPT'), @@ -360,11 +351,6 @@ seed(role_key, permission_key, grant_type, data_scope) AS ( ('admin', 'cross_review:proposal:read', 'GRANT', 'DEPT'), ('admin', 'cross_review:proposal:delete', 'GRANT', 'DEPT'), ('admin', 'cross_review:proposal:vote', 'GRANT', 'DEPT'), - ('admin', 'evaluation_point:list:read', 'GRANT', 'DEPT'), - ('admin', 'evaluation_point:detail:read', 'GRANT', 'DEPT'), - ('admin', 'evaluation_point:create:write', 'GRANT', 'DEPT'), - ('admin', 'evaluation_point:update:write', 'GRANT', 'DEPT'), - ('admin', 'evaluation_point:delete:delete', 'GRANT', 'DEPT'), ('admin', 'rag:app:read', 'GRANT', 'DEPT'), ('admin', 'rag:chat:use', 'GRANT', 'DEPT'), ('admin', 'rag:conversation:read', 'GRANT', 'DEPT'), @@ -374,6 +360,7 @@ seed(role_key, permission_key, grant_type, data_scope) AS ( ('admin', 'rag:dataset:read', 'GRANT', 'DEPT'), ('admin', 'users:list:read', 'GRANT', 'DEPT'), ('admin', 'users:update:write', 'GRANT', 'DEPT'), + ('admin', 'qichacha:company:query', 'GRANT', 'DEPT'), ('common', 'auth:me:read', 'GRANT', 'SELF'), ('common', 'documents:upload:write', 'GRANT', 'SELF'), diff --git a/scripts/创建sql/verify_entry_module_menu_profile.sql b/scripts/创建sql/verify_entry_module_menu_profile.sql new file mode 100644 index 0000000..27cacac --- /dev/null +++ b/scripts/创建sql/verify_entry_module_menu_profile.sql @@ -0,0 +1,209 @@ +-- 入口模块菜单模板、多租户可见性、文档归属巡检脚本 +-- 用途: +-- 1. 检查 entry_module_menu_profile_migration.sql 是否补齐字段、索引、默认值。 +-- 2. 检查入口模块 features/menu_profile 是否存在非法值或空配置。 +-- 3. 检查文档 entry_module_id 回填情况和二级分组重复风险。 +-- 4. 本脚本只查询,不修改数据。 +-- +-- 推荐执行方式: +-- psql "$DATABASE_URL" -f scripts/创建sql/verify_entry_module_menu_profile.sql + +-- ========================================================= +-- 1. 必要字段检查 +-- ========================================================= +SELECT + table_name, + column_name, + data_type, + is_nullable, + column_default +FROM information_schema.columns +WHERE table_schema = current_schema() + AND ( + (table_name = 'leaudit_entry_modules' AND column_name IN ('menu_profile', 'features')) + OR (table_name = 'leaudit_documents' AND column_name = 'entry_module_id') + ) +ORDER BY table_name, column_name; + +-- ========================================================= +-- 2. 必要索引检查 +-- ========================================================= +SELECT + schemaname, + tablename, + indexname, + indexdef +FROM pg_indexes +WHERE schemaname = current_schema() + AND indexname IN ( + 'idx_leaudit_entry_modules_menu_profile', + 'idx_leaudit_documents_entry_module_id', + 'uq_leaudit_ep_groups_parent_doc_type_active' + ) +ORDER BY indexname; + +-- ========================================================= +-- 3. 入口模块菜单配置总览 +-- ========================================================= +SELECT + em.id, + em.name, + em.path, + em.menu_profile, + em.features, + em.is_enabled, + COALESCE( + STRING_AGG(emt.tenant_code, ', ' ORDER BY emt.tenant_code) + FILTER (WHERE emt.is_enabled IS TRUE), + '' + ) AS enabled_tenants +FROM leaudit_entry_modules em +LEFT JOIN leaudit_entry_module_tenants emt + ON emt.entry_module_id = em.id +WHERE em.deleted_at IS NULL +GROUP BY em.id, em.name, em.path, em.menu_profile, em.features, em.is_enabled +ORDER BY em.sort_order, em.id; + +-- ========================================================= +-- 4. 非法 menu_profile 检查:结果应为空 +-- ========================================================= +SELECT + id, + name, + path, + menu_profile +FROM leaudit_entry_modules +WHERE deleted_at IS NULL + AND menu_profile NOT IN ('document_review', 'contract', 'govdoc', 'cross_checking', 'custom') +ORDER BY id; + +-- ========================================================= +-- 5. 空 features 检查:结果应为空或只包含刻意关闭菜单的入口 +-- ========================================================= +SELECT + id, + name, + path, + menu_profile, + features +FROM leaudit_entry_modules +WHERE deleted_at IS NULL + AND ( + features IS NULL + OR jsonb_typeof(features) <> 'array' + OR jsonb_array_length(features) = 0 + ) +ORDER BY id; + +-- ========================================================= +-- 6. 非法 feature 编码检查:结果应为空 +-- ========================================================= +SELECT + em.id, + em.name, + em.menu_profile, + invalid_feature.feature +FROM leaudit_entry_modules em +CROSS JOIN LATERAL jsonb_array_elements_text(em.features) AS invalid_feature(feature) +WHERE em.deleted_at IS NULL + AND invalid_feature.feature NOT IN ( + 'home', + 'documents', + 'upload', + 'rules', + 'rule_groups', + 'contract_template_search', + 'contract_template_list', + 'govdoc_audits', + 'govdoc_upload', + 'cross_checking', + 'cross_checking_upload', + 'cross_checking_list', + 'usage_stats' + ) +ORDER BY em.id, invalid_feature.feature; + +-- ========================================================= +-- 7. 文档归属缺失检查 +-- ========================================================= +SELECT + COUNT(*) AS documents_without_entry_module +FROM leaudit_documents +WHERE deleted_at IS NULL + AND entry_module_id IS NULL; + +SELECT + d.id, + d.normalized_name AS document_name, + d.type_id, + dt.name AS document_type_name, + d.group_id, + g.name AS group_name, + d.tenant_code, + d.created_at +FROM leaudit_documents d +LEFT JOIN leaudit_document_types dt + ON dt.id = d.type_id +LEFT JOIN leaudit_evaluation_point_groups g + ON g.id = d.group_id +WHERE d.deleted_at IS NULL + AND d.entry_module_id IS NULL +ORDER BY d.created_at DESC NULLS LAST, d.id DESC +LIMIT 50; + +-- ========================================================= +-- 8. 文档 entry_module_id 与分组/文档类型推导不一致检查 +-- ========================================================= +SELECT + d.id, + d.normalized_name AS document_name, + d.entry_module_id AS document_entry_module_id, + COALESCE(g.entry_module_id, parent.entry_module_id, dt.entry_module_id) AS resolved_entry_module_id, + d.type_id, + d.group_id, + d.tenant_code +FROM leaudit_documents d +LEFT JOIN leaudit_document_types dt + ON dt.id = d.type_id +LEFT JOIN leaudit_evaluation_point_groups g + ON g.id = d.group_id +LEFT JOIN leaudit_evaluation_point_groups parent + ON parent.id = g.pid +WHERE d.deleted_at IS NULL + AND d.entry_module_id IS NOT NULL + AND COALESCE(g.entry_module_id, parent.entry_module_id, dt.entry_module_id) IS NOT NULL + AND d.entry_module_id <> COALESCE(g.entry_module_id, parent.entry_module_id, dt.entry_module_id) +ORDER BY d.id DESC +LIMIT 100; + +-- ========================================================= +-- 9. 重复二级分组检查:结果应为空 +-- ========================================================= +SELECT + pid, + document_type_id, + COUNT(*) AS duplicated_count, + STRING_AGG(id::text || ':' || name, ' / ' ORDER BY id) AS groups +FROM leaudit_evaluation_point_groups +WHERE deleted_at IS NULL + AND COALESCE(pid, 0) <> 0 + AND document_type_id IS NOT NULL +GROUP BY pid, document_type_id +HAVING COUNT(*) > 1 +ORDER BY duplicated_count DESC, pid, document_type_id; + +-- ========================================================= +-- 10. 一级分组缺入口模块检查:兼容期允许存在,但新链路应逐步清零 +-- ========================================================= +SELECT + id, + code, + name, + document_type_id, + sort_order, + is_enabled +FROM leaudit_evaluation_point_groups +WHERE deleted_at IS NULL + AND COALESCE(pid, 0) = 0 + AND entry_module_id IS NULL +ORDER BY sort_order, id; diff --git a/test-results/.last-run.json b/test-results/.last-run.json new file mode 100644 index 0000000..5fca3f8 --- /dev/null +++ b/test-results/.last-run.json @@ -0,0 +1,4 @@ +{ + "status": "failed", + "failedTests": [] +} \ No newline at end of file diff --git a/test-results/leaudit-playwright-entry-module-remaining-acceptance.js b/test-results/leaudit-playwright-entry-module-remaining-acceptance.js new file mode 100644 index 0000000..7f5f92a --- /dev/null +++ b/test-results/leaudit-playwright-entry-module-remaining-acceptance.js @@ -0,0 +1,167 @@ +const { chromium, request } = require('/home/wren-dev/Porject/leaudit-platform/legal-platform-frontend/node_modules/playwright'); + +const FRONTEND = process.env.LEAUDIT_FRONTEND_URL || 'http://172.16.0.59:5173'; +const BACKEND = process.env.LEAUDIT_BACKEND_URL || 'http://127.0.0.1:8096'; +const runId = `pwr${Date.now()}`; +const results = []; + +function record(name, ok, details = {}) { + results.push({ name, ok, details }); + console.log(`[${ok ? 'PASS' : 'FAIL'}] ${name}: ${JSON.stringify(details)}`); + if (!ok) throw new Error(`${name}: ${JSON.stringify(details)}`); +} + +async function asJson(response) { + try { + return await response.json(); + } catch { + return { text: await response.text() }; + } +} + +function unwrap(payload) { + if (payload && typeof payload === 'object' && Object.prototype.hasOwnProperty.call(payload, 'data')) { + return payload.data; + } + return payload; +} + +async function apiFetch(api, method, path, token, options = {}) { + const headers = { ...(options.headers || {}) }; + if (token) headers.Authorization = `Bearer ${token}`; + const response = await api.fetch(`${BACKEND}${path}`, { method, ...options, headers }); + return { response, body: await asJson(response) }; +} + +async function loginFrontendContext(browser) { + const context = await browser.newContext({ viewport: { width: 1440, height: 1000 } }); + const loginResponse = await context.request.post(`${FRONTEND}/api/auth/password-login`, { + data: { username: '000', password: 'admin06111' }, + }); + const loginBody = await asJson(loginResponse); + if (!loginResponse.ok() || !loginBody?.access_token) { + throw new Error(`前端登录失败 ${loginResponse.status()} ${JSON.stringify(loginBody)}`); + } + return { context, token: loginBody.access_token }; +} + +async function createEntryModule(api, token) { + const payload = { + name: `PW验收政务入口-${runId}`, + description: 'playwright remaining acceptance', + path: '/govdoc/audits', + route_path: '/govdoc/audits', + menu_profile: 'govdoc', + features: ['home', 'govdoc_audits', 'govdoc_upload', 'rule_groups'], + tenants: [{ tenant_code: 'PUBLIC', tenant_name: '公共资源域', enabled: true, sort_order: 1 }], + }; + const { response, body } = await apiFetch(api, 'POST', '/api/v3/entry-modules', token, { data: payload }); + record('创建名字不含公文的 govdoc 入口成功', response.status() === 200, { status: response.status(), body }); + const module = unwrap(body); + record('govdoc 入口保存 menu_profile/features', module.menu_profile === 'govdoc' && module.features?.includes('govdoc_audits'), module); + return module; +} + +async function createDocType(api, token, moduleId) { + const payload = { + code: `pw.remaining.${runId}`, + name: `PW验收文档类型-${runId}`, + description: 'playwright remaining acceptance', + entryModuleId: Number(moduleId), + isEnabled: true, + sortOrder: 1, + ruleSetIds: [], + }; + const { response, body } = await apiFetch(api, 'POST', '/api/document-types', token, { data: payload }); + record('为验收入口创建文档类型成功', response.status() === 200, { status: response.status(), body }); + return unwrap(body); +} + +async function sidebarTexts(page) { + return await page.locator('.sidebar a.sidebar-menu-item').evaluateAll((els) => ( + els.map((el) => (el.textContent || '').replace(/\s+/g, ' ').trim()).filter(Boolean) + )); +} + +async function createCommonUserToken(api, adminToken) { + const sub = `pw-no-image-${runId}`; + const login = await apiFetch(api, 'POST', '/api/auth/login', null, { + data: { + userInfo: { + sub, + username: sub, + nickname: `PW无图标权限-${runId}`, + email: `${sub}@playwright.local`, + phone_number: '13800000000', + ou_id: 'pw-no-image', + ou_name: 'Playwright验收组织', + is_leader: false, + }, + area: '公共资源域', + expiresIn: 3600, + }, + }); + record('创建无图标权限测试用户登录成功', login.response.status() === 200, { status: login.response.status(), body: login.body }); + const loginData = unwrap(login.body); + const userId = Number(loginData.user_info?.user_id); + + const roles = await apiFetch(api, 'GET', '/api/v3/rbac/roles?page=1&page_size=200', adminToken); + const roleItems = unwrap(roles.body)?.items || []; + const commonRole = roleItems.find((item) => item.role_key === 'common'); + record('找到 common 角色用于无权限验收', Boolean(commonRole?.id), { commonRole }); + + const assign = await apiFetch(api, 'POST', `/api/v3/rbac/users/${userId}/roles`, adminToken, { + data: { role_ids: [Number(commonRole.id)] }, + }); + record('无图标权限测试用户分配 common 角色成功', assign.response.status() === 200, { status: assign.response.status(), body: assign.body }); + return loginData.access_token; +} + +async function main() { + const browser = await chromium.launch({ headless: true }); + const { context, token } = await loginFrontendContext(browser); + const api = await request.newContext(); + const page = await context.newPage(); + + const module = await createEntryModule(api, token); + await createDocType(api, token, module.id); + + await page.goto(`${FRONTEND}/`, { waitUntil: 'domcontentloaded', timeout: 30000 }); + await page.waitForLoadState('networkidle', { timeout: 20000 }).catch(() => {}); + await page.getByRole('button', { name: module.name, exact: true }).click({ timeout: 15000 }); + await page.waitForURL((url) => url.pathname === '/govdoc/audits' && url.searchParams.get('entryModuleId') === String(module.id), { timeout: 15000 }); + await page.waitForLoadState('networkidle', { timeout: 20000 }).catch(() => {}); + await page.waitForTimeout(1200); + + const side = await sidebarTexts(page); + const headingText = await page.locator('.govdoc-audit-scope h2').first().textContent({ timeout: 10000 }); + const storage = await page.evaluate(() => JSON.parse(sessionStorage.getItem('selectedEntryModuleContext') || 'null')); + + record('公文入口标题使用当前入口模块名称', headingText === `${module.name}文档列表`, { headingText, expected: `${module.name}文档列表` }); + record('公文入口上下文保存为 govdoc', storage?.menuProfile === 'govdoc' && Number(storage?.id) === Number(module.id), storage); + record('govdoc 侧栏显示公文列表/公文上传/规则分组', ['公文列表', '公文上传', '规则分组'].every((text) => side.includes(text)), { side }); + record('govdoc 侧栏不显示普通文件上传/文档列表', !side.includes('文件上传') && !side.includes('文档列表'), { side }); + + const commonToken = await createCommonUserToken(api, token); + const image403 = await apiFetch(api, 'POST', `/api/v3/entry-modules/${module.id}/image`, commonToken, { + multipart: { + file: { + name: 'entry-module.png', + mimeType: 'image/png', + buffer: Buffer.from('89504e470d0a1a0a', 'hex'), + }, + }, + }); + record('无 entry_module:image:write 用户上传入口图标返回 403', image403.response.status() === 403, { status: image403.response.status(), body: image403.body }); + + await api.dispose(); + await context.close(); + await browser.close(); + console.log('\nSUMMARY ' + JSON.stringify({ runId, pass: results.filter((r) => r.ok).length, fail: results.filter((r) => !r.ok).length, results }, null, 2)); +} + +main().catch((err) => { + console.error('\nERROR', err); + console.log('\nSUMMARY ' + JSON.stringify({ runId, pass: results.filter((r) => r.ok).length, fail: results.filter((r) => !r.ok).length, results }, null, 2)); + process.exit(1); +}); diff --git a/tests/test_cross_review_progress_permission.py b/tests/test_cross_review_progress_permission.py new file mode 100644 index 0000000..a2b0962 --- /dev/null +++ b/tests/test_cross_review_progress_permission.py @@ -0,0 +1,55 @@ +from fastapi_modules.fastapi_leaudit.services.impl.crossReviewServiceImpl import CrossReviewServiceImpl + + +def test_cross_review_task_item_masks_progress_without_permission(): + item = CrossReviewServiceImpl._build_task_item_vo( + row={ + "task_id": 10, + "task_name": "交叉评查任务", + "task_type": "CITY", + "doc_type_id": 2, + "doc_type_code": "contract", + "status": "in_progress", + "total_documents": 4, + "completed_documents": 1, + "current_user_role": "principal", + "current_user_can_confirm": True, + "create_time": None, + "evaluation_tenants": [], + "evaluation_regions": [], + }, + CanViewProgress=False, + ) + + assert item.progress is None + assert item.totalDocuments is None + assert item.completedDocuments is None + assert item.currentUserRole == "principal" + assert item.currentUserCanConfirm is True + + +def test_cross_review_task_item_keeps_progress_with_permission(): + item = CrossReviewServiceImpl._build_task_item_vo( + row={ + "task_id": 10, + "task_name": "交叉评查任务", + "task_type": "CITY", + "doc_type_id": 2, + "doc_type_code": "contract", + "status": "in_progress", + "total_documents": 4, + "completed_documents": 1, + "current_user_role": "participant", + "current_user_can_confirm": False, + "create_time": None, + "evaluation_tenants": [], + "evaluation_regions": [], + }, + CanViewProgress=True, + ) + + assert item.progress == 25 + assert item.totalDocuments == 4 + assert item.completedDocuments == 1 + assert item.currentUserRole == "participant" + assert item.currentUserCanConfirm is False diff --git a/tests/test_govdoc_permissions.py b/tests/test_govdoc_permissions.py new file mode 100644 index 0000000..2d4200d --- /dev/null +++ b/tests/test_govdoc_permissions.py @@ -0,0 +1,128 @@ +"""内部公文权限控制测试。""" + +import pytest +from starlette.responses import JSONResponse + +from fastapi_modules.fastapi_leaudit.controllers.govdocController import GovdocController + + +class _DenyPermissionService: + """拒绝所有权限的测试权限服务。""" + + async def CheckPermission(self, user_id: int, permission_key: str) -> bool: + """检查权限。""" + return False + + +class _AllowPermissionService: + """允许所有权限的测试权限服务。""" + + async def CheckPermission(self, user_id: int, permission_key: str) -> bool: + """检查权限。""" + return True + + +class _FakeGovdocService: + """记录调用的测试公文服务。""" + + def __init__(self) -> None: + self.list_called = False + self.upload_called = False + + async def ListDocuments(self, **kwargs): + """记录列表调用。""" + self.list_called = True + return {"items": [], "total": 0, "page": kwargs["page"], "pageSize": kwargs["pageSize"]} + + async def UploadDocument(self, **kwargs): + """记录上传调用。""" + self.upload_called = True + return {"documentId": 1} + + +def _find_endpoint(controller: GovdocController, path: str, method: str): + """根据路径和方法查找路由 endpoint。""" + full_path = f"{controller.router.prefix}{path}" + for route in controller.router.routes: + if getattr(route, "path", "") == full_path and method in getattr(route, "methods", set()): + return route.endpoint + raise AssertionError(f"未找到路由 {method} {full_path}") + + +@pytest.mark.asyncio +async def test_govdoc_list_requires_document_read_permission(): + """公文列表无查看权限时返回 403,且不调用业务服务。""" + controller = GovdocController() + service = _FakeGovdocService() + controller.GovdocService = service + controller.PermissionService = _DenyPermissionService() + endpoint = _find_endpoint(controller, "/documents", "GET") + + response = await endpoint( + page=1, + pageSize=20, + keyword=None, + fileExt=None, + region=None, + tenant_code=None, + entry_module_id=None, + type_ids=None, + document_type_id=None, + status=None, + resultStatus=None, + createdBy=None, + dateFrom=None, + dateTo=None, + payload={"user_id": 7}, + ) + + assert isinstance(response, JSONResponse) + assert response.status_code == 403 + assert service.list_called is False + + +@pytest.mark.asyncio +async def test_govdoc_upload_requires_document_create_permission(): + """公文上传无创建权限时返回 403,且不调用业务服务。""" + controller = GovdocController() + service = _FakeGovdocService() + controller.GovdocService = service + controller.PermissionService = _DenyPermissionService() + endpoint = _find_endpoint(controller, "/documents", "POST") + + response = await endpoint(file=object(), payload={"user_id": 7}) + + assert isinstance(response, JSONResponse) + assert response.status_code == 403 + assert service.upload_called is False + + +@pytest.mark.asyncio +async def test_govdoc_list_calls_service_when_permission_granted(): + """公文列表有查看权限时正常调用业务服务。""" + controller = GovdocController() + service = _FakeGovdocService() + controller.GovdocService = service + controller.PermissionService = _AllowPermissionService() + endpoint = _find_endpoint(controller, "/documents", "GET") + + response = await endpoint( + page=1, + pageSize=20, + keyword=None, + fileExt=None, + region=None, + tenant_code=None, + entry_module_id=None, + type_ids=None, + document_type_id=None, + status=None, + resultStatus=None, + createdBy=None, + dateFrom=None, + dateTo=None, + payload={"user_id": 7}, + ) + + assert response.data["total"] == 0 + assert service.list_called is True diff --git a/tests/test_home_dashboard_statistics.py b/tests/test_home_dashboard_statistics.py new file mode 100644 index 0000000..1aaece5 --- /dev/null +++ b/tests/test_home_dashboard_statistics.py @@ -0,0 +1,73 @@ +"""首页统计接口测试。""" + +import pytest + +from fastapi_modules.fastapi_leaudit.services.impl.homeServiceImpl import HomeServiceImpl + + +class _FakeDocument: + def __init__(self, *, audit_status: int, failed_count: int = 0, updated_at: str = "2026-05-23T10:00:00") -> None: + self.auditStatus = audit_status + self.failedCount = failed_count + self.updatedAt = updated_at + + +class _FakePage: + def __init__(self, *, documents, total_pages: int) -> None: + self.documents = documents + self.totalPages = total_pages + + +class _FakeDocumentService: + def __init__(self) -> None: + self.calls = [] + + async def ListDocuments(self, **kwargs): + self.calls.append(kwargs) + page = kwargs["Page"] + if page == 1: + return _FakePage( + documents=[ + _FakeDocument(audit_status=0, updated_at="2026-05-23T09:00:00"), + _FakeDocument(audit_status=2, updated_at="2026-05-23T09:30:00"), + _FakeDocument(audit_status=1, failed_count=0, updated_at="2026-05-03T10:00:00"), + _FakeDocument(audit_status=1, failed_count=2, updated_at="2026-05-20T10:00:00"), + ], + total_pages=2, + ) + if page == 2: + return _FakePage( + documents=[ + _FakeDocument(audit_status=1, failed_count=1, updated_at="2026-05-22T10:00:00"), + _FakeDocument(audit_status=1, failed_count=0, updated_at="2026-04-18T10:00:00"), + _FakeDocument(audit_status=0, updated_at="2026-05-10T10:00:00"), + ], + total_pages=2, + ) + return _FakePage(documents=[], total_pages=2) + + +@pytest.mark.asyncio +async def test_home_dashboard_statistics_uses_entry_scope_and_all_pages(): + """首页统计按入口模块与文档类型过滤,并拉取全量分页后计算。""" + document_service = _FakeDocumentService() + service = HomeServiceImpl(DocumentService=document_service) + + result = await service.GetDashboardStatistics( + UserId=7, + Today="2026-05-23", + TypeIds=[10, 11], + EntryModuleId=3, + ) + + assert result.todayPendingFiles == 2 + assert result.monthlyReviewedFiles == 3 + assert result.monthlyPassRate == 33 + assert result.issuesDetected == 3 + assert result.monthlyReviewGrowth.value == 200 + assert result.monthlyReviewGrowth.isUp is True + assert {call["EntryModuleId"] for call in document_service.calls} == {3} + assert {tuple(call["TypeIds"]) for call in document_service.calls} == {(10, 11)} + assert [call["Page"] for call in document_service.calls] == [1, 2] + assert all("DateFrom" not in call for call in document_service.calls) + assert all("DateTo" not in call for call in document_service.calls) diff --git a/tests/test_qichacha_config_client.py b/tests/test_qichacha_config_client.py new file mode 100644 index 0000000..b06d497 --- /dev/null +++ b/tests/test_qichacha_config_client.py @@ -0,0 +1,66 @@ +"""企查查配置与客户端测试。""" + +from __future__ import annotations + +import hashlib + +import pytest + +from fastapi_admin import config + + +def test_qichacha_settings_are_exported_from_app_toml(): + """企查查配置应通过 fastapi_admin.config 导出。""" + assert config.QICHACHA_BASE_URL == "https://api.qichacha.com" + assert config.QICHACHA_ENTERPRISE_PATH == "/ECIV4/GetBasicDetailsByName" + assert config.QICHACHA_DISHONESTY_PATH == "/ShixinCheck/GetList" + assert config.QICHACHA_TIMEOUT == 30 + assert config.QICHACHA_CACHE_DAYS == 30 + + +def test_qichacha_client_builds_expected_signature_headers(monkeypatch): + """企查查客户端应按 AppKey + Timespan + SecretKey 生成大写 MD5 Token。""" + from fastapi_modules.fastapi_leaudit.services.impl.qichachaClient import QichachaClient + + monkeypatch.setattr("time.time", lambda: 1779433125) + client = QichachaClient(AppKey="app-key", SecretKey="secret-key") + + headers = client.BuildHeaders() + + expected = hashlib.md5("app-key1779433125secret-key".encode("utf-8")).hexdigest().upper() + assert headers == {"Token": expected, "Timespan": "1779433125"} + + +@pytest.mark.asyncio +async def test_qichacha_client_parses_enterprise_and_dishonesty(monkeypatch): + """企查查客户端应分别解析工商与失信接口结果。""" + from fastapi_modules.fastapi_leaudit.services.impl.qichachaClient import QichachaClient + + calls: list[tuple[str, dict[str, str]]] = [] + + async def fake_request(self, Url: str, Params: dict[str, str]) -> dict: + calls.append((Url, Params)) + if "GetBasicDetailsByName" in Url: + return { + "Status": "200", + "Message": "成功", + "Result": {"Name": "广州测试有限公司", "CreditCode": "91440000TEST"}, + } + return { + "Status": "200", + "Message": "成功", + "Result": {"VerifyResult": 1, "Data": [{"Anno": "案号1"}]}, + } + + monkeypatch.setattr(QichachaClient, "Request", fake_request) + client = QichachaClient(AppKey="app-key", SecretKey="secret-key") + + enterprise = await client.GetEnterpriseInfo("广州测试有限公司") + dishonesty = await client.GetDishonestyInfo("广州测试有限公司") + + assert enterprise["Name"] == "广州测试有限公司" + assert enterprise["CreditCode"] == "91440000TEST" + assert dishonesty["VerifyResult"] == 1 + assert dishonesty["Data"][0]["Anno"] == "案号1" + assert calls[0][1] == {"key": "app-key", "keyword": "广州测试有限公司"} + assert calls[1][1] == {"key": "app-key", "searchKey": "广州测试有限公司"} diff --git a/tests/test_qichacha_service.py b/tests/test_qichacha_service.py new file mode 100644 index 0000000..19a134d --- /dev/null +++ b/tests/test_qichacha_service.py @@ -0,0 +1,194 @@ +"""企查查服务测试。""" + +from __future__ import annotations + +from datetime import UTC, datetime, timedelta +from types import SimpleNamespace + +import pytest + + +class _FakeSession: + async def commit(self): + return None + + async def refresh(self, obj): + return obj + + async def flush(self): + return None + + +class _FakeSessionContext: + async def __aenter__(self): + return _FakeSession() + + async def __aexit__(self, exc_type, exc, tb): + return None + + +async def _unexpected_upsert(cls, session, **fields): + pytest.fail("fresh cache should not upsert") + + +class _FakeClient: + def __init__(self): + self.calls: list[tuple[str, str]] = [] + + async def QueryCompany(self, Keyword: str): + self.calls.append(("company", Keyword)) + return ( + {"Name": "广州测试有限公司", "CreditCode": "91440000TEST"}, + {"VerifyResult": 1, "Data": [{"Anno": "案号1"}, {"Anno": "案号2"}]}, + "91440000TEST", + "广州测试有限公司", + ) + + async def GetEnterpriseInfo(self, Keyword: str): + self.calls.append(("enterprise", Keyword)) + return {"Name": "广州测试有限公司", "CreditCode": "91440000TEST"} + + async def GetDishonestyInfo(self, Keyword: str): + self.calls.append(("dishonesty", Keyword)) + return {"VerifyResult": 0, "Data": []} + + +@pytest.mark.asyncio +async def test_query_company_uses_fresh_cache(monkeypatch): + """未过期缓存应直接返回,不调用企查查 API。""" + from fastapi_modules.fastapi_leaudit.models.qichachaCompanyInfo import QichachaCompanyInfo + from fastapi_modules.fastapi_leaudit.services.impl.qichachaServiceImpl import QichachaServiceImpl + + now = datetime.now(UTC) + record = SimpleNamespace( + Id=1, + searchKey="广州测试有限公司", + creditCode="91440000TEST", + companyName="广州测试有限公司", + enterprise={"Name": "广州测试有限公司", "CreditCode": "91440000TEST"}, + dishonesty={"VerifyResult": 0, "Data": []}, + created_at=now, + updated_at=now, + ) + fake_client = _FakeClient() + + async def fake_find(cls, session, Keyword): + return record + + monkeypatch.setattr( + "fastapi_modules.fastapi_leaudit.services.impl.qichachaServiceImpl.GetAsyncSession", + lambda: _FakeSessionContext(), + ) + monkeypatch.setattr(QichachaCompanyInfo, "FindByKeyword", classmethod(fake_find)) + monkeypatch.setattr(QichachaCompanyInfo, "Upsert", classmethod(_unexpected_upsert)) + + service = QichachaServiceImpl(Client=fake_client, CacheDays=30) + result = await service.QueryCompany(Keyword="广州测试有限公司", ForceRefresh=False) + + assert result.success is True + assert result.data is not None + assert result.data.companyName == "广州测试有限公司" + assert result.data.hasDishonesty is False + assert result.data.dishonestyCount == 0 + assert fake_client.calls == [] + + +@pytest.mark.asyncio +async def test_query_company_refreshes_when_forced(monkeypatch): + """强制刷新时应调用企查查 API 并写入缓存。""" + from fastapi_modules.fastapi_leaudit.models.qichachaCompanyInfo import QichachaCompanyInfo + from fastapi_modules.fastapi_leaudit.services.impl.qichachaServiceImpl import QichachaServiceImpl + + old_record = SimpleNamespace( + Id=1, + searchKey="旧名称", + creditCode=None, + companyName=None, + enterprise=None, + dishonesty=None, + created_at=datetime.now(UTC) - timedelta(days=1), + updated_at=datetime.now(UTC) - timedelta(days=1), + ) + saved: dict[str, object] = {} + fake_client = _FakeClient() + + async def fake_find(cls, session, Keyword): + return old_record + + async def fake_upsert(cls, session, **fields): + saved.update(fields) + return SimpleNamespace( + Id=1, + searchKey=fields["SearchKey"], + creditCode=fields["CreditCode"], + companyName=fields["CompanyName"], + enterprise=fields["Enterprise"], + dishonesty=fields["Dishonesty"], + created_at=datetime.now(UTC), + updated_at=datetime.now(UTC), + ) + + monkeypatch.setattr( + "fastapi_modules.fastapi_leaudit.services.impl.qichachaServiceImpl.GetAsyncSession", + lambda: _FakeSessionContext(), + ) + monkeypatch.setattr(QichachaCompanyInfo, "FindByKeyword", classmethod(fake_find)) + monkeypatch.setattr(QichachaCompanyInfo, "Upsert", classmethod(fake_upsert)) + + service = QichachaServiceImpl(Client=fake_client, CacheDays=30) + result = await service.QueryCompany(Keyword="广州测试有限公司", ForceRefresh=True) + + assert fake_client.calls == [("company", "广州测试有限公司")] + assert saved["SearchKey"] == "广州测试有限公司" + assert saved["CreditCode"] == "91440000TEST" + assert result.data is not None + assert result.data.hasDishonesty is True + assert result.data.dishonestyCount == 2 + + +@pytest.mark.asyncio +async def test_query_company_refreshes_stale_cache(monkeypatch): + """超过缓存天数的数据应自动刷新。""" + from fastapi_modules.fastapi_leaudit.models.qichachaCompanyInfo import QichachaCompanyInfo + from fastapi_modules.fastapi_leaudit.services.impl.qichachaServiceImpl import QichachaServiceImpl + + stale_record = SimpleNamespace( + Id=1, + searchKey="广州测试有限公司", + creditCode="OLD", + companyName="旧公司", + enterprise={"Name": "旧公司"}, + dishonesty={"VerifyResult": 0, "Data": []}, + created_at=datetime.now(UTC) - timedelta(days=90), + updated_at=datetime.now(UTC) - timedelta(days=90), + ) + fake_client = _FakeClient() + + async def fake_find(cls, session, Keyword): + return stale_record + + async def fake_upsert(cls, session, **fields): + return SimpleNamespace( + Id=1, + searchKey=fields["SearchKey"], + creditCode=fields["CreditCode"], + companyName=fields["CompanyName"], + enterprise=fields["Enterprise"], + dishonesty=fields["Dishonesty"], + created_at=datetime.now(UTC), + updated_at=datetime.now(UTC), + ) + + monkeypatch.setattr( + "fastapi_modules.fastapi_leaudit.services.impl.qichachaServiceImpl.GetAsyncSession", + lambda: _FakeSessionContext(), + ) + monkeypatch.setattr(QichachaCompanyInfo, "FindByKeyword", classmethod(fake_find)) + monkeypatch.setattr(QichachaCompanyInfo, "Upsert", classmethod(fake_upsert)) + + service = QichachaServiceImpl(Client=fake_client, CacheDays=30) + result = await service.QueryCompany(Keyword="广州测试有限公司") + + assert fake_client.calls == [("company", "广州测试有限公司")] + assert result.data is not None + assert result.data.companyName == "广州测试有限公司" diff --git a/tests/test_review_document_payload_page_quality.py b/tests/test_review_document_payload_page_quality.py new file mode 100644 index 0000000..5ddcee3 --- /dev/null +++ b/tests/test_review_document_payload_page_quality.py @@ -0,0 +1,96 @@ +from fastapi_modules.fastapi_leaudit.domian.vo.documentVo import DocumentDetailVO +from fastapi_modules.fastapi_leaudit.domian.vo.pageQualityVo import PageQualitySummaryVO +from fastapi_modules.fastapi_leaudit.services.impl.documentServiceImpl import DocumentServiceImpl + + +def test_review_document_payload_includes_page_quality_summary(): + detail = DocumentDetailVO( + documentId=71, + internalDocumentNo=10071, + versionGroupKey="vg-71", + versionNo=1, + rootVersionId=71, + previousVersionId=None, + typeId=10, + typeCode="case", + typeName="行政许可", + groupId=None, + groupName=None, + region="梅州", + tenantCode="MEIZHOU", + tenantName="梅州", + normalizedName="图片模糊测试", + fileId=7001, + fileName="(图片模糊)第71号.pdf", + fileExt="pdf", + mimeType="application/pdf", + fileSize=1024, + ossUrl="/bucket/documents/71.pdf", + processingStatus="completed", + currentRunId=9001, + runStatus="completed", + resultStatus="warning", + latestErrorCode=None, + latestErrorMessage=None, + totalScore=88, + passedCount=2, + failedCount=1, + skippedCount=0, + documentNumber="71", + auditStatus=0, + isTestDocument=False, + pageQualityRunId=501, + pageQualityRunStatus="completed", + pageQualitySummaryStatus="review", + pageQualityIssueCount=2, + pageQualityWarningText="发现疑似模糊页", + updatedAt="2026-05-23T10:00:00", + hasHistory=False, + totalVersions=1, + historyVersions=[], + remark=None, + pageCount=10, + pageQualitySummary=PageQualitySummaryVO( + runId=501, + runStatus="completed", + summaryStatus="review", + totalPages=10, + reviewPageCount=2, + rejectPageCount=0, + warningText="发现疑似模糊页", + pages=[3, 7], + finishedAt="2026-05-23T10:01:00", + ), + attachments=[], + ) + + payload = DocumentServiceImpl._buildReviewPageQualityPayload( + detail, + [ + {"pageNum": 7, "qualityStatus": "review", "qualityScore": 0.64, "reasonText": "图片略模糊"}, + {"pageNum": 3, "qualityStatus": "reject", "qualityScore": 0.25, "reasonText": "图片严重模糊"}, + ], + ) + + assert payload == { + "pageQualityRunId": 501, + "pageQualityRunStatus": "completed", + "pageQualitySummaryStatus": "review", + "pageQualityIssueCount": 2, + "pageQualityWarningText": "发现疑似模糊页", + "pageQualitySummary": { + "runId": 501, + "runStatus": "completed", + "summaryStatus": "review", + "totalPages": 10, + "reviewPageCount": 2, + "rejectPageCount": 0, + "warningText": "发现疑似模糊页", + "pages": [3, 7], + "finishedAt": "2026-05-23T10:01:00", + }, + "pageQualityResults": [ + {"pageNum": 3, "qualityStatus": "reject", "qualityScore": 0.25, "reasonText": "图片严重模糊"}, + {"pageNum": 7, "qualityStatus": "review", "qualityScore": 0.64, "reasonText": "图片略模糊"}, + ], + } diff --git a/tests/test_rule_write_scope.py b/tests/test_rule_write_scope.py index df821bb..419926c 100644 --- a/tests/test_rule_write_scope.py +++ b/tests/test_rule_write_scope.py @@ -1,5 +1,6 @@ from fastapi_common.fastapi_common_web.domain.responses import StatusCodeEnum from fastapi_common.fastapi_common_web.exception.LeauditException import LeauditException +import pytest from fastapi_modules.fastapi_leaudit.services.impl.rbacAdminServiceImpl import RbacAdminServiceImpl from fastapi_modules.fastapi_leaudit.services.impl.rbacServiceImpl import RbacServiceImpl @@ -225,3 +226,25 @@ def test_permission_cache_is_shared_and_can_invalidate_user(): PermissionServiceImpl.InvalidateUser(12345) assert 12345 not in first._permission_cache assert 12345 not in second._permission_cache + + +@pytest.mark.asyncio +async def test_rbac_admin_permission_assertion_uses_permission_service(monkeypatch): + service = RbacAdminServiceImpl() + checked_permissions: list[tuple[int, str]] = [] + + async def fake_context(user_id: int): + return {"can_manage": True, "is_super_admin": False} + + async def fake_check_permission(self, user_id: int, permission_key: str): + checked_permissions.append((user_id, permission_key)) + return permission_key != "rbac:roles:update" + + monkeypatch.setattr(service, "_getCurrentUserContext", fake_context) + monkeypatch.setattr(PermissionServiceImpl, "CheckPermission", fake_check_permission) + + with pytest.raises(LeauditException) as exc_info: + await service._assertPermissions(99, ["rbac:roles:update"]) + + assert exc_info.value.status == StatusCodeEnum.HTTP_403_FORBIDDEN + assert checked_permissions == [(99, "rbac:roles:update")] diff --git a/tests/test_usage_stats_role_permission_scope.py b/tests/test_usage_stats_role_permission_scope.py new file mode 100644 index 0000000..6f0bb79 --- /dev/null +++ b/tests/test_usage_stats_role_permission_scope.py @@ -0,0 +1,17 @@ +"""系统使用统计角色权限语义测试。""" + +from fastapi_modules.fastapi_leaudit.services.impl.usageStatsServiceImpl import UsageStatsServiceImpl + + +def test_usage_stats_service_does_not_require_admin_role_after_controller_permission_check(): + """统计服务不再用管理员角色二次拦截,权限由控制器权限点决定。""" + service = UsageStatsServiceImpl() + + service._assert_stats_access( + { + "is_global": False, + "can_manage": False, + "tenant_scope_value": "梅州", + "tenant_code": "MZ", + } + )