diff --git a/.axhub/make/axhub.config.json b/.axhub/make/axhub.config.json index 9419f2d..69ff55f 100644 --- a/.axhub/make/axhub.config.json +++ b/.axhub/make/axhub.config.json @@ -13,7 +13,8 @@ }, "versionCollaboration": { "remote": { - "url": "https://gitea.lnh2e.com/wangmian/OneOS1.2.git" + "url": "https://gitea.lnh2e.com/wangmian/OneOS1.2.git", + "defaultBranch": "main" } }, "cloudPublishing": { diff --git a/.axhub/make/sidebar-tree.json b/.axhub/make/sidebar-tree.json index d6fc6be..6e99253 100644 --- a/.axhub/make/sidebar-tree.json +++ b/.axhub/make/sidebar-tree.json @@ -1,7 +1,49 @@ { "version": 1, - "updatedAt": "2026-07-21T08:13:15.367Z", + "updatedAt": "2026-07-24T17:38:43.442Z", "prototypes": [ + { + "id": "item-prototypes-autorodo-web", + "kind": "item", + "title": "autorodo web", + "itemKey": "prototypes/autorodo-web" + }, + { + "id": "item-prototypes-oneos-v2-h5-vehicle-assets", + "kind": "item", + "title": "oneos v2 h5 vehicle assets", + "itemKey": "prototypes/oneos-v2-h5-vehicle-assets" + }, + { + "id": "item-prototypes-oneos-v2-form-kit", + "kind": "item", + "title": "oneos v2 form kit", + "itemKey": "prototypes/oneos-v2-form-kit" + }, + { + "id": "item:prototypes:payment-qr-code", + "kind": "item", + "title": "羚牛收款二维码", + "itemKey": "prototypes/payment-qr-code" + }, + { + "id": "item:prototypes:lease-contract-redesign", + "kind": "item", + "title": "lease-contract-redesign", + "itemKey": "prototypes/lease-contract-redesign" + }, + { + "id": "item:prototypes:vehicle-inspection", + "kind": "item", + "title": "验车入库", + "itemKey": "prototypes/vehicle-inspection" + }, + { + "id": "item:prototypes:vehicle-purchase-contract", + "kind": "item", + "title": "车辆采购合同", + "itemKey": "prototypes/vehicle-purchase-contract" + }, { "id": "folder-1782874576229-r8atbu", "kind": "folder", @@ -13,12 +55,24 @@ "title": "原型演示", "itemKey": "prototypes/oneos-prototype-demo" }, + { + "id": "item:prototypes:oneos-v2", + "kind": "item", + "title": "OneOS V2 全局规范示范", + "itemKey": "prototypes/oneos-v2" + }, { "id": "item:prototypes:oneos-web-workbench-new", "kind": "item", "title": "工作台", "itemKey": "prototypes/oneos-web-workbench-new" }, + { + "id": "item:prototypes:message-center", + "kind": "item", + "title": "消息中心", + "itemKey": "prototypes/message-center" + }, { "id": "folder-oneos-approval", "kind": "folder", @@ -63,6 +117,19 @@ } ] }, + { + "id": "folder-oneos-vehicle-ops", + "kind": "folder", + "title": "车辆运维", + "children": [ + { + "id": "item:prototypes:vehicle-fault-handling", + "kind": "item", + "title": "故障处置", + "itemKey": "prototypes/vehicle-fault-handling" + } + ] + }, { "id": "folder-1782874624657-4c8hzv", "kind": "folder", @@ -273,6 +340,289 @@ "kind": "item", "title": "云效生产线手册", "itemKey": "prototypes/yunxiao-pipeline-handbook" + }, + { + "id": "folder-ds-v2-legacy-snapshots", + "kind": "folder", + "title": "设计规范对照·旧版", + "children": [ + { + "id": "item:prototypes:business-dept-ledger-legacy", + "kind": "item", + "title": "【旧版】项目盈亏情况", + "itemKey": "prototypes/business-dept-ledger-legacy" + }, + { + "id": "item:prototypes:contract-template-management-legacy", + "kind": "item", + "title": "【旧版】合同模板管理", + "itemKey": "prototypes/contract-template-management-legacy" + }, + { + "id": "item:prototypes:customer-management-legacy", + "kind": "item", + "title": "【旧版】客户管理", + "itemKey": "prototypes/customer-management-legacy" + }, + { + "id": "item:prototypes:customer-payment-collection-legacy", + "kind": "item", + "title": "【旧版】客户回款情况", + "itemKey": "prototypes/customer-payment-collection-legacy" + }, + { + "id": "item:prototypes:insurance-procurement-legacy", + "kind": "item", + "title": "【旧版】保险采购", + "itemKey": "prototypes/insurance-procurement-legacy" + }, + { + "id": "item:prototypes:lease-business-detail-legacy", + "kind": "item", + "title": "【旧版】租赁业务明细", + "itemKey": "prototypes/lease-business-detail-legacy" + }, + { + "id": "item:prototypes:lease-business-ledger-legacy", + "kind": "item", + "title": "【旧版】租赁业务台账", + "itemKey": "prototypes/lease-business-ledger-legacy" + }, + { + "id": "item:prototypes:lease-business-line-overview-legacy", + "kind": "item", + "title": "【旧版】业务条线说明", + "itemKey": "prototypes/lease-business-line-overview-legacy" + }, + { + "id": "item:prototypes:lease-contract-management-legacy", + "kind": "item", + "title": "【旧版】租赁合同", + "itemKey": "prototypes/lease-contract-management-legacy" + }, + { + "id": "item:prototypes:message-center-legacy", + "kind": "item", + "title": "【旧版】消息中心", + "itemKey": "prototypes/message-center-legacy" + }, + { + "id": "item:prototypes:oneos-h5-h2-order-legacy", + "kind": "item", + "title": "【旧版】加氢记录(H5)", + "itemKey": "prototypes/oneos-h5-h2-order-legacy" + }, + { + "id": "item:prototypes:oneos-h5-vehicle-assets-legacy", + "kind": "item", + "title": "【旧版】车辆资产(H5)", + "itemKey": "prototypes/oneos-h5-vehicle-assets-legacy" + }, + { + "id": "item:prototypes:oneos-prototype-demo-legacy", + "kind": "item", + "title": "【旧版】原型演示", + "itemKey": "prototypes/oneos-prototype-demo-legacy" + }, + { + "id": "item:prototypes:oneos-web-approval-cc-legacy", + "kind": "item", + "title": "【旧版】我的抄送", + "itemKey": "prototypes/oneos-web-approval-cc-legacy" + }, + { + "id": "item:prototypes:oneos-web-approval-done-legacy", + "kind": "item", + "title": "【旧版】我的已办", + "itemKey": "prototypes/oneos-web-approval-done-legacy" + }, + { + "id": "item:prototypes:oneos-web-approval-initiated-legacy", + "kind": "item", + "title": "【旧版】我发起的", + "itemKey": "prototypes/oneos-web-approval-initiated-legacy" + }, + { + "id": "item:prototypes:oneos-web-approval-todo-legacy", + "kind": "item", + "title": "【旧版】我的待办", + "itemKey": "prototypes/oneos-web-approval-todo-legacy" + }, + { + "id": "item:prototypes:oneos-web-business-legacy", + "kind": "item", + "title": "【旧版】业务管理", + "itemKey": "prototypes/oneos-web-business-legacy" + }, + { + "id": "item:prototypes:oneos-web-data-analysis-legacy", + "kind": "item", + "title": "【旧版】数据分析", + "itemKey": "prototypes/oneos-web-data-analysis-legacy" + }, + { + "id": "item:prototypes:oneos-web-finance-legacy", + "kind": "item", + "title": "【旧版】财务管理(合包)", + "itemKey": "prototypes/oneos-web-finance-legacy" + }, + { + "id": "item:prototypes:oneos-web-h2-station-legacy", + "kind": "item", + "title": "【旧版】加氢记录", + "itemKey": "prototypes/oneos-web-h2-station-legacy" + }, + { + "id": "item:prototypes:oneos-web-h2-station-analysis-legacy", + "kind": "item", + "title": "【旧版】加氢站分析", + "itemKey": "prototypes/oneos-web-h2-station-analysis-legacy" + }, + { + "id": "item:prototypes:oneos-web-h2-station-site-legacy", + "kind": "item", + "title": "【旧版】站点信息", + "itemKey": "prototypes/oneos-web-h2-station-site-legacy" + }, + { + "id": "item:prototypes:oneos-web-h2-station-stats-legacy", + "kind": "item", + "title": "【旧版】加氢站数量统计", + "itemKey": "prototypes/oneos-web-h2-station-stats-legacy" + }, + { + "id": "item:prototypes:oneos-web-h2-station-weekly-legacy", + "kind": "item", + "title": "【旧版】站点周报统计", + "itemKey": "prototypes/oneos-web-h2-station-weekly-legacy" + }, + { + "id": "item:prototypes:oneos-web-help-center-legacy", + "kind": "item", + "title": "【旧版】帮助中心", + "itemKey": "prototypes/oneos-web-help-center-legacy" + }, + { + "id": "item:prototypes:oneos-web-lease-contract-legacy", + "kind": "item", + "title": "【旧版】车辆租赁合同", + "itemKey": "prototypes/oneos-web-lease-contract-legacy" + }, + { + "id": "item:prototypes:oneos-web-ledger-data-legacy", + "kind": "item", + "title": "【旧版】台账数据", + "itemKey": "prototypes/oneos-web-ledger-data-legacy" + }, + { + "id": "item:prototypes:oneos-web-ops-legacy", + "kind": "item", + "title": "【旧版】运维管理", + "itemKey": "prototypes/oneos-web-ops-legacy" + }, + { + "id": "item:prototypes:oneos-web-procurement-legacy", + "kind": "item", + "title": "【旧版】采购管理", + "itemKey": "prototypes/oneos-web-procurement-legacy" + }, + { + "id": "item:prototypes:oneos-web-workbench-new-legacy", + "kind": "item", + "title": "【旧版】工作台", + "itemKey": "prototypes/oneos-web-workbench-new-legacy" + }, + { + "id": "item:prototypes:payment-records-legacy", + "kind": "item", + "title": "【旧版】收款记录", + "itemKey": "prototypes/payment-records-legacy" + }, + { + "id": "item:prototypes:self-operated-business-ledger-legacy", + "kind": "item", + "title": "【旧版】物流业务明细", + "itemKey": "prototypes/self-operated-business-ledger-legacy" + }, + { + "id": "item:prototypes:self-operated-contract-legacy", + "kind": "item", + "title": "【旧版】自营合同", + "itemKey": "prototypes/self-operated-contract-legacy" + }, + { + "id": "item:prototypes:self-operated-dispatch-task-legacy", + "kind": "item", + "title": "【旧版】调度任务", + "itemKey": "prototypes/self-operated-dispatch-task-legacy" + }, + { + "id": "item:prototypes:supplier-management-legacy", + "kind": "item", + "title": "【旧版】供应商管理", + "itemKey": "prototypes/supplier-management-legacy" + }, + { + "id": "item:prototypes:task-work-order-legacy", + "kind": "item", + "title": "【旧版】任务工单", + "itemKey": "prototypes/task-work-order-legacy" + }, + { + "id": "item:prototypes:vehicle-fault-handling-legacy", + "kind": "item", + "title": "【旧版】故障处置", + "itemKey": "prototypes/vehicle-fault-handling-legacy" + }, + { + "id": "item:prototypes:vehicle-h2-fee-ledger-legacy", + "kind": "item", + "title": "【旧版】车辆氢费明细", + "itemKey": "prototypes/vehicle-h2-fee-ledger-legacy" + }, + { + "id": "item:prototypes:vehicle-inspection-legacy", + "kind": "item", + "title": "【旧版】vehicle inspection", + "itemKey": "prototypes/vehicle-inspection-legacy" + }, + { + "id": "item:prototypes:vehicle-maintenance-ledger-legacy", + "kind": "item", + "title": "【旧版】维修保养台账", + "itemKey": "prototypes/vehicle-maintenance-ledger-legacy" + }, + { + "id": "item:prototypes:vehicle-management-legacy", + "kind": "item", + "title": "【旧版】车辆资产", + "itemKey": "prototypes/vehicle-management-legacy" + }, + { + "id": "item:prototypes:vehicle-pickup-receivable-legacy", + "kind": "item", + "title": "【旧版】提车应收款", + "itemKey": "prototypes/vehicle-pickup-receivable-legacy" + }, + { + "id": "item:prototypes:vehicle-purchase-contract-legacy", + "kind": "item", + "title": "【旧版】vehicle purchase contract", + "itemKey": "prototypes/vehicle-purchase-contract-legacy" + }, + { + "id": "item:prototypes:vehicle-return-settlement-legacy", + "kind": "item", + "title": "【旧版】还车应结款", + "itemKey": "prototypes/vehicle-return-settlement-legacy" + }, + { + "id": "item:prototypes:yunxiao-pipeline-handbook-legacy", + "kind": "item", + "title": "【旧版】云效生产线手册", + "itemKey": "prototypes/yunxiao-pipeline-handbook-legacy" + } + ] } ] }, diff --git a/.claude/skills/AutoRDO/SKILL.md b/.claude/skills/AutoRDO/SKILL.md new file mode 100644 index 0000000..aa2dae4 --- /dev/null +++ b/.claude/skills/AutoRDO/SKILL.md @@ -0,0 +1,83 @@ +--- +name: AutoRDO +description: >- + AutoRDO (Requirement Description Optimization): cleans fragmented chat logs, + voice transcripts, oral notes, and feedback ledgers into written title and description; + auto-detects 类型/优先级/标签/提交部门/提交人 from content; maps OneOS modules via oneos-domain.md; + splits multi-line into multiple results; if any 待确认 exists, MUST switch to Plan mode + for choice-based confirmation. Does not change Yunxiao status or create tasks. + Pair with YunxiaoPMapp for cloud write; never load yunxiao-requirement-lifecycle. +--- + +# AutoRDO + +**Requirement Description Optimization**(需求描述优化)。 +将碎片化、通俗化文字(聊天/录音/反馈台账)在**保留原意**前提下拆解为**标题、描述**,并**自动识别**类型、优先级、标签、提交部门、提交人,供入库写入云效需求。 +多行/多条独立诉求 → **自动拆成多份**转译结果。 + +**强制**:清洗结果中**只要存在任意「待确认」**,Agent **必须立刻** `SwitchMode` → **plan**,逐条用选择题消解;**无需**用户再说「确认待确认」。无待确认则可直接交定稿。 + +## 边界(强制) + +| 做 | 不做 | +|---|---| +| 提炼标题与书面描述;自动识别类型/优先级/标签/提交部门/提交人 | 改云效状态 / 建任务 / **直接**打云效标签 | +| 多行/多条 → 多份转译结果 | 写成完整 AutoPRD / 臆造业务结论 | +| 有待确认 → 强制 Plan 逐条确认 | 停在初稿等口令;把猜测写入定稿 | +| 去除描述**结尾句号** | 加载 `yunxiao-requirement-lifecycle` | + +云效建单与打标由 **`$YunxiaoPMapp`** 负责;本 Skill 只出清洗稿与**推荐元数据**。 + +## 何时使用 + +- 口令含 `AutoRDO` / `清洗聊天` / `录音整理` / `原始诉求` +- 粘贴反馈台账(含部门/优先级/模块列)或口述碎片 +- YunxiaoPMapp「记录需求」前必须先跑本 Skill(材料为碎片/台账时) + +## 输入 + +- 聊天、会议速记、口述、录音转写 +- **多行/台账**:含反馈人、所属部门、业务模块、优先级、PC或移动端等列时优先采信列值 +- 确认阶段补充文字 + +## 处理规则 + +**强制前置**:ONE-OS 材料先 Read [references/oneos-domain.md](references/oneos-domain.md),元数据规则见 [references/meta-fields.md](references/meta-fields.md),再按 [references/rules.md](references/rules.md) 执行。 + +1. **多条拆解** → 每条独立成稿 +2. **OneOS 对齐** → 模块/部门标准名 +3. **元数据识别** → 类型、优先级、标签、提交部门、提交人(显式列 > 正文标记 > 语义推断;不确定 → 待确认) +4. **标题 + 描述转译** → 去口语、去结尾句号 +5. **有待确认 → 强制 Plan** → 见 [references/confirm-pending.md](references/confirm-pending.md) + +## 输出 + +```markdown +## 原始诉求(AutoRDO) + +**标题**:<清晰标题> +**类型**:【新增】|【优化】 +**优先级**:P1-高|P2-中|P3-低 +**标签**:<标准模块>[, PC端|小程序] +**提交部门**:<标准部门名或空> +**提交人**:<姓名或空> + +**描述**: +<转译正文;无结尾句号> +反馈:<有则附日期/禅道/状态/端> + +待确认: +- … +``` + +多条时先写「共 N 条」,再 `### 1` … `### N`。 +交 YunxiaoPMapp 示例:`记录需求:标题=…;类型=…;优先级=…;标签=…;提交部门=…;提交人=…;描述=…` + +## 口令 + +```text +AutoRDO:<粘贴聊天或台账行> +AutoRDO:录音转写如下 … +按下列补充消解待确认: +<补充说明> +``` diff --git a/.claude/skills/AutoRDO/agents/openai.yaml b/.claude/skills/AutoRDO/agents/openai.yaml new file mode 100644 index 0000000..a3ccd63 --- /dev/null +++ b/.claude/skills/AutoRDO/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "AutoRDO" + short_description: "清洗诉求并识别类型优先级标签部门提交人" + default_prompt: "按 AutoRDO 清洗:先读 oneos-domain.md 与 meta-fields.md;多行拆多条;出标题+描述+类型/优先级/标签/提交部门/提交人。有待确认则同轮强制切 Plan 用选择题消解;不改云效、不直接打标、不贴词典全文。" diff --git a/.claude/skills/AutoRDO/references/confirm-pending.md b/.claude/skills/AutoRDO/references/confirm-pending.md new file mode 100644 index 0000000..24dec13 --- /dev/null +++ b/.claude/skills/AutoRDO/references/confirm-pending.md @@ -0,0 +1,126 @@ +# AutoRDO · 待确认逐条 Plan 确认 + +## 目标 + +清洗稿中的「待确认」经产品经理**逐条人工确认**后,回填进标题/描述,得到可交 YunxiaoPMapp 的定稿。 +确认过程**优先选择题**;选项用 [oneos-domain.md](oneos-domain.md) 辅助生成;也支持用补充说明/需求描述文字直接消解待确认点。 + +## 何时进入(强制) + +```text +清洗初稿产出后: + 若任一需求含「待确认」→ 同轮必须 SwitchMode → plan + 若全部无待确认 → 不必进 Plan,初稿即定稿 +``` + +- **触发条件只有一条**:存在待确认(单条或多条中任意一点) +- **禁止**要求用户再说「确认待确认」「开始确认」才进 Plan +- 用户在 Plan 中可用选择题作答,也可粘贴补充说明消解 + +**不进 Plan 的唯一情况**:清洗结果中没有任何「待确认」条目。 + +## Plan 门禁(强制) + +1. 清洗初稿写出后(可先展示初稿摘要),**立刻** `SwitchMode` → **plan**,说明:存在待确认,须逐条确认后再回填定稿;本阶段仍**不写云效** +2. Plan 列出:待确认的**需求条号**、每条下的待确认点清单、推荐确认方式(选择题 / 文字补充) +3. **用户确认 / 批准 Plan / 「执行」之前**:禁止把未确认内容写进定稿描述 +4. 批准后:按条推进确认 → 回填 → 输出定稿 + +## 逐条确认顺序 + +```text +需求 1 的待确认点 1 → 点 2 → … → 需求 1 定稿片段 +需求 2 的待确认点 1 → … +… +全部完成后输出「已确认定稿」全集 +``` + +- **一次只推进一个待确认点**(或同一点下的一组互斥选项),避免一屏堆十几个问题 +- 多条需求时,Plan 清单可注明建议从第 1 条起;用户可指定跳过或改序 +- 某条无「待确认」→ 跳过,保留初稿即可 + +## 确认方式(优先选择) + +每个待确认点按下列优先级出题: + +| 优先级 | 方式 | 适用 | +|---|---|---| +| 1 | **选择题**(2–5 个互斥选项 + 可选「其他/我补充」) | 模块归属、部门名称、类型/优先级、是否/范围、枚举字段、拆条边界等 | +| 2 | **多选** | 明确可多选的范围(如适用端:PC+H5) | +| 3 | **文字补充** | 无法枚举、需自由描述字段清单/规则细节时 | + +### 选项如何用 OneOS 词典生成 + +先 Read `oneos-domain.md`,再出选项: + +- **模块名不清**:候选 = 词典中相关条线的标准模块名(如还车应结款 / 违章管理 / 还车管理),勿自造模块 +- **部门/角色口语**:候选 = 第 1 节标准名(业管→业务管理组,安全小组→安全部 等)+「保持单据原文分区名」 +- **跨条线联动**:选项写清主责模块 vs 联动对象;标题只挂主责,联动写描述 +- **故事点细节**:仅当材料或用户补充已暗示时,才把词典闭环中的合理分支列为选项;**禁止**把未提及的故事点细节默认选中 + +每题结构建议: + +```text +【需求 n · 待确认 k】<待确认原文> +依据词典,请选择: +A. … +B. … +C. … +D. 其他(请补充一句) +也可用一段需求描述直接回答本点 +``` + +## 用需求描述补充 + +用户可用以下任一方式消解待确认,**不必**每题都点选: + +- 粘贴一段补充说明 / 功能规则 / 字段清单 +- 回复「按下列描述确认:…」 +- 对某一待确认点直接写答案句 + +Agent 须: + +1. 将补充文字**映射**到对应待确认点(可一次消解多点) +2. 写入该条**描述**(书面化、去结尾句号),从「待确认」列表**移除已消解项** +3. 补充仍含糊的部分 → 保留或新开「待确认」,继续选择题 +4. **不**把补充里未说的内容脑补进去 + +## 回填定稿规则 + +确认完成后,对该条输出「已确认」版本: + +```markdown +### n(已确认) + +## 原始诉求(AutoRDO) + +**标题**:… + +**描述**: +<初稿 + 已确认信息合并;无结尾句号> + +待确认: +- (无则写「无」或省略本段) +``` + +- 标题若因模块/动作确认而需微调,可改,并在确认回合说明改了什么 +- 未确认点**不得**假装已确认;可标「本条暂缓,待确认仍保留」 +- 全部条完成后,给总览:已确认条数 / 仍含待确认条数 + +## 口令 + +```text +AutoRDO:<材料> → 有待确认则自动进 Plan +按下列补充消解待确认: +<粘贴补充说明> +第 6 条待确认选 A;第 7 条补充:字段非必填,计入应补汇总 +``` + +(「确认待确认」等口令**不再作为**进 Plan 的前置条件;有待确认即强制进。) + +## 不做 + +- 确认阶段仍不改云效、不建任务 +- 不把 `oneos-domain.md` 全文贴进定稿 +- 不跳过 Plan 直接把「猜的答案」写进描述 +- 不等用户额外口令才进 Plan(有待确认必须进) diff --git a/.claude/skills/AutoRDO/references/meta-fields.md b/.claude/skills/AutoRDO/references/meta-fields.md new file mode 100644 index 0000000..e0b8a3d --- /dev/null +++ b/.claude/skills/AutoRDO/references/meta-fields.md @@ -0,0 +1,96 @@ +# AutoRDO · 元数据自动识别 + +从粘贴内容(台账表格行、聊天、口述)自动识别下列字段,写入每条原始诉求输出;**不**直接改云效打标(打标由 YunxiaoPMapp 执行)。 + +## 输出字段 + +| 字段 | 说明 | 无依据时 | +|---|---|---| +| **类型** | `【新增】` / `【优化】`(与云效标题前缀一致) | 默认 `【优化】`,并列入待确认 | +| **优先级** | `P1-高` / `P2-中` / `P3-低` | 默认 `P2-中`,高不确定则待确认 | +| **标签** | 主模块标签(对齐 `oneos-domain.md` 标准模块名);可附端标签 `PC端` / `小程序` | 仅模块不确定则待确认 | +| **提交部门** | 映射到词典标准部门名 | 材料无部门且无法推断 → 待确认或空 | +| **提交人** | 反馈人/用户姓名 | 材料无人名 → 空(不臆造) | + +## 识别优先级 + +```text +1. 显式列/字段(反馈台账:优先级、所属部门、反馈方、业务模块、PC或移动端…) +2. 正文中的明确标记(P1、【优化】、@某人、部门署名) +3. 语义推断(用词 + oneos-domain 模块/部门) +4. 仍不确定 → 待确认(选择题),禁止假装已确认 +``` + +## 类型(【新增】/【优化】) + +| 信号 | 类型 | +|---|---| +| 新增、增加、支持、允许、增加列/字段/入口、批量导入(首次能力) | 【新增】 | +| 优化、调整、修改、放开、更清晰、重构、移动至、改由、精确到、兼容 | 【优化】 | +| 台账/口令已写 `【新增】`/`【优化】` | 以显式为准 | +| 同时含新增与优化语义 | 以**主诉求**定类型;次要点写在描述 | + +标题可带类型前缀供入库:`【优化】交车管理:…`;若用户只要裸标题,类型仍单独输出字段。 + +## 优先级 + +| 信号 | 优先级 | +|---|---| +| 列值 `P1-高` / `P1` / 紧急、阻塞、无法办理、P1 | `P1-高` | +| 列值 `P2-中` / `P2` | `P2-中` | +| 列值 `P3-低` / `P3` / 文案、置灰、体验微调 | `P3-低` | +| 无信号 | 默认 `P2-中` | + +## 标签 + +1. **主标签** = 归属标准模块名(`oneos-domain.md`),如 `还车应结款`、`违章管理`、`交车管理` +2. 台账「业务模块」列优先;口语映射到标准名(验车管理→验车入库,调拨管理→车辆调拨) +3. **端标签**(可选,可多选):材料含 `PC端`/`小程序`/`PC及小程序` 时写入 +4. 「其他」「全局优化」「小程序审批中心」等非模块名:主标签用最贴近模块或 `系统全局`;勿发明云效不存在的随意标签名 +5. 输出格式:逗号分隔,如 `还车应结款, PC端` + +## 提交部门 + +| 材料常见写法 | 标准输出 | +|---|---| +| 业务管理部、业管、业务管理 | 业务管理组 | +| 运维部、运维 | 运维部 | +| 数智中心、数智、信息化 | 数智部 | +| 安全部、安全 | 安全部 | +| 财务部、财务 | 财务部 | +| 法务部、法务 | 法务部 | +| 采购部/采购组 | 按词典区分输出 | + +台账「所属部门」列优先;聊天中「我们运维…」可推断,低置信度则待确认。 + +## 提交人 + +- 台账「反馈方/用户」「反馈人」列 → 原样作为提交人 +- 聊天 @姓名、署名「张三:」→ 取该名 +- 多人联合反馈 → 取主反馈人(首列/首个署名);其余可写描述附注 +- **禁止**用当前对话用户名冒充提交人(除非材料写明) + +## 与描述附注的关系 + +- **结构化字段**(类型/优先级/标签/提交部门/提交人)单独成行,供 YunxiaoPMapp 建单口令复用 +- 禅道编号、处理状态、反馈日期等 → 可放描述末行「反馈:…」附注,不升格为强制字段 + +## 输出模板(每条) + +```markdown +## 原始诉求(AutoRDO) + +**标题**:<清晰标题;可选带【新增】/【优化】前缀> +**类型**:【新增】|【优化】 +**优先级**:P1-高|P2-中|P3-低 +**标签**:<模块>[, PC端|小程序] +**提交部门**:<标准部门名或空> +**提交人**:<姓名或空> + +**描述**: +<转译正文;无结尾句号> +反馈:<日期 · 原文部门 · 状态 · 禅道号 · 端 …>(有则写) + +待确认: +- … +``` diff --git a/.claude/skills/AutoRDO/references/oneos-domain.md b/.claude/skills/AutoRDO/references/oneos-domain.md new file mode 100644 index 0000000..5a47ee4 --- /dev/null +++ b/.claude/skills/AutoRDO/references/oneos-domain.md @@ -0,0 +1,399 @@ +# ONE-OS 业务领域词典(供 AutoRDO 对齐) + +> 来源:业务条线说明原型 `lease-business-line-overview` +> 用途:清洗标题/描述时,优先用下列**标准模块名、部门名、闭环表述**对齐,避免口语臆造模块或错挂条线。 +> 系统定位:1 套系统 · 5 大业务条线闭环 · 3 大数据底座 · 演进方向为业财一体 +> **强制**:本文件只用于理解与用词对齐;**禁止**把本词典全文贴进云效描述或 AutoRDO 输出稿。 + +--- + +## 0. 系统总览 + +| 维度 | 口径 | +|---|---| +| 平台名 | ONE-OS(亦写作 OneOS) | +| 五大业务条线 | 租赁 · 能源 · 运维 · 安全 · 物流 | +| 三大数据底座 | 车辆资产运营情况 · 各业务条线盈亏情况 · 里程调度情况 | +| 现阶段 | 业务驱动 · 财务手工维护(收款/付款/核销多人工) | +| 演进方向 | 业财闭环一体化(打通财务 YS,「收款记录」「付款记录」与业务台账双向回写) | + +**标题提炼提示**:口述涉及合同/台账/交还车/氢费/证照/调度时,先归入对应条线再落模块名;跨条线联动(如还车应结款联动违章事故)在描述中写明联动对象,标题以主责模块为准。 + +--- + +## 1. 业务部门 / 角色词典(标准用词) + +清洗时统一下列名称;同义口语请映射到标准名。 + +| 标准名称 | 常见口语/别称 | 主要出现条线 | +|---|---|---| +| 业务管理组 | 业管、业务管理 | 租赁、运维、安全、物流 | +| 业务管理组-能源部 | 能源组、业管能源、业务管理部-能源组 | 能源、租赁还车 | +| 业务服务组 | 业务服务 | 物流 | +| 业务员 | 业务 | 租赁 | +| 调度岗 | 调度 | 物流 | +| 法务部 | 法务 | 租赁、运维采购合同 | +| 财务部 | 财务 | 租赁评级、能源账户、运维付款/维保、安全违章事故、物流台账 | +| 安全部 | 安全 | 租赁评级、证照、安全条线 | +| 运维部 | 运维、区域操作员 | 运维全链路、交还车、租赁提车后交车 | +| 采购部 | 采购 | 能源加氢站、运维采购合同/验车 | +| 采购组 | 采购组 | 供应商管理(能源/运维) | +| 数智部 | 数智、信息化 | 合同模板电子化、能源扩展 | +| 董事长 | 老板、总 | 非标合同、资产出售过户报废确认 | +| 司机 | 提车司机、内部司机 | 交车培训、故障、安全培训 | +| 加氢站 / 签约加氢站 | 站点、站方 | 能源 | + +**组织边界提示** + +- 「采购组」侧重供应商主数据;「采购部」侧重站点管理与采购/三方租赁合同。 +- 「业务管理组」与「业务管理组-能源部」勿混用;氢费、加氢对账默认能源部。 +- 物流调度明确:**非 TMS**,不做配载与承运商调度。 + +--- + +## 2. 功能模块总表(按条线) + +### 2.1 租赁业务条线(6 模块 · 全链路闭环) + +**条线闭环**:客户评级决定是否可签 → 标准模板支撑签约 → 租赁合同进入履约 → 提车应收对齐后交车 → 台账承接租期账单 → 还车应结款会签归档。 + +| 步 | 模块名 | 一句话定位 | 责任部门 | +|---|---|---|---| +| 1 | 客户管理 | 准入评级 · 决定能否签约、走哪条流程 | 业务管理组、法务部、财务部、安全部 | +| 2 | 标准合同管理 | 模板配置 · 支撑电子合同一键发起 | 法务部、数智部 | +| 3 | 租赁合同 | 签约发起 · 标准 / 非标 / 禁止三条路径 | 业务员、法务部 | +| 4 | 提车应收款 | 应收生成 · 实收对齐 · 触发交车 | 业务员、法务部、业务管理组、运维部 | +| 5 | 租赁业务台账 | 交车后账单 · 周期收费与实收延续 | 运维部、业务员、业务管理组 | +| 6 | 还车应结款 | 还车结算 · 多部门会签 · 归档闭环 | 业务管理组、安全部、运维部、业务管理组-能源部 | + +**关键结果标签(客户管理)**:可标准签约 · 可签·走非标 · 禁止签约·高风险 + +--- + +### 2.2 能源业务条线(6 模块 · 业财闭环) + +**条线闭环**:供应商主数据支撑站点绑定 → 签约站账号与订单主动上报 → 非签约站氢费补录对账 → 客户对账单关联收款 → 加氢记录标记已付款 → 成熟后向采购端与司机端延伸。 + +| 步 | 模块名 | 一句话定位 | 责任部门 | +|---|---|---|---| +| 1 | 供应商管理 | 加氢/充电站供应商 · 站点绑定与业财直连 | 采购组、财务部 | +| 2 | 加氢站管理 | 签约站 / 非签约站 · 账号、余额与价格 | 采购部、加氢站 | +| 3 | 加氢订单 | 主动上报 · PLC 自动采集 · 预约接单 | 签约加氢站、业务管理组-能源部 | +| 4 | 车辆氢费明细 | 非签约站补录 · 对账归档 · 余额关联 | 业务管理组-能源部 | +| 5 | 氢费账户 | 客户预付款 · 对账单 · 业财收款闭环 | 业务管理组-能源部、财务部 | +| 6 | 扩展规划 | 上游采购 · 下游司机服务 | 采购部、业务管理组-能源部、数智部 | + +--- + +### 2.3 运维管理条线(15 模块) + +**主线(main)**:验车入库 → 上牌 → 证照 → 备车 → 交车 → 还车 → 出售/过户/报废出库 +**并行能力(support)**:供应商、采购合同、替换车、故障、年审、维保、异动、调拨(无严格先后) + +**条线闭环**:供应商与采购合同入库 → 上牌证照与备车整备 → 交车/替换/还车闭环 → 故障与年审维保 → 异动调拨 → 出售/过户/报废审批出库。 + +| 步 | 模块名 | 轨道 | 一句话定位 | 责任部门 | +|---|---|---|---|---| +| 1 | 供应商管理 | support | 整车厂 / 三方租赁 · 付款底座 | 采购组、财务部 | +| 2 | 车辆采购 / 三方租赁合同 | support | 车型数量 · 付款关联 · 验车任务 | 采购部、法务部、财务部 | +| 3 | 验车入库 | main | 采购 / 三方来源 · 验完自动入库 | 运维部、采购部 | +| 4 | 车辆上牌 | main | 车架号入库 · 完成牌证登记 | 运维部 | +| 5 | 证照管理 | main | 一车一档 · 到期待办推送 | 运维部、安全部 | +| 6 | 备车管理 | main | 交车前整备 · 随时可交付 | 运维部 | +| 7 | 交车管理 | main | 司机培训留痕 · 区域派单 · OCR 比对 · 电子签 | 运维部、业务管理组、司机 | +| 8 | 替换车管理 | support | 临时 / 永久替换 · 费用自动核算 | 运维部、业务管理组 | +| 9 | 还车管理 | main | 区域派单 · 费用自动计算 · 电子签 | 运维部、业务管理组 | +| 10 | 故障管理 | support | AI 助手 + 人工 · 证据链归档 | 运维部、司机 | +| 11 | 年审记录 | support | 提前 3 个月 · OCR 刷新证照 | 运维部 | +| 12 | 维修与保养记录 | support | 费用汇总 · 业财一体 · 盈亏核算 | 运维部、财务部 | +| 13 | 车辆异动 | support | 出库维修 / 年审 / 保养 · 成本归集 | 运维部 | +| 14 | 车辆调拨 | support | 跨区调拨 · 运维权转移 | 运维部、业务管理组 | +| 15 | 车辆出售、过户、报废 | main | 资产变更审批 · 过程留痕 · 自动出库 | 运维部、董事长、财务部 | + +--- + +### 2.4 安全管理条线(6 模块 · 安全闭环) + +**条线闭环**:司机信息与证照 → 定期培训与资料库学习 → 违规记录关联评级 → 违章/事故登记 → 联动还车应结款与代处理费用。 + +| 步 | 模块名 | 一句话定位 | 责任部门 | +|---|---|---|---| +| 1 | 司机管理 | 物流内部司机 · 信息证照一体 | 安全部、业务管理组 | +| 2 | 内部司机培训 | 定期安全培训 · 证据链留痕 | 安全部、司机 | +| 3 | 安全培训资料库 | 资料上传 · APP / 小程序学习 | 安全部、司机 | +| 4 | 违规记录 | 司机违规 · 关联评级 | 安全部、业务管理组 | +| 5 | 违章管理 | 分值罚款 · 还车联动 · 代处理 | 安全部、财务部、业务管理组 | +| 6 | 事故管理 | 权责划分 · 保险赔付 · 还车联动 | 安全部、财务部、业务管理组 | + +--- + +### 2.5 物流业务条线(3 模块 · 经营闭环) + +**条线闭环**:物流合同生效 → 轻量调度派车(交车后可办结)→ 办结自动生成物流业务明细 → 汇总盈亏 → 汇集项目盈亏表 → 演进关联收款记录完成业财闭环。 + +| 步 | 模块名 | 一句话定位 | 责任部门 | +|---|---|---|---| +| 1 | 物流合同 | 关键信息录入 · 业务确认生效 · 可派车 | 业务管理组、业务服务组 | +| 2 | 调度任务 | 轻量派车 · 交还车校验 · 办结出车 | 业务服务组、调度岗、运维部 | +| 3 | 物流台账 | 任务自动生成 · 补录导入 · 盈亏核算 | 业务服务组、财务部 | + +--- + +### 2.6 三大数据底座(管理决策层,非业务条线) + +| 底座 | 看什么 | 主要数据来源条线 | +|---|---|---| +| 车辆资产运营情况 | 规模 · 结构 · 健康度 · 品牌型号出勤率下钻省市 | 运维、车辆管理、证照/保险、交车/备车 | +| 各业务条线盈亏情况 | 收入 · 成本 · 利润(条线/项目/单车) | 租赁台账、物流台账、氢费账户、收款、维保/事故/违章 | +| 里程调度情况 | 里程补贴 · 客户履约 · 智能替换与运维干预 | 车机/GPS/人工里程、客户里程约定、替换车、运维干预 | + +--- + +## 3. 流程闭环速查(端到端一句话) + +| 条线 | 闭环一句话 | +|---|---| +| 租赁 | 客户 → 模板 → 签约 → 提车应收 → 交车 → 台账账单 → 还车应结款归档 | +| 能源 | 供应商 → 加氢站 → 订单采集 → 氢费明细 → 客户账户收款 → 记录已付款 | +| 运维 | 采购/验车入库 → 牌证备车 → 交还车 → 维保故障 → 异动调拨 → 处置出库 | +| 安全 | 司机档案 → 培训资料 → 违规评级 → 违章事故 → 还车应结款费用 | +| 物流 | 合同生效 → 调度派车办结 → 台账明细/盈亏 →(演进)收款业财闭环 | +| 系统级 | 五条线业务闭环 → 三大数据底座决策 → 打通财务 YS 业财一体 | + +**高频跨模块联动(写描述时勿漏)** + +- 租赁签约路径依赖**客户管理**评级(标准 / 非标 / 禁止) +- **提车应收**对齐后触发运维**交车**;交车成功生成**租赁业务台账** +- **还车管理**完成后生成**还车应结款**;安全**违章/事故**费用写入应结项 +- 运维**采购/三方租赁合同**自动生成**验车入库**任务 +- 物流**调度办结**须车辆已**交车**;办结自动写**物流台账** +- 能源签约站**加氢订单**与非签约站**氢费明细**统一进入**氢费账户**对账收款 + +--- + +## 4. 故事点(按模块:起点 → 怎么运作 → 闭环) + +> 结构对齐页面「起点 / 怎么运作 / 闭环」。标题用「模块名 + 核心动作」;描述保留谁发起、关键规则、闭环结果,不升格成完整 PRD。 +> 下列为各模块业务事实摘要,用于**识别归属与用词**;材料未提及的细节**不得**脑补进描述。 + +### 4.1 租赁 + +#### 客户管理 +- **起点**:业务管理组维护客户基础信息,作为后续合同与账单的统一客户主数据 +- **怎么运作**:法务每 3 个月评估法律风险;财务实时维护财务风险等级;安全实时维护安全风险等级;系统综合形成签约策略 +- **闭环**:未达禁止标准可签;偏离常规范式走「非标准流程」;达禁止标准禁止新签,现有合同标「高风险」并通知业务跟进或终止 + +#### 标准合同管理 +- **起点**:法务部维护各车型试用/正式等标准模板,明确条款边界与风控要求 +- **怎么运作**:数智部按模板配置电子合同;条款可设「触发车型」;「风控红线」被改进非标审批;「锁定区域」不可随意改 +- **闭环**:业务员直选模板走标准流程;触碰红线或锁定区外修改转法务非标审批 + +#### 租赁合同 +- **起点**:业务员选羚牛签约主体与乙方客户;系统按评级判标准/非标/禁止 +- **怎么运作**:合同要素实时反写电子合同预览;生成电子合同/条款附件/授权委托书;越界转非标;支持续签、转正式、转三方、增值服务、授权委托书、主动终止等 +- **闭环**:审批通过进入履约并串联提车应收、交车、台账;非标须法务与董事长确认后放行 + +#### 提车应收款 +- **起点**:提交租赁合同审批时,按合同条款自动生成提车应收金额 +- **怎么运作**:法务在「收款记录」导入到账(后期对接 YS/银企直联);业务/业管关联账单并分配金额;实收对齐后标记完成 +- **闭环**:应收完成后按约定生成交车任务,属地运维执行交车 + +#### 租赁业务台账 +- **起点**:运维交车成功后自动生成该车租赁账单,应付款日初始「待设置」 +- **怎么运作**:设置应付款日后生成首期应收;提车实收写入首期;溢出并入第二期减免 +- **闭环**:持续记录每期应收/实收/状态,支撑租期运营与还车结算 + +#### 还车应结款 +- **起点**:运维完成还车后自动生成还车应结款任务 +- **怎么运作**:业管、安全、运维、能源各自完成部门审批;汇总应退/应补;应补关联收款记录、应退关联付款记录 +- **闭环**:业管发起总审批;完成后自动归档,租赁单条业务全链路闭环 + +--- + +### 4.2 能源 + +#### 供应商管理 +- **起点**:加氢站/充电站作为供应商纳入统一体系,采购组维护基础信息与结算要素 +- **怎么运作**:创建站点时绑定供应商;氢费/电费对账支持直连付款;收付款明细关联财务记录;支撑付款/收款申请审批 +- **闭环**:供应商主数据与站点、对账、财务贯通,打好能源业财基础 + +#### 加氢站管理 +- **起点**:采购部统一管理签约站/非签约站;签约站须关联供应商与系统账号 +- **怎么运作**:签约站账号登录加氢订单(PC/移动/H5);预付款余额;营业状态;成本价关联氢费记录;能源部形成对账单并发起付款(后期对接 YS) +- **闭环**:站点/账号/余额/价格/营业状态集中管理,签约站可自助上报 + +#### 加氢订单 +- **起点**:签约站登录后多渠道上报订单;PLC 站点可自动获取 +- **怎么运作**:多端快速识别记录;PLC 自动获取;联动「小羚羚」预约接单,司机确认;进入加氢记录供对账 +- **闭环**:主动上报或自动采集,预约在线闭环,减少手工补录 + +#### 车辆氢费明细 +- **起点**:非签约站氢费由业务管理组-能源部手动维护 +- **怎么运作**:录入并关联站点/车辆/客户;对账后标「已对账」归档;联动预付款余额与客户能源账户;OCR 与电子围栏辅助核对 +- **闭环**:签约/非签约氢费统一归集,余额变动可追溯 + +#### 氢费账户 +- **起点**:管理客户氢费预付款余额与客户对账单 +- **怎么运作**:维护余额变动;生成对账单;关联「收款记录」;收款后加氢记录标「已付款」;支持充值/开票直达财务(替代钉钉,对接 YS) +- **闭环**:明细→对账→收款全链路可核对,付款状态与财务实收一致 + +#### 扩展规划 +- **起点**:条线成熟后向上游采购与下游司机服务延伸 +- **怎么运作**:上游氢气采购;下游外部氢能车司机服务;与现有供应商/站点/订单/账户衔接 +- **闭环**:覆盖「采购—站点—用氢—结算—司机服务」更大范围生态 + +--- + +### 4.3 运维 + +#### 供应商管理 +- **起点**:整车厂、三方租赁企业纳入供应商体系 +- **怎么运作**:维护档案;为采购/三方租赁合同提供主数据;支撑付款底座 +- **闭环**:与采购合同、付款记录贯通 + +#### 车辆采购 / 三方租赁合同 +- **起点**:向整车厂/三方租赁厂商发起采购或三方租赁合同 +- **怎么运作**:创建审批;关联付款记录;按条款自动生成验车任务 +- **闭环**:合同、付款、验车联动,入库合规入口 + +#### 验车入库 +- **起点**:对采购或三方来源氢能车执行验车 +- **怎么运作**:接收验车任务;记录过程与问题;验完自动入库 +- **闭环**:验车结果与合同、库存绑定,来源可审计 + +#### 车辆上牌 +- **起点**:对仅有车架号的新车执行上牌 +- **怎么运作**:筛选待上牌车;录入/同步牌照;更新库存状态 +- **闭环**:牌证完整,为证照与交车提供前置条件 + +#### 证照管理 +- **起点**:行驶证、道路运输证、登记证、特种设备证/标识、加氢卡、安全阀、压力表等一车一档 +- **怎么运作**:维护档案与有效期;临近到期生成年审/等评/换证待办;推送运维/安全 +- **闭环**:证照全生命周期管理,降低漏办与违规风险 + +#### 备车管理 +- **起点**:日常提前整备,确保交车任务可随时交付 +- **怎么运作**:按计划/库存策略发起检查;记录整备项与责任人 +- **闭环**:交车前置完成,提升交付效率与质量一致性 + +#### 交车管理 +- **起点**:承接租赁/物流交车任务,按区域派属地运维;司机先完成在线培训并留痕 +- **怎么运作**:司机上传身份证/驾驶证/从业资格证、签名与现场拍照;交付证照及保险有效车辆;记录里程/氢量/电量/位置;OCR 胎纹;还车时比对算磨损等费用;E 签宝电子签归档 +- **闭环**:培训与交车数据完整可查,为还车费用与争议提供依据 + +#### 替换车管理 +- **起点**:因客户或车辆原因发起临时/永久替换 +- **怎么运作**:选类型与新旧车;完成替换交还;客户原因自动算费用差 +- **闭环**:过程可追踪、费用可核算,履约不中断 + +#### 还车管理 +- **起点**:处理业务/业管还车任务,按区域派属地运维 +- **怎么运作**:记录还车时间/里程/氢电量/位置;OCR 胎纹对比算磨损费;按里程与氢电差算费用;E 签宝确认归档 +- **闭环**:费用依据交还对比自动出具 + +#### 故障管理 +- **起点**:微信 AI 故障助手或人工记录故障关键信息 +- **怎么运作**:记时间地点车型紧急程度;留存图文视频聊天证据;运维处理至关闭;形成待办统计;沉淀品牌故障类型统计支撑采购论证 +- **闭环**:全过程留痕,证据链完整 + +#### 年审记录 +- **起点**:按行驶证审验有效期提前 3 个月生成年审任务 +- **怎么运作**:按车按月归集;完成后 OCR 新行驶证刷新证照;按月量化完成率 +- **闭环**:任务不遗漏,证照与办理结果同步 + +#### 维修与保养记录 +- **起点**:运维手动维护负责车辆维保记录(首期) +- **怎么运作**:配件费/人工费汇总;按承担方(客户/我司)生成客户账单或计入运维成本;计入车辆盈亏 +- **闭环**:维保成本可追溯可分摊,支撑单车盈亏与对客户计费 + +#### 车辆异动 +- **起点**:因当地维修/年审/保养出库异动,须提前审批 +- **怎么运作**:记异动前后氢电量里程;异动期间加氢记录归集为异动成本 +- **闭环**:可审批可计量,避免成本漏记 + +#### 车辆调拨 +- **起点**:车辆从 A 地调至 B 地运营,须提前审批 +- **怎么运作**:记发起方/费用/运输;接收方确认;运维操作权由 A 转 B +- **闭环**:跨区调拨透明,责任属地随车切换 + +#### 车辆出售、过户、报废 +- **起点**:达出售/过户/报废条件发起资产变更,录入交易方价格过户残值等 +- **怎么运作**:提交申请;董事长确认;运维记录全过程归档;完成后退出运营并出库 +- **闭环**:处置审批留痕,生命周期闭环至出库归档 + +--- + +### 4.4 安全 + +#### 司机管理 +- **起点**:管理物流内部司机,支撑快速选取与档案维护 +- **怎么运作**:维护基础信息与在职状态;管理驾驶证/从业资格证;与交车、台账联动 +- **闭环**:主数据统一、证照可查,支撑调度与合规 + +#### 内部司机培训 +- **起点**:定期开展安全培训并形成可追溯记录 +- **怎么运作**:制定计划组织参训;记录时间内容参训人;形成证据链供交管查询 +- **闭环**:培训电子归档,随时可调阅 + +#### 安全培训资料库 +- **起点**:安全部上传培训与安全运营资料供司机学习 +- **怎么运作**:维护课件制度指引;分类发布版本检索;司机经 APP/小程序学习 +- **闭环**:资料集中更新,移动端可学可查 + +#### 违规记录 +- **起点**:记录内部司机运营违规 +- **怎么运作**:登记事件类型时间结果;关联司机档案;联动客户安全评级 +- **闭环**:违规可追踪汇总,支撑选用与评级 + +#### 违章管理 +- **起点**:记录交通违章并与还车应结款联动 +- **怎么运作**:登记时间地点分值罚款及是否缴纳;未处理纳入还车结算;逾期未缴自动生成代处理费用 +- **闭环**:违章可查,代处理费用与还车/财务口径一致 + +#### 事故管理 +- **起点**:记录事故信息,支撑定责理赔与还车结算 +- **怎么运作**:登记权责划分;记保险介入/赔付/上浮;联动还车应结款写入安全费用 +- **闭环**:登记→赔付→上浮→应结款贯通,费用不遗漏 + +--- + +### 4.5 物流 + +#### 物流合同 +- **起点**:创建物流运力服务合同,录客户/车型/车辆数/交车时间地点,上传附件 +- **怎么运作**:录入项目与合同要素;业务确认生效后可创建轻量调度并提示运维交车 +- **闭环**:合同成为调度派车与交车的上游凭证 + +#### 调度任务 +- **起点**:在生效合同下创建出车任务,派车派司机;非 TMS +- **怎么运作**:选合同、计划出车日、线路、是否多趟与计价口径;未交车须先交车;办结确认实际出车后自动生成物流业务明细一行 +- **闭环**:出车事实追溯到合同与车牌,驱动台账落账 + +#### 物流台账 +- **起点**:承接调度办结自动生成的出车明细,导入/行编辑补录异常与历史 +- **怎么运作**:办结写入营收侧并算金额/总成本/盈亏;氢费/ETC 等成本可手工补录;汇集项目盈亏表;演进关联「收款记录」 +- **闭环**:收入与司机/车辆成本同台账可核对,盈亏可对接财务实收 + +--- + +## 5. AutoRDO 应用约定 + +1. **标题**:优先 `模块标准名:核心动作/结果`(10–30 字),模块名以本词典为准(例:`验车入库:采购与三方租赁合同自动生成验车记录及入库`) +2. **条线归属**:一句需求只挂一个主模块;跨条线联动写在描述里,不硬塞进标题 +3. **部门用词**:口语「业管/能源组/运维」等映射到第 1 节标准名 +4. **闭环口径**:若材料只讲做到一半,闭环写实际说到的结果,缺的标「待确认」 +5. **不做**:不把本词典全文贴进云效描述或输出稿;不脑补材料未出现的故事点细节 +6. **业财关键词**:收款记录、付款记录、对账单、应收/实收、YS、银企直联、E 签宝、小羚羚、PLC——保留原文业务对象名,勿随意改写 + +--- + +## 6. 条线演进(辅助理解,非强制写入描述) + +| 条线 | 现阶段 | 演进方向 | +|---|---|---| +| 租赁 | 合同电子化 · 业务闭环化 | 移动端商城直连发起合同,后续自动打通 | +| 能源 | 手工维护氢费明细 | 签约站主动上报,能源部转为核对确认 | +| 运维 | 车辆使用环节闭环 | 入库→运作→出库全链路 | +| 安全 | 司机安全全链条 | 与客户安全评级挂钩、事前预判 | +| 物流 | 合同 + 调度办结落账 | 成本自动回填、司机在线、关联收款;不做完整 TMS | +| 系统 | 业务驱动 · 财务手工 | 打通 YS · 业财一体 | diff --git a/.claude/skills/AutoRDO/references/rules.md b/.claude/skills/AutoRDO/references/rules.md new file mode 100644 index 0000000..3d6cce1 --- /dev/null +++ b/.claude/skills/AutoRDO/references/rules.md @@ -0,0 +1,67 @@ +# AutoRDO 清洗与拆解细则 + +## 目标 + +碎片 → 可入库的书面「标题」与「描述」。保留原意;不写成产品 PRD。 +多行/多条独立输入 → **多份**转译结果(一份需求一份稿)。 + +## 必做与精炼规则 + +| 规则 | 说明 | +|---|---| +| **多条拆解** | 输入为多条独立诉求时,**先拆后洗**:
• **拆分信号**(满足任一即可):换行且每行一句独立诉求;`1. 2. 3.` / `-` / `*` 列表;表格多行;空行分段;「另外」「还有」「第二条」等显式分条
• **每条**各自输出一份标题+描述+待确认,**禁止**合并成一条大需求
• **不拆**:同一条需求内部的续写、补充说明、功能规则子点(属于描述细节,挂在该条下)
• **边界不清**:无法判断是一条还是多条时,在文首标「待确认:拆条边界」,并按最可能的分条给出结果,请用户校对 | +| **OneOS 领域对齐** | 涉及 ONE-OS / 羚牛业务时,**先读** [oneos-domain.md](oneos-domain.md):
• 部门口语映射到标准名(业管→业务管理组,能源组→业务管理组-能源部 等)
• 标题模块名优先用词典标准名(如「验车入库」而非口语「验车管理」——若材料确指该模块)
• 跨条线联动写在描述,标题只挂主责模块
• **禁止**把词典全文或未在材料中出现的故事点细节写入输出 | +| **标题提炼** | 根据材料内容理解,自动概括简炼、清晰的标准需求标题:
• **内容理解**:准确识别归属的业务模块与核心动作/诉求(如:`验车入库:采购与三方租赁合同自动生成验车记录及入库`)。
• **自然流畅**:表达自然书面化,无需死板强求拼接固定句式;彻底剔除“我们要做一个”、“主要是用于”、“完成之后才能”等口语废话。
• **字数适中**:控制在 10-30 字内,能让读者一眼看懂需求核心。 | +| **元数据识别** | 详见 [meta-fields.md](meta-fields.md)。从台账列或正文自动识别并输出:
• **类型**:`【新增】`/`【优化】`
• **优先级**:`P1-高`/`P2-中`/`P3-低`
• **标签**:标准模块名(+ 可选 PC端/小程序)
• **提交部门**:映射词典标准名(业务管理部→业务管理组,数智中心→数智部 等)
• **提交人**:反馈方/用户列或署名
• 显式列优先;推断不确定则待确认;**不**直接写云效打标 | +| **描述转译** | 原文的转译结果,将口述/碎片材料转译为规范书面表达,保留原意(不改变谁要求什么、约束与例外;不替换成「更优方案」);业财对象名(收款记录、付款记录、YS、E 签宝、小羚羚、PLC 等)按词典保留 | +| 书面化 | 口语改成完整句子或清晰短条目;主谓齐全 | +| 去口头禅 | 删除:嗯、啊、那个、就是说、然后呢、对对对、你懂吧 等 | +| 自我修正 | 以最后一次明确口径为准;若前后矛盾且未决,列入「待确认」 | +| 去重复 | 同一意思多轮确认只保留一处;多条拆解时,各条之间不要互相抄写无关内容 | +| 轻度格式 | 可用短标题/条目;禁止展开成总览/角色/流程图/故事点等 PRD 结构 | +| 去结尾句号 | 描述中每个句子或条目末尾去掉 `。`;保留 `?` `!` 若确为疑问/感叹 | +| 待确认 | 材料含糊处写 `待确认:…`,不臆造;**只要存在待确认,同轮强制**进入 [confirm-pending.md](confirm-pending.md) 的 Plan 逐条确认(无需用户再说「确认待确认」) | +| **待确认 Plan 确认** | 详见 [confirm-pending.md](confirm-pending.md):
• 清洗结果含待确认 → **立刻** `SwitchMode` → plan
• 按需求条号**逐点**确认;**优先选择题**(2–5 项 +「其他/我补充」),选项用 `oneos-domain.md` 辅助
• 也支持用户粘贴**需求描述/补充说明**一次消解多点
• 确认后回填描述并移除已消解项;未确认不得写入定稿 | + +## 多条拆解示例 + +**输入(多行):** +```text +还车应结款生成后自动展示违章事故信息,不必等安全员提交 +验车完成后才能车辆入库 +氢费账户对账单要能关联收款记录 +``` + +**输出**:3 份独立的「原始诉求(AutoRDO)」,每份各自标题+描述;不要揉成一条。 + +## 标题提炼对比示例 + +- ❌ **坏标题(直抄口语/带废话)**:`我们现在要做一个验车管理功能主要是采购合同和三方租赁合同自动形成验车记录验车完成之后才能车辆入库` +- ✅ **好标题(自然清晰,覆盖标准模块)**:`验车入库:采购与三方租赁合同自动生成验车记录及入库`(模块名对齐 oneos-domain) + +## 不做 + +- 不生成 AutoPRD 十章或「产品说明」 +- 不写验收清单、故事点、mermaid +- 不改云效、不建【交付】/分析/设计 +- 不加载 `yunxiao-requirement-lifecycle` +- 不把多条独立诉求合并成一条「大杂烩」需求 + +## 录音 + +1. 已有转写 → 按上文清洗转写稿并拆解标题与描述;转写中若含多条独立诉求,同样先拆后洗 +2. 仅音频 → 回报「请提供转写文本后再 AutoRDO」,停止声称已清洗完成 + +## 质量自检 + +- [ ] 多行/多条输入时,已按条拆成多份结果(条数与输入独立诉求数一致或已说明待确认边界) +- [ ] 已对照 `oneos-domain.md`(ONE-OS 相关材料) +- [ ] **标题清晰通顺**(标准模块名 + 核心动作,无死板句式绑定,无口语废话) +- [ ] 部门/业财关键词已映射或保留为词典标准口径 +- [ ] **描述为原文的转译结果**,干系人能认出这是自己说的意思;未脑补词典故事点 +- [ ] 无口头禅堆砌、无未决矛盾被写成定论 +- [ ] 描述末尾无 `。` +- [ ] 未冒充完整 PRD;未把词典全文写入输出 +- [ ] 已输出类型/优先级/标签/提交部门/提交人(有依据或已标待确认;未臆造提交人) +- [ ] 若存在待确认:已同轮强制进 Plan(未等「确认待确认」口令);优先选择题、选项有词典依据;文字补充已映射到对应点;定稿无未确认臆造 +- [ ] 若无待确认:未多余进 Plan diff --git a/.claude/skills/YunxiaoPM/SKILL.md b/.claude/skills/YunxiaoPM/SKILL.md new file mode 100644 index 0000000..b6a827e --- /dev/null +++ b/.claude/skills/YunxiaoPM/SKILL.md @@ -0,0 +1,113 @@ +--- +name: YunxiaoPM +description: >- + 产品经理云效(Projex)自动化:记录需求(压缩点选 1a2b3a4d:类型/项目/优先级/标签)、 + 实时点选云效项目、推进 待处理→已确认→分析中→设计中→设计完成→待开发, + 交付树【交付】ASSOCIATED /【分析】【设计】TASK_SUB,无单快轨与编号直推交棒何斐, + 创建迭代并挂【交付】(不挂需求)。用户说 YunxiaoPM、YunxiaoPMapp、需求任务、记录需求、受理确认、开始分析、 + 开始设计、设计完成、交棒开发、快轨待开发、编号直推、创建迭代 时使用。 + 不建【开发】/【测试】。凡写云效先 Plan 确认再一口气 apply;禁止对齐 + yunxiao-requirement-lifecycle。 +--- + +# 需求任务(YunxiaoPM) + +产品部云效自动化。斜杠调起 **`/YunxiaoPM`**;对外中文名 **需求任务**(原名 YunxiaoPMapp)。本 Skill **自洽成篇**;**禁止** fork / include / 「对齐」`yunxiao-requirement-lifecycle`。 + +产品经理会话**不要**同时挂载旧 lifecycle Skill,避免双建任务。 + +## Plan 模式门禁(强制 · 凡写云效) + +凡会改云效的操作(建单、改状态、建/改任务、打标、传附件、改负责人、建迭代等),Agent **第一步**必须: + +1. `SwitchMode` → **plan**(说明:先对齐参数与执行清单,确认后再一口气 apply)。 +2. **新建**依赖项目空间时:先按 [references/project-selection.md](references/project-selection.md) **实时拉项目列表并点选(门禁 PJ)**;禁止静默使用 `runtime-ids.json` 默认 `spaceIdentifier`。口令/选项**无法对应**时:**自动重拉项目列表一次**再匹配;仍失败则停请用户重选(禁止第 3 次空转拉取)。 +3. Plan 写清:**已选项目名 + spaceId**、目标需求/任务**编号**、将改状态、将建/复用的交付·分析·设计编号策略、迭代版本类型(若适用)、§0.1① 占位风险勾选(若适用)、**不会做的事**(不建【开发】/【测试】、不按标题查重)。**记录需求**须按 [compact-select.md](references/compact-select.md) 给出 1–4 题字母表,接受压缩答复如 `1a2b3a4d`。 +4. **用户确认 / 批准 Plan / 「执行」之前**:禁止 apply;项目未点选同禁。压缩串须先解析回显,再等「执行」。 +5. 确认后切回 Agent,**同一轮按清单一口气执行到底**,再一次性校验回报;中途缺参才停下。 + +**例外(可读可不进 Plan):** 仅查状态 / 为什么没流转 / 给我方案。 + +**禁止:** 以「参数已齐」「速度路径」「用户很熟」跳过 Plan;「批准计划」若清单未点齐关键参数(含 **PJ 项目**),仍视为未完成门禁。 + +## 真相源模型 + +```text +需求状态 = 阶段看板唯一真相 +【交付】 = 每需求最多 1 条容器(ASSOCIATED→需求) +【分析】/【设计】 = TASK_SUB→交付(交付「子项」必可见;与 ASSOCIATED 同 create 互斥) +【开发】/【测试】 = 不进本 Skill +查重/复用唯一渠道 = 任务编号(ONEOS-xx);禁止按标题 +``` + +编号权威:需求描述 `## 工作项编号(系统)`(见 [references/workitem-ids.md](references/workitem-ids.md))。 +操作顺序:口令显式编号 > 读该区块 > ASSOCIATED/SUB 校验;冲突则停。 + +## 外置调用(禁止本 Skill 内嵌对方全文) + +| 时机 | 调用 | +|---|---| +| 入库清洗聊天/录音 | **`$AutoRDO`**(独立 Skill;路径 `AutoRDO/SKILL.md`;不内嵌清洗细则) | +| 设计完成 PRD + 对象存储链接 + 回填【交付】 | **`$oneos-autoprd`(AutoPRD)**;创建【交付】仍占位,设计完成才灌 MD | +| 人员 / 状态 / 字段 ID;项目 catalog 仅缓存 | [assets/runtime-ids.json](assets/runtime-ids.json) | +| **PJ 云效项目点选**(新建必选;实时列表) | [references/project-selection.md](references/project-selection.md) · [scripts/list_projects.py](scripts/list_projects.py) | +| **压缩点选 `1a2b3a4d`**(类型/项目/优先级/标签) | [references/compact-select.md](references/compact-select.md) · [scripts/list_tags.py](scripts/list_tags.py) | +| 阶段日历工时 | [references/work-hours.md](references/work-hours.md) + [assets/cn-workday-calendar.json](assets/cn-workday-calendar.json) + [scripts/workday_hours.py](scripts/workday_hours.py) | + +## 路由(按需完整阅读) + +| 场景 | 模块 | +|---|---| +| 交付树、关联约定、禁止项 | [references/model.md](references/model.md) | +| 描述双段 · AutoRDO / 占位 / AutoPRD | [references/description-split.md](references/description-split.md) | +| 步骤 0–5 标准路径 | [references/stage-flow.md](references/stage-flow.md) | +| 无单快轨到待开发 | [references/fast-track.md](references/fast-track.md) | +| 编号直推交棒 | [references/number-push.md](references/number-push.md) | +| 交棒门禁 · 回退最小集 | [references/handoff-and-rollback.md](references/handoff-and-rollback.md) | +| 计划开始/完成 · 阶段日历工时 | [references/work-hours.md](references/work-hours.md) | +| Make 导出 ZIP + 复制截图 | [references/make-export-attach.md](references/make-export-attach.md) | +| 创建迭代 · V主.副.子 · 只挂交付 | [references/sprint.md](references/sprint.md) | +| 口令面 | [references/commands.md](references/commands.md) | +| 记录需求元字段(优先级/标签/提交部门/提交人) | [references/record-meta-fields.md](references/record-meta-fields.md) | +| 云效项目点选 PJ | [references/project-selection.md](references/project-selection.md) | +| 验收清单 · 回报模板 | [references/acceptance.md](references/acceptance.md) | +| 交接契约(开发 Skill 入口) | [references/handoff-contract.md](references/handoff-contract.md) | +| 已验证实写 API · 极速建单 | [references/live-api.md](references/live-api.md) · [scripts/live_create_fast.py](scripts/live_create_fast.py) | +| 2026-07-23 复盘与耗时对比 | [references/live-perf-2026-07-23.md](references/live-perf-2026-07-23.md) | + +## 口令速查 + +```text +记录需求:…;项目=(Plan 点选,勿默认);优先级=紧急|高|中|低;标签=…;提交部门=…;提交人=…;推进至=暂不推进|已确认|分析中|设计中|设计完成|待开发|待开发(快轨) +受理确认:ONEOS-xx +开始分析:ONEOS-xx +开始设计:ONEOS-xx;交付任务=…;分析任务=… +设计完成:ONEOS-xx;设计任务=…;原型=… +交棒开发:ONEOS-xx;交付任务=… +快轨待开发:ONEOS-xx +编号直推:分析任务=ONEOS-b / 设计任务=ONEOS-c / 交付任务=ONEOS-a +创建迭代:版本类型=主|副|子;交付任务=ONEOS-a,ONEOS-b,…;名称前缀=… +``` + +**PJ 项目**:新建前必须从云效实时列表点选(见 [project-selection.md](references/project-selection.md));口令带项目名仅作预填建议。 +**压缩点选**:记录需求 Plan 展示 `1.类型 2.项目 3.优先级 4.标签` 字母表;你可回 `1a2b3a4d`(见 [compact-select.md](references/compact-select.md))。标签未命中会自动重拉一次标签候选并重生选项。 +**记录需求**元字段(优先级/标签/提交部门/提交人):与压缩点选并用;缺项用字母表补齐。见 [references/record-meta-fields.md](references/record-meta-fields.md)。 + +后续口令**优先带任务编号**;未带则读「工作项编号(系统)」;仍无则询问;**禁止按标题补全**。 + +## 本 Skill 终点 + +交棒完成(需求=待开发;【交付】负责人=何斐)后结束。可一句:「请技术经理使用开发 Skill」。 +例外:交棒后「创建迭代并关联交付」仍属 YunxiaoPM。 + +**明确不做:** 创建【开发】/【测试】、挂仓库、开分支、提测、写用例。 + +## §0.1 五条补齐(摘要) + +1. **交棒占位**:标准路径下交付仍为 `等待设计任务完成后自动填入` 时**允许**交棒,但 Plan 必须勾选风险,回报首行标红。**快轨**有手工/原型时禁止占位(见 [fast-track.md](references/fast-track.md))。 +2. **预计工时**:标准路径 = **阶段日历工时**(工作日×8);**快轨待开发**需求默认预计/实际各 **2**。 +3. **编号真相源**在需求「工作项编号(系统)」;新建后立即 PATCH 该区块。 +4. **无单快轨**:【设计】描述同步需求;计划起止=当日;TASK_SUB→交付后补 ASSOCIATED→需求;【交付】描述手工同步或 AutoPRD;交付/设计与需求同标签;设计当日完成态。 +5. **描述双段**不互相覆盖;迭代**只挂【交付】**(需求不挂迭代);回退重做设计则**新开设计编号**,交付计划开始不改。 + +细则见各 references。 diff --git a/.claude/skills/YunxiaoPM/acceptance.md b/.claude/skills/YunxiaoPM/acceptance.md new file mode 100644 index 0000000..26cc0b7 --- /dev/null +++ b/.claude/skills/YunxiaoPM/acceptance.md @@ -0,0 +1,49 @@ +# 验收清单与回报 + +## apply 后必须自检 + +| # | 项 | 通过标准 | +|---|---|---| +| 1 | 需求编号 | 回报含 ONEOS-xx | +| 2 | 任务编号 | 交付/分析/设计凡新建或操作均回报编号;禁止只报标题 | +| 3 | 编号区块 | 需求「工作项编号(系统)」与实际一致 | +| 4 | ASSOCIATED | **仅交付**详情关联项可见需求(`createWorkitemRelationInfo=ASSOCIATED`;禁止 PARENT 冒充) | +| 5 | SUB | **必过**:分析/设计 create 用 `TASK_SUB→交付`(含 `parent`+`parentIdentifier`);交付「子项」tab 须可见。与 ASSOCIATED 同 create 互斥,阶段任务关联项可空(见 live-api.md) | +| 6 | 计划开始 | 未误覆盖已有值 | +| 7 | 阶段日历工时 | 有计划完成时已写入;脚注存在;回报用语正确 | +| 8 | 交棒 | 需求=待开发;交付负责人=何斐(交棒场景) | +| 9 | 占位风险 | 交付仍占位时首行标红 | +| 10 | 负向 | 本轮**无**【开发】/【测试】任务;未按标题查重 | + +设计完成额外:AutoPRD 段已写;交付非占位(除非失败停下);ZIP+截图已挂或已列出缺项。 + +迭代额外:只挂【交付】(需求不挂);版本号符合递增规则。 + +## 回报模板(精简) + +```text +【YunxiaoPMapp】 +风险:(若有占位交棒则首行标红) +需求:ONEOS-xx | 状态=… +交付:ONEOS-a | 负责人=… +分析:ONEOS-b | …(无则写无) +设计:ONEOS-c | …(无则写无) +阶段日历工时:分析 Hh / 设计 Hh(若本轮写入) +附件:…(若本轮) +迭代:…(若本轮) +下一步:请技术经理使用开发 Skill(交棒后) +``` + +## 适合全自动 vs 人工门禁 + +| 适合全自动 | 建议半自动/人工门禁 | +|---|---| +| 建单、打标签、挂迭代、写描述、建交付树、改状态、交棒负责人、幂等查重 | 受理(已确认)、是否快轨、设计完成是否真可开发、跨需求优先级与迭代容量、回退与取消 | + +## 实写性能(2026-07-23 复盘) + +| 项 | 要求 | +|---|---| +| 改状态 | 只用 `status/transit`(见 [live-api.md](live-api.md)) | +| 改【交付】负责人 | 只用 `PATCH …/{id}` + `propertyKey=assignedTo` | +| 极速复测 | `scripts/live_create_fast.py`;默认不开浏览器 | diff --git a/.claude/skills/YunxiaoPM/agents/openai.yaml b/.claude/skills/YunxiaoPM/agents/openai.yaml new file mode 100644 index 0000000..f0fdad9 --- /dev/null +++ b/.claude/skills/YunxiaoPM/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "需求任务" + short_description: "云效:压缩点选记录需求→分析/设计→交棒待开发→挂迭代" + default_prompt: "按需求任务(/YunxiaoPM)处理:先 Plan 出压缩点选字母表并确认项目,用户回 1a2b3a4d 与「执行」后再 apply。" diff --git a/.claude/skills/YunxiaoPM/assets/cn-workday-calendar.json b/.claude/skills/YunxiaoPM/assets/cn-workday-calendar.json new file mode 100644 index 0000000..65039c3 --- /dev/null +++ b/.claude/skills/YunxiaoPM/assets/cn-workday-calendar.json @@ -0,0 +1,95 @@ +{ + "schema_version": 1, + "timezone": "Asia/Shanghai", + "note": "法定放假日 holidays;国务院公布的周末调休补班日 workdays_on_weekend。缺年则禁止静默按仅去周末计算。", + "source": { + "2025": "国办发明电〔2024〕12号 https://www.gov.cn/zhengce/zhengceku/202411/content_6986383.htm", + "2026": "国办发明电〔2025〕7号 https://www.gov.cn/zhengce/content/202511/content_7047090.htm" + }, + "years": { + "2025": { + "holidays": [ + "2025-01-01", + "2025-01-28", + "2025-01-29", + "2025-01-30", + "2025-01-31", + "2025-02-01", + "2025-02-02", + "2025-02-03", + "2025-02-04", + "2025-04-04", + "2025-04-05", + "2025-04-06", + "2025-05-01", + "2025-05-02", + "2025-05-03", + "2025-05-04", + "2025-05-05", + "2025-05-31", + "2025-06-01", + "2025-06-02", + "2025-10-01", + "2025-10-02", + "2025-10-03", + "2025-10-04", + "2025-10-05", + "2025-10-06", + "2025-10-07", + "2025-10-08" + ], + "workdays_on_weekend": [ + "2025-01-26", + "2025-02-08", + "2025-04-27", + "2025-09-28", + "2025-10-11" + ] + }, + "2026": { + "holidays": [ + "2026-01-01", + "2026-01-02", + "2026-01-03", + "2026-02-15", + "2026-02-16", + "2026-02-17", + "2026-02-18", + "2026-02-19", + "2026-02-20", + "2026-02-21", + "2026-02-22", + "2026-02-23", + "2026-04-04", + "2026-04-05", + "2026-04-06", + "2026-05-01", + "2026-05-02", + "2026-05-03", + "2026-05-04", + "2026-05-05", + "2026-06-19", + "2026-06-20", + "2026-06-21", + "2026-09-25", + "2026-09-26", + "2026-09-27", + "2026-10-01", + "2026-10-02", + "2026-10-03", + "2026-10-04", + "2026-10-05", + "2026-10-06", + "2026-10-07" + ], + "workdays_on_weekend": [ + "2026-01-04", + "2026-02-14", + "2026-02-28", + "2026-05-09", + "2026-09-20", + "2026-10-10" + ] + } + } +} diff --git a/.claude/skills/YunxiaoPM/assets/runtime-ids.json b/.claude/skills/YunxiaoPM/assets/runtime-ids.json new file mode 100644 index 0000000..d4579ac --- /dev/null +++ b/.claude/skills/YunxiaoPM/assets/runtime-ids.json @@ -0,0 +1,221 @@ +{ + "schema_version": 1, + "skill": "YunxiaoPM", + "verified_at": "2026-07-25", + "note": "YunxiaoPM constants. Project space MUST be user-selected (PJ gate); project.spaceIdentifier is last-known cache only — never auto-apply. See references/project-selection.md.", + "project": { + "selection_mode": "user_pick_required", + "name": null, + "name_aliases": [ + "01_ONEOS", + "ONEOS", + "统一运营管理平台", + "统一运营管理平台PC端", + "统一运营管理平台 PC 端" + ], + "spaceIdentifier": null, + "customCode": null, + "spaceType": "Project", + "organizationIdentifier": "697c54a19df7fdfa65466405", + "default_sprint_name_prefix": "统一运营管理平台PC端", + "list_api": "GET /projex/api/workspace/project/search/list", + "last_selected": { + "name": "01_ONEOS", + "spaceIdentifier": "1280be963a5a2cc126a4118dca", + "customCode": "ONEOS", + "note": "历史常用项;仅作预填建议,禁止未点选即使用" + }, + "refreshed_at": "2026-07-25" + }, + "projects_catalog": [ + { + "name": "05_羚牛碳资产平台", + "identifier": "ff104a3bce09463da136a97999", + "customCode": "CARBON" + }, + { + "name": "06_对外客户项目", + "identifier": "baca8b4d676c5af67e475caec0", + "customCode": "CUST" + }, + { + "name": "07_LNBOX", + "identifier": "35e1e915a052379bbdfb993aac", + "customCode": "LNBOX" + }, + { + "name": "04_AI应用", + "identifier": "d9002ea72c6c3a1a97b02be750", + "customCode": "AIAPP" + }, + { + "name": "03_数据中台", + "identifier": "a801cef5c9a68fa051c07432c7", + "customCode": "DATA" + }, + { + "name": "02_小羚羚APP", + "identifier": "db771a2cca07bed43b369af077", + "customCode": "XLLAPP" + }, + { + "name": "01_ONEOS", + "identifier": "1280be963a5a2cc126a4118dca", + "customCode": "ONEOS" + }, + { + "name": "敏捷研发示例项目", + "identifier": "65eca0c2e16a23939081e19e14", + "customCode": "DEMO" + } + ], + "people": { + "wangmian": { + "displayName": "王冕", + "identifier": "6811df000601d2fea60144a9", + "role": "产品创建人/默认负责人" + }, + "hefei": { + "displayName": "何斐", + "identifier": "695f0400562f09713f9c3a93", + "role": "待开发交棒【交付】负责人" + } + }, + "tags": { + "故障管理": "ceb526a7343995577645317e9a", + "还车应结款": "4204960ce658c94b17abb7c5f6", + "工作台": "b62d21beac55c0b43389915eab", + "交车管理": "b6947b5aca82a8c759612ba039", + "合同管理": "23873be81931cb0dc1bf87a456", + "安全培训": "76988b5f73ef515de5930d59b9", + "证照管理": "11f4c2ec65901a8f3199c81706", + "还车管理": "1e0fce2d6929ff4ac13b310e97" + }, + "status": { + "req": { + "待处理": "100005", + "已确认": "32", + "分析中": "154395", + "设计中": "156603", + "设计完成": "307012", + "待开发": "1582fc929d429111b925309493" + }, + "task": { + "待处理": "100005", + "已完成": "100014" + }, + "transit_api": "POST /projex/api/workitem/workitem/{id}/status/transit", + "fast_handoff_hops": [ + "设计完成", + "待开发" + ], + "note": "见 references/live-api.md;禁止再用 updateStatus" + }, + "workitem_types": { + "product_req": { + "name": "产品类需求", + "category": "Req", + "identifier": "9uy29901re573f561d69jn40" + }, + "task": { + "name": "任务", + "category": "Task", + "identifier": "ba102e46bc6a8483d9b7f25c" + } + }, + "priority": { + "紧急": "646004e97f54bb77fec7b455df", + "高": "95b89e0a524d9693e1f335ffe5", + "中": "fa155d1214f9f8db222d39db3b", + "低": "92924feff9c1085891e7511872" + }, + "fields": { + "plan_start": { + "fieldIdentifier": "79", + "name": "计划开始时间", + "value_shape": "YYYY-MM-DD HH:mm:ss China wall time preferred; epoch ms string also accepted", + "example": "2026-07-27 12:00:00", + "update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON" + }, + "plan_end": { + "fieldIdentifier": "80", + "name": "计划完成时间", + "value_shape": "YYYY-MM-DD HH:mm:ss or epoch ms string", + "update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON" + }, + "estimated_hours": { + "fieldIdentifier": "101586", + "name": "预计工时", + "note": "不可直接改字段;须工时预估登记", + "create_api": "POST /projex/api/workitem/workitem/time/estimate body spentTime,type,recordUserIdentifier,workitemIdentifier", + "delete_api": "DELETE /projex/api/workitem/workitem/time/estimate/{workitemId}/{estimateId}", + "list_api": "GET /projex/api/workitem/workitem/time/estimate/list?workitemIdentifier=" + }, + "actual_hours": { + "name": "实际工时", + "note": "快轨待开发默认 2", + "create_api": "POST /projex/api/workitem/workitem/time body actualTime,type,recordUserIdentifier,workitemIdentifier,gmtStart,gmtEnd (epoch ms string)", + "list_api": "GET /projex/api/workitem/workitem/time/list?workitemIdentifier=", + "fast_track_default": 2, + "verified_at": "2026-07-27", + "verified_on": "ONEOS-293" + }, + "fast_track": { + "req_estimated_hours": 2, + "req_actual_hours": 2, + "design_plan_start_end_same_day": true, + "design_description": "copy_req_document", + "delivery_description": "manual_sync_or_autoprd", + "delivery_design_same_tags_as_req": true, + "design_associated_to_req_after_task_sub": true, + "document_update_api": "PATCH /projex/api/workitem/workitem/{id}/document {content,formatType:RICHTEXT}" + }, + "submit_department": { + "fieldIdentifier": "3132597a9718d1c282b7ba5a0c", + "name": "提交部门", + "format": "string input", + "update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON", + "verified_at": "2026-07-27", + "verified_on": "ONEOS-293" + }, + "submitter": { + "fieldIdentifier": "9e01269e96f91fbb97d36bf5b3", + "name": "提交人", + "format": "string input", + "update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON", + "verified_at": "2026-07-27", + "verified_on": "ONEOS-293" + }, + "tag": { + "fieldIdentifier": "tag", + "name": "标签", + "propertyKey": "tag" + } + }, + "assignee_rules": { + "待开发_交付任务": "hefei", + "分析中_交付与分析": "creator", + "设计中_设计": "creator" + }, + "relation": { + "delivery_to_req": "ASSOCIATED", + "stage_to_delivery": "TASK_SUB", + "create_field": "createWorkitemRelationInfo", + "forbid": [ + "PARENT_as_关联项", + "ASSOCIATED_only_for_stage_tasks_without_TASK_SUB" + ], + "note": "交付必须 ASSOCIATED→需求。分析/设计默认 TASK_SUB→交付(子项 tab)。二者同 create 互斥。见 references/live-api.md" + }, + "delivery_placeholder": "等待设计任务完成后自动填入", + "task_title_prefixes": { + "delivery": "【交付】", + "analysis": "【分析】", + "design": "【设计】" + }, + "publish_url": { + "shape": "{baseUrl}/{prototype-id}/index.html", + "forbid_prototypes_prefix": true, + "require_index_html": true + } +} diff --git a/.claude/skills/YunxiaoPM/docs-YunxiaoPMapp-实现原理-开发Skill对接.md b/.claude/skills/YunxiaoPM/docs-YunxiaoPMapp-实现原理-开发Skill对接.md new file mode 100644 index 0000000..24a728d --- /dev/null +++ b/.claude/skills/YunxiaoPM/docs-YunxiaoPMapp-实现原理-开发Skill对接.md @@ -0,0 +1,317 @@ +# YunxiaoPMapp 实现原理说明 + +> 供**开发部门**设计 / 制作「开发侧 Skill」(暂称 **YunxiaoDevapp**)时对齐契约。 +> 本文描述产品侧 Skill **YunxiaoPMapp** 的模型、边界、数据契约与交棒接口;**不是**对 `yunxiao-requirement-lifecycle` 的兼容说明。 +> 技能包:https://github.com/15810879921-coder/oneos-pm-skills · `skills/YunxiaoPMapp/` +> 文档版本:2026-07-24 + +--- + +## 1. 为什么拆成两个 Skill + +| | YunxiaoPMapp(产品) | 开发 Skill(待建) | +|---|---|---| +| 职责 | 需求从「待处理」到「待开发」交棒 | 从「待开发」到开发完成 / 提测前 | +| 任务类型 | 【交付】【分析】【设计】 | 【开发】(可多条)、可选挂仓库/分支 | +| 负责人交接 | 交棒时【交付】→ **何斐** | 拆【开发】子任务并指派研发 | +| 明确不做 | 建【开发】/【测试】、开分支、提测 | 不建【分析】/【设计】、不改 AutoRDO 段 | + +**硬规则:** 两个 Skill **禁止互相 include / fork 全文**;只认本文第 6 章「交棒契约」。产品会话与开发会话不要同时挂载旧 `yunxiao-requirement-lifecycle`,避免双建任务树。 + +--- + +## 2. 核心真相源(必须先统一) + +```text +需求状态 = 阶段看板唯一真相(分析中 / 设计中 / 待开发 …) +【交付】任务 = 每需求最多 1 条「交付容器」 +【分析】【设计】 = 【交付】下的阶段子项(产品侧建) +【开发】【测试】 = 开发 / 测试 Skill 建(产品侧永不创建) +查重 / 复用 = 只认云效任务编号 ONEOS-xx,禁止按标题 +``` + +### 2.1 关系模型(云效) + +```mermaid +flowchart TB + Req["产品类需求 ONEOS-R"] + DEL["【交付】ONEOS-a"] + AN["【分析】ONEOS-b"] + DE["【设计】ONEOS-c"] + DEV["【开发】… 开发 Skill"] + TEST["【测试】… 测试 Skill"] + + DEL -->|"ASSOCIATED 关联项"| REQ + AN -->|"TASK_SUB 子项"| DEL + DE -->|"TASK_SUB 子项"| DEL + DEV -.->|"建议:SUB→交付 或 ASSOCIATED→需求+交付"| DEL + TEST -.->|"测试 Skill"| REQ +``` + +| 关系 | 谁写 | 验收 | +|---|---|---| +| `ASSOCIATED` | **仅【交付】→ 需求** | 交付详情「关联项」可见需求 | +| `TASK_SUB` | 【分析】/【设计】→【交付】 | 交付详情「子项」可见阶段任务 | +| (开发侧建议) | 【开发】→【交付】或需求 | 由开发 Skill 自定,但须可编号查重 | + +**踩坑(已复现):** 对分析/设计只写 `parentIdentifier` + `ASSOCIATED→需求`,交付「子项」会为空。正确:create 时 `createWorkitemRelationInfo=TASK_SUB` + `parent`/`parentIdentifier`=交付。同一 create **只能**带一条关系,ASSOCIATED 与 TASK_SUB 互斥。 + +### 2.2 标题只是展示 + +- 形式:`【交付】`/`【分析】`/`【设计】` + 需求标题 +- **不参与查重**;改标题不影响幂等 +- 开发侧【开发】标题建议 `【开发】` + 范围简述,同样**只认编号** + +--- + +## 3. 编号权威源 + +需求描述固定区块(机器可解析): + +```markdown +## 工作项编号(系统) +- 交付:ONEOS-a +- 分析:ONEOS-b(无则写无) +- 设计:ONEOS-c(无则写无) +``` + +| 规则 | 说明 | +|---|---| +| 新建后立即 PATCH | 只改本区块,不动 AutoRDO / AutoPRD | +| 操作顺序 | 口令显式编号 > 读本区块 > ASSOCIATED/SUB 反查 | +| 冲突 | 停下人工;**禁止按标题猜** | +| 多交付编号 | 停止并列号请人合并 | + +**开发 Skill 入口建议:** 口令必须带 `需求=ONEOS-R` + `交付任务=ONEOS-a`;缺则读本区块;仍无则询问。 + +--- + +## 4. 需求描述双段(产品写 · 开发只读) + +```markdown +## 原始诉求(AutoRDO) +(聊天/录音清洗稿;设计完成也不删) + +## 产品说明(AutoPRD) +(设计完成才灌满;含对象存储预览链接) + +## 工作项编号(系统) +- 交付 / 分析 / 设计 +``` + +| 段 | 谁写 | 开发侧用法 | +|---|---|---| +| AutoRDO | `$AutoRDO` → 产品记需求时 | 占位交棒时**唯一可信**业务原文 | +| AutoPRD | `$oneos-autoprd` → 设计完成时 | 正式需求说明;缺则有权要求产品补设计完成 | +| 编号区块 | YunxiaoPMapp | 定位交付树 | + +**【交付】描述:** 设计完成前固定占位文案 `等待设计任务完成后自动填入`;设计完成后替换为 AutoPRD 正文。 +**允许占位交棒**,但产品回报必须标红风险;开发 Skill 应检测占位并提示「材料不齐,可凭 AutoRDO 开工或退回补设计」。 + +对象存储预览 URL 形态:`{baseUrl}/{prototype-id}/index.html`(禁止加 `prototypes/` 前缀、禁止去掉 `index.html`)。 + +--- + +## 5. 产品侧阶段状态机(0→5) + +```text +0 创建需求(待处理) ─AutoRDO→ 仅需求,不建任务 +1 受理确认(已确认) 仍不建任务 +2 分析中 建【交付】+【分析】 +3 设计中 建【设计】;收口【分析】计划完成 +4 设计完成 收口【设计】;AutoPRD+附件;灌【交付】描述 +5 待开发交棒 ★ 需求→待开发;【交付】负责人→何斐 + ★ = YunxiaoPMapp 终点 / 开发 Skill 起点 +``` + +### 5.1 各步要点(开发需知道的副作用) + +| 步骤 | 需求状态 | 任务侧关键动作 | +|---|---|---| +| 2 分析中 | 分析中 | 新建【交付】ASSOCIATED→需求;描述=占位;计划开始仅空写当日且**此后不可改**;新建【分析】TASK_SUB→交付 | +| 3 设计中 | 设计中 | 新建【设计】;【分析】计划完成=当日 + 阶段日历工时 | +| 4 设计完成 | 设计完成 | 【设计】完成态;需求+交付挂 ZIP/截图;交付描述换正式说明 | +| 5 交棒 | **待开发** | **只改交付负责人→何斐**;不新建分析/设计;不写交付计划完成 | + +### 5.2 两条旁路(仍交到同一终点) + +| 路径 | 前提 | 行为 | +|---|---|---| +| **无单快轨** | 尚无分析路径 / 明确跳过分析 | 待开发 + 新建交付+设计(设计当日收口);**不建分析**;默认可不跑 AutoPRD | +| **编号直推** | 已有分析/设计**编号** | 收口空计划完成 → 待开发 + 交付交棒何斐;不新建冗余单 | + +--- + +## 6. 交棒契约(开发 Skill 必须实现) + +### 6.1 入口条件(产品已完成) + +```text +需求状态 = 待开发 +【交付】任务编号 = ONEOS-a(唯一) +【交付】负责人 = 何斐 +【交付】ASSOCIATED → 该需求 +可选:分析/设计编号在「工作项编号(系统)」中 +``` + +### 6.2 开发 Skill 建议入口口令 + +```text +接手开发:需求=ONEOS-R;交付任务=ONEOS-a +拆分开发:交付任务=ONEOS-a;范围=前端|后端|…;负责人=… +开始开发:开发任务=ONEOS-d +开发完成:开发任务=ONEOS-d +``` + +### 6.3 开发 Skill 建议职责边界 + +| 应做 | 不应做 | +|---|---| +| 在【交付】下建一条或多条【开发】(编号幂等) | 再建【分析】/【设计】或第二套【交付】 | +| 正式关联:建议 SUB→交付,或同时 ASSOCIATED→需求 | 只按标题「【开发】xxx」查重 | +| 挂仓库 / 分支 / MR(按你们 Codeup 规范) | 覆盖需求 AutoRDO 段 | +| 改【开发】状态与负责人 | 擅自把需求从待开发退回(除非产品授权回退口令) | +| 检测交付描述是否仍为占位并提示 | 假装 AutoPRD 已齐全 | +| 全部【开发】完成后通知测试 Skill / 建【测试】 | 在产品 Skill 会话里混跑 | + +### 6.4 共享常量(可引用,勿整包加载) + +路径均在 `skills/YunxiaoPMapp/assets/`: + +| 文件 | 内容 | +|---|---| +| `runtime-ids.json` | 项目 spaceId、何斐 ID、工作项类型、状态 transit、字段 79/80 | +| `cn-workday-calendar.json` | 法定节假日 / 调休(阶段日历工时) | + +开发侧可复制一份到自己的 `assets/`,或只读引用短路径;**不要**把 YunxiaoPMapp 的 references 全文 include 进开发 Skill。 + +--- + +## 7. 计划时间与「阶段日历工时」 + +产品侧口径(开发侧若写计划时间建议对齐语义): + +| 对象 | 计划开始 | 计划完成 | 预计工时 | +|---|---|---|---| +| 【交付】 | 首次分析中空则写当日,**永不覆盖** | 产品侧不写(留给上线) | 一般不推全周期 | +| 【分析】【设计】 | 创建时空则写当日,**永不覆盖** | 阶段收口日 | `工作日×8` | + +```text +预计工时 = 阶段日历工时(Lead Time)≠ 人力投入人天 +算法:起止日含首尾,扣周末与法定假,计入调休补班;×8 小时 +禁止:(结束−开始+1)×8 的自然日算法 +``` + +脚注固定:`【系统】预计工时=阶段日历工时(工作日×8),非人力投入预估` +脚本:`scripts/workday_hours.py` + +**建议:** 开发 Skill 若给【开发】写预计工时,在文档中明确是「人力投入预估」还是「阶段日历工时」,避免与产品侧混称。 + +--- + +## 8. Agent 运行时原理(制作 Skill 时照抄模式) + +### 8.1 Plan 门禁 + +凡写云效:`SwitchMode → plan` → 用户确认 → 一口气 apply → 一次校验回报。 +禁止用「参数已齐 / 速度路径」跳过 Plan。 + +### 8.2 模块化路由 + +`SKILL.md` 只做路由与门禁;细则在 `references/*.md`;常量在 `assets/`;脚本在 `scripts/`。 +外置能力(AutoRDO、AutoPRD)**调用对方 Skill**,不内嵌对方全文。 + +### 8.3 幂等 + +```text +幂等键 = 项目 + 需求编号 + 任务类型角色(交付/分析/设计/开发…)+ 已登记任务编号 +命中编号 → 复用并补缺字段;禁止新建第二条同角色主容器(交付唯一) +``` + +### 8.4 验收与回报 + +每次 apply 后自检(产品侧清单见 `references/acceptance.md`)。开发侧建议至少回报: + +```text +【YunxiaoDevapp】 +需求:ONEOS-R | 状态=待开发|开发中|… +交付:ONEOS-a | 负责人=… +开发:ONEOS-d1, ONEOS-d2 | … +仓库/分支:… +下一步:… +``` + +### 8.5 已验证写路径(可复用思路) + +| 动作 | 约定(见 live-api.md) | +|---|---| +| 改状态 | `POST …/status/transit`(勿用错误 updateStatus) | +| 改负责人 | `PATCH …/{id}` + `propertyKey=assignedTo` | +| 建单关系 | create 带 `createWorkitemRelationInfo` | +| 极速复测 | `scripts/live_create_fast.py`(可参考,开发侧另写自己的脚本) | + +--- + +## 9. 回退最小集(开发需配合) + +| 场景 | 产品侧规则 | 开发侧注意 | +|---|---|---| +| 待开发 → 退回设计中 | 需求回退;交付负责人可改回产品;**交付计划开始不改**;重做设计则**新开设计编号** | 已建【开发】是否取消 / 暂停由开发 Skill 定义;勿静默删编号 | +| 设计完成后需求大变 | Plan 问是否回退;更新 AutoPRD;AutoRDO 保留+变更纪要 | 勿覆盖 AutoRDO | +| 取消需求 | 任务标取消;不删编号区块 | 同步取消未完成【开发】 | + +--- + +## 10. 建议的「YunxiaoDevapp」目录骨架 + +```text +YunxiaoDevapp/ +├── SKILL.md # 门禁 + 路由 + 边界(引用本文契约,不 include PMapp) +├── assets/ +│ └── runtime-ids.json # 可从 PMapp 复制/裁剪 +├── references/ +│ ├── model.md # 【开发】与交付/需求的关系约定 +│ ├── handoff-intake.md # 认交付编号 + 占位检测 +│ ├── split-dev-tasks.md # 拆前端/后端多【开发】 +│ ├── codeup.md # 分支 / MR / 关联 +│ ├── commands.md # 口令面 +│ └── acceptance.md +└── scripts/ # 可选极速 API +``` + +`SKILL.md` description 建议写明:触发词「接手开发 / 拆分开发 / 开始开发」;**Does NOT create 【分析】/【设计】**;入口条件需求=待开发。 + +--- + +## 11. 对照检查表(开发 Skill 评审用) + +- [ ] 是否只认「需求编号 + 交付任务编号」,禁止标题查重 +- [ ] 是否拒绝创建第二套【交付】或【分析】【设计】 +- [ ] 占位交棒时是否提示风险且不假装 PRD 齐全 +- [ ] 【开发】是否可编号幂等、可挂到交付树 +- [ ] 是否与 YunxiaoPMapp **会话隔离**(不同 Skill、不同口令) +- [ ] 计划开始字段是否遵守「已有值不覆盖」(若沿用同一字段) +- [ ] 提测 / 【测试】是否交给测试 Skill,本包边界清晰 + +--- + +## 12. 相关文档索引 + +| 主题 | 路径(仓库内) | +|---|---| +| Skill 入口 | `skills/YunxiaoPMapp/SKILL.md` | +| 交付树模型 | `skills/YunxiaoPMapp/references/model.md` | +| 交棒契约 | `skills/YunxiaoPMapp/references/handoff-contract.md` | +| 标准路径 0–5 | `skills/YunxiaoPMapp/references/stage-flow.md` | +| 交棒门禁 / 回退 | `skills/YunxiaoPMapp/references/handoff-and-rollback.md` | +| 描述双段 | `skills/YunxiaoPMapp/references/description-split.md` | +| 编号区块 | `skills/YunxiaoPMapp/references/workitem-ids.md` | +| 快轨 / 编号直推 | `references/fast-track.md` · `number-push.md` | +| 工时算法 | `references/work-hours.md` | +| 实写 API | `references/live-api.md` | + +安装产品 Skill: + +```bash +npx skills add 15810879921-coder/oneos-pm-skills --skill YunxiaoPMapp -a cursor -g -y +``` diff --git a/.claude/skills/YunxiaoPM/docs-产品部流转流程图.md b/.claude/skills/YunxiaoPM/docs-产品部流转流程图.md new file mode 100644 index 0000000..9afb919 --- /dev/null +++ b/.claude/skills/YunxiaoPM/docs-产品部流转流程图.md @@ -0,0 +1,179 @@ +# 产品部流转:YunxiaoPMapp(到交棒为止) + +> 用途:先把**产品部**在云效上的工作流转说清楚;开发 / 测试 / 发版不在本 Skill 内,仅在文末标出交接点。 +> 依据:`references/stage-flow.md` · `model.md` · `fast-track.md` · `handoff-contract.md` +> 日期:2026-07-24 + +--- + +## 1. 产品部管到哪里 + +```text +产品部(YunxiaoPMapp)终点 = 需求状态「待开发」+【交付】负责人「何斐」 +之后 = 请技术经理使用开发 Skill(不建【开发】/【测试】) +``` + +| 谁 | 做什么 | 不做什么 | +|---|---|---| +| 产品 / YunxiaoPMapp | 建需求、打标签、建【交付】【分析】【设计】、推进状态、设计完成灌 AutoPRD、交棒 | 建【开发】【测试】、开分支、提测、发版 | +| 技术经理(接手) | 从「待开发」起拆开发 | 不改写 AutoRDO;不新建第二套【交付】 | + +--- + +## 2. 总览流程图(标准路径) + +```mermaid +flowchart TB + subgraph inputs [材料入口] + Chat[聊天/录音/口述] + AutoRDO["$AutoRDO 清洗"] + Proto[原型页 Make] + end + + subgraph pm [产品部 · YunxiaoPMapp] + S0["0 创建需求\n状态=待处理\n只写 AutoRDO\n不建任务"] + S1["1 受理确认\n状态=已确认\n仍不建任务"] + S2["2 分析中\n建【交付】+【分析】"] + S3["3 设计中\n建【设计】\n收口【分析】"] + S4["4 设计完成\n收口【设计】\nAutoPRD+附件\n灌【交付】描述"] + S5["5 待开发交棒\n交付负责人→何斐\n★ 产品终点"] + end + + subgraph after [产品之后 · 不在本 Skill] + Dev["开发 Skill\n拆【开发】…"] + Test["测试 Skill"] + Rel["发版"] + end + + Chat --> AutoRDO --> S0 + Proto -.-> S4 + S0 --> S1 --> S2 --> S3 --> S4 --> S5 + S5 -->|"交棒契约:需求编号+交付编号"| Dev + Dev --> Test --> Rel +``` + +--- + +## 3. 各步说明(产品侧) + +| 步骤 | 需求状态 | 云效任务动作 | 描述写什么 | 口令示例 | +|---|---|---|---|---| +| 0 创建 | 待处理 | 无 | `## 原始诉求(AutoRDO)`;编号区块待建 | `记录需求:…;推进至=暂不推进` | +| 1 受理 | 已确认 | 仍无 | 不动 | `受理确认:ONEOS-xx` | +| 2 分析中 | 分析中 | 新建【交付】ASSOCIATED→需求;新建【分析】TASK_SUB→交付 | 交付=占位文案 | `开始分析:ONEOS-xx` | +| 3 设计中 | 设计中 | 新建【设计】TASK_SUB→交付;分析计划完成=当日+阶段日历工时 | — | `开始设计:ONEOS-xx;交付=…;分析=…` | +| 4 设计完成 | 设计完成 | 设计完成态;需求+交付挂 ZIP/截图;交付描述换 AutoPRD | `$oneos-autoprd` 灌 `## 产品说明` | `设计完成:ONEOS-xx;设计=…;原型=…` | +| 5 交棒 | **待开发** | **只**把【交付】负责人改为何斐 | 若仍占位须标红风险 | `交棒开发:ONEOS-xx;交付=…` | + +**编号权威:** 需求描述 `## 工作项编号(系统)`(交付 / 分析 / 设计)。后续口令优先带 `ONEOS-xx`,禁止按标题查重。 + +--- + +## 4. 交付树(产品建出来的结构) + +```mermaid +flowchart TB + REQ["产品类需求 ONEOS-R\n状态=阶段真相"] + DEL["【交付】ONEOS-a\nASSOCIATED→需求"] + AN["【分析】ONEOS-b"] + DE["【设计】ONEOS-c"] + + DEL -->|"关联项 ASSOCIATED"| REQ + AN -->|"子项 TASK_SUB"| DEL + DE -->|"子项 TASK_SUB"| DEL +``` + +验收口诀: +- 打开【交付】→「关联项」能看到需求 +- 打开【交付】→「子项」能看到分析/设计(必须 `TASK_SUB`,不能只写 parentIdentifier) + +--- + +## 5. 两条旁路(仍交到同一终点) + +```mermaid +flowchart LR + subgraph standard [标准] + A1[分析中] --> A2[设计中] --> A3[设计完成] --> A4[待开发] + end + subgraph fast [无单快轨] + B1[待处理/已确认等] --> B2["待开发\n建交付+设计当日收口\n不建分析"] + end + subgraph push [编号直推] + C1[已有分析/设计编号] --> C2[收口空计划完成] --> C3[待开发+交棒何斐] + end +``` + +| 路径 | 何时用 | 注意 | +|---|---|---| +| 标准 0→5 | 正常产品节奏 | 设计完成才灌 AutoPRD | +| 快轨 | 明确跳过分析、急交棒 | 默认可不跑 AutoPRD;交付常占位→回报标红 | +| 编号直推 | 树上已有阶段任务编号 | 不新建冗余单;只认编号 | + +--- + +## 6. 产品部人机边界(Plan 门禁) + +```text +凡写云效:Plan 对齐 → 人确认 → 一口气 apply → 一次校验回报 +``` + +| 适合全自动 | 建议人点一下 | +|---|---| +| 建单、打标、建树、改状态、交棒负责人、幂等复用 | 优先级、标签、是否快轨、设计是否真可开发、占位是否接受交棒 | + +--- + +## 7. 交棒给下游时交出什么 + +产品回报至少包含: + +```text +【YunxiaoPMapp】 +风险:(占位交棒则首行标红) +需求:ONEOS-R | 状态=待开发 +交付:ONEOS-a | 负责人=何斐 +分析:ONEOS-b 或 无 +设计:ONEOS-c 或 快轨占位说明 +下一步:请技术经理使用开发 Skill +``` + +下游入口条件见 `references/handoff-contract.md`:认 **需求编号 + 交付任务编号**。 + +--- + +## 8. 和全链路的关系(本文件边界) + +```mermaid +flowchart LR + PM[产品部本文] --> Handoff[待开发交棒] + Handoff --> Dev[开发] + Dev --> QA[测试] + QA --> Rel[发版] +``` + +全链路(含谢佳伟 / 时生亮)见仓库原型: +`src/prototypes/yunxiao-pipeline-handbook/` +产品 Skill 对接开发说明: +`docs-YunxiaoPMapp-实现原理-开发Skill对接.md` + +--- + +## 9. 相关 references + +| 主题 | 路径 | +|---|---| +| 标准 0–5 | `references/stage-flow.md` | +| 交付树 / 子项 | `references/model.md` | +| 快轨 | `references/fast-track.md` | +| 编号直推 | `references/number-push.md` | +| 交棒门禁 | `references/handoff-and-rollback.md` | +| 交棒契约 | `references/handoff-contract.md` | +| 口令 | `references/commands.md` | + +--- + +## 延伸阅读 + +- 四角色泳道(产品/开发/测试/发版):`docs-四角色泳道流程图.md` +- 开发侧草案:`docs-开发侧流转草案.md` diff --git a/.claude/skills/YunxiaoPM/docs-全流程分步说明.md b/.claude/skills/YunxiaoPM/docs-全流程分步说明.md new file mode 100644 index 0000000..10a37c9 --- /dev/null +++ b/.claude/skills/YunxiaoPM/docs-全流程分步说明.md @@ -0,0 +1,318 @@ +# 全流程分步说明:产品 → 开发 → 测试 → 发版 + +> 把整条链路拆成**按顺序执行的步骤**,每步说清:谁做、做什么、云效上变成什么、下一步谁接。 +> 读完应能照着口令/动作走完一轮(标准路径)。 +> 日期:2026-07-24 + +--- + +## 先记住三件事 + +1. **需求状态 = 阶段真相** + 看需求状态就知道现在卡在哪一段(分析中 / 待开发 / 开发中 / 待测试…)。 +2. **每条需求只有 1 个【交付】容器** + 分析、设计、开发、测试都挂在这个交付下面(子项),不要再建第二个【交付】。 +3. **编号说话,不靠标题查** + 口令尽量带 `ONEOS-xx`;改标题不影响关联。 + +```text +需求(状态主轴) + └─【交付】(容器,关联到需求) + ├─【分析】【设计】 ← 产品建 + ├─【开发】… ← 开发建 + └─【测试】 ← 开发提测时建 +【发版】另建,挂迭代 + 范围内需求 +``` + +--- + +## 总步骤一览(共 18 步) + +| 阶段 | 步骤 | 一句话 | +|---|---|---| +| **产品** | 1~6 | 从记诉求到交棒给何斐 | +| **开发** | 7~12 | 从拆任务到提测 | +| **测试** | 13~15 | 从接测到测试完成 | +| **发版** | 16~17 | 从建发版到发布结果 | +| **收口** | 18 | 产品验收关闭 | + +--- + +## 阶段 A|产品(步骤 1~6) + +### 步骤 1|记录需求 + +| | | +|---|---| +| **谁** | 产品 / AI(YunxiaoPMapp) | +| **做什么** | 把聊天、录音、口述先洗成 AutoRDO,再建一条**产品类需求** | +| **需求状态** | **待处理** | +| **云效任务** | 不建任何任务 | +| **描述里写** | `## 原始诉求(AutoRDO)`;编号区先空着 | +| **口令例** | `记录需求:…;推进至=暂不推进` | +| **为什么** | 先落盘,避免口头需求丢;还没确认值不值得做 | + +--- + +### 步骤 2|受理确认 + +| | | +|---|---| +| **谁** | 产品 | +| **做什么** | 确认要做:定优先级、打标签(从云效标签里点选) | +| **需求状态** | **已确认** | +| **云效任务** | 仍不建任务 | +| **口令例** | `受理确认:ONEOS-xx` | +| **为什么** | 和「还没想清楚」的待处理分开;确认后才进分析/设计 | + +--- + +### 步骤 3|开始分析 + +| | | +|---|---| +| **谁** | 产品 / AI | +| **做什么** | ① 建 **【交付】**(整条需求的唯一容器,关联到需求)② 建 **【分析】**(挂在交付**子项**下) | +| **需求状态** | **分析中** | +| **云效任务** | 【交付】+【分析】 | +| **口令例** | `开始分析:ONEOS-xx` | +| **验收** | 打开【交付】→「关联项」有需求;「子项」有【分析】 | +| **为什么** | 分析要有落点;后续设计/开发/测试都挂同一棵树 | + +--- + +### 步骤 4|开始设计 + +| | | +|---|---| +| **谁** | 产品 / AI | +| **做什么** | 建 **【设计】**(子项挂交付);收口【分析】计划时间 | +| **需求状态** | **设计中** | +| **云效任务** | 新增【设计】 | +| **口令例** | `开始设计:ONEOS-xx;交付=…;分析=…` | +| **为什么** | 进入原型/方案设计;分析阶段收口 | + +--- + +### 步骤 5|设计完成 + +| | | +|---|---| +| **谁** | 产品 / AI | +| **做什么** | 收口【设计】;跑 AutoPRD,把**产品说明**灌进【交付】描述;挂原型 ZIP/截图等附件 | +| **需求状态** | **设计完成** | +| **口令例** | `设计完成:ONEOS-xx;设计=…;原型=…` | +| **为什么** | 开发接手时看的是【交付】里的产品说明,不是聊天记录 | + +--- + +### 步骤 6|交棒开发(产品终点) + +| | | +|---|---| +| **谁** | 产品 / AI | +| **做什么** | 需求改为待开发;**【交付】负责人改为何斐**(不新建开发/测试任务) | +| **需求状态** | **待开发** ★ | +| **交出什么** | 需求编号 +【交付】编号 | +| **口令例** | `交棒开发:ONEOS-xx;交付=…` | +| **为什么** | 产品部到此结束;技术经理从这里拆开发 | + +> 旁路:急单可用**快轨**(跳过分析、直接待开发),仍交到同一终点「待开发 + 交付→何斐」。 + +--- + +## 阶段 B|开发(步骤 7~12) + +### 步骤 7|分配开发任务 + +| | | +|---|---| +| **谁** | 开发主管何斐 / 开发 Skill | +| **做什么** | 输入**交付任务编号** → 自动建 **【开发】**;标题=`【开发】`+需求标题;**描述人工补**;指定开发人;自动写计划时间 | +| **关联** | 子项挂【交付】;关联项挂需求 | +| **需求状态** | 仍为 **待开发**(确认即可,一般不用再改) | +| **口令例** | `分配任务:交付=ONEOS-a;开发人=张三` | +| **为什么** | 一条需求可拆多个开发任务;都挂在同一交付下 | + +--- + +### 步骤 8|开始开发 + +| | | +|---|---| +| **谁** | 开发人员 | +| **做什么** | 口令带上**开发任务编号** | +| **开发任务** | → **处理中** | +| **需求状态** | → **开发中** | +| **口令例** | `开始开发:ONEOS-d` | +| **为什么** | 看板一眼能看出「正在写代码」 | + +--- + +### 步骤 9|写代码 / 提 MR + +| | | +|---|---| +| **谁** | 开发人员 | +| **做什么** | 按【交付】里的产品说明实现;分支/MR 建议挂上开发任务编号与需求编号 | +| **需求状态** | 仍为 **开发中** | +| **为什么** | 代码可追溯到哪条开发任务、哪条需求 | + +--- + +### 步骤 10|AI 自测门禁(开发侧草案) + +| | | +|---|---| +| **谁** | AI(开发完成后触发) | +| **做什么** | 找与**需求同标题**的测试计划 → 按用例自测 → 不过则自动提缺陷(标题含开发任务编号)并尝试修复,直到本计划用例全过 | +| **需求状态** | 仍为 **开发中**(未全过前) | +| **为什么** | 在正式提测前先挡住明显问题(属草案,规则可再收紧) | + +--- + +### 步骤 11|开发任务完成 + +| | | +|---|---| +| **谁** | 系统 / AI(用例全过之后) | +| **做什么** | 该条【开发】标为**已完成** | +| **需求状态** | → **开发完成**(若有多条开发,建议等**全部**完成再改,避免过早) | +| **为什么** | 标记「代码+门禁」这一段结束 | + +--- + +### 步骤 12|完成开发 · 正式提测 + +| | | +|---|---| +| **谁** | 开发人员(自测也完成) | +| **做什么** | 口令「完成开发」→ 自动建 **【测试】**(标题=`【测试】`+任务名);子项挂【交付】;建议再关联需求;指派测试(如谢佳伟) | +| **需求状态** | → **待测试** ★ | +| **口令例** | `完成开发:ONEOS-d` | +| **为什么** | 开发交棒给测试;测试侧开始接单 | + +--- + +## 阶段 C|测试(步骤 13~15) + +### 步骤 13|接测试任务 + +| | | +|---|---| +| **谁** | 测试(谢佳伟等) | +| **做什么** | 打开【测试】任务;确认关联的需求与交付;准备/核对测试计划与用例包(建议按**需求编号**分包,比「同标题」更稳) | +| **需求状态** | **待测试** → 开始执行后改为 **测试中** | +| **为什么** | 正式测试以计划用例为准,不靠口头「测过了」 | + +--- + +### 步骤 14|执行用例 · 缺陷回流 + +| | | +|---|---| +| **谁** | 测试执行;失败时回流开发修 | +| **做什么** | 按用例执行;失败建缺陷 → 开发修复 → 再测 | +| **需求状态** | **测试中** | +| **为什么** | 质量关口;未绿不能进发版 | + +--- + +### 步骤 15|测试完成 + +| | | +|---|---| +| **谁** | 测试 / AI 辅助判定 | +| **做什么** | **本需求**相关用例全绿 →【测试】收口;通知发版对接人 | +| **需求状态** | → **测试完成** ★ | +| **注意** | 只卡本需求,不卡同迭代里别的需求 | +| **为什么** | 发版范围以「测试完成」的需求为准 | + +--- + +## 阶段 D|发版(步骤 16~17) + +### 步骤 16|建发版任务 + +| | | +|---|---| +| **谁** | 发版对接人(时生亮)· 手工或 AI | +| **做什么** | 建 **【发版】**;关联**迭代** + 本期内要上的需求(建议再挂交付/测试);汇总更新说明 | +| **需求状态** | 进入发布流程时 → **发布中** | +| **为什么** | 一次发版可能含多条需求;要有明确范围清单 | + +--- + +### 步骤 17|发布结果回写 + +| | | +|---|---| +| **谁** | 发版对接人 / 流水线 | +| **做什么** | 执行发布 | +| **结果** | 成功 → **发布完成**;失败 → **发布失败**(可重试再进发布中) | +| **为什么** | 成败要回写云效,避免「以为发了其实没发」 | + +--- + +## 阶段 E|收口(步骤 18) + +### 步骤 18|业务验收 · 关闭 + +| | | +|---|---| +| **谁** | 产品 | +| **做什么** | 业务验收通过后,关闭需求,并收口【交付】 | +| **需求状态** | → **已关闭** | +| **为什么** | 全链路结束;树上任务与需求一致收口 | + +--- + +## 状态主链(串起来看) + +```text +待处理 → 已确认 → 分析中 → 设计中 → 设计完成 + → 待开发 ← 产品交棒给何斐 + → 开发中 → 开发完成 + → 待测试 → 测试中 → 测试完成 + → 发布中 → 发布完成(或发布失败可重试) + → 已关闭 ← 产品验收关单 +``` + +--- + +## 四个交棒口(最重要) + +| 交棒 | 交出人 | 接棒人 | 交出物 | 状态落点 | +|---|---|---|---|---| +| ① 产品→开发 | 产品 | 何斐 | 需求号 + 交付号 | 待开发 | +| ② 开发→测试 | 开发人 | 测试 | 【测试】任务已建好 | 待测试 | +| ③ 测试→发版 | 测试 | 发版 | 本需求用例全绿 | 测试完成 | +| ④ 发版→产品 | 发版 | 产品 | 发布完成 / 更新说明 | 发布完成 → 已关闭 | + +--- + +## 谁在什么时候动(对照表) + +| 步骤 | 产品 | 开发主管 | 开发人 | 测试 | 发版 | +|---|---|---|---|---|---| +| 1~2 记需求/确认 | ● | | | | | +| 3~5 分析设计 | ● | | | | | +| 6 交棒 | ● | 接 | | | | +| 7 分配任务 | | ● | | | | +| 8~9 开始写码 | | | ● | | | +| 10~11 AI门禁/完成任务 | | | ●/AI | | | +| 12 提测 | | | ● | 接 | | +| 13~15 测试 | | | 修缺陷 | ● | | +| 16~17 发版 | | | | | ● | +| 18 关闭 | ● | | | | | + +--- + +## 相关文档 + +| 文档 | 用途 | +|---|---| +| `docs-四角色泳道流程图.md` | 泳道图总览 | +| `docs-产品部流转流程图.md` | 产品 1~6 细规 | +| `docs-开发侧流转草案.md` | 开发 7~12 细规与待拍板 | +| `yunxiao-pipeline-handbook` | 云效规则与配置 | diff --git a/.claude/skills/YunxiaoPM/docs-四角色泳道流程图.md b/.claude/skills/YunxiaoPM/docs-四角色泳道流程图.md new file mode 100644 index 0000000..1dcbaac --- /dev/null +++ b/.claude/skills/YunxiaoPM/docs-四角色泳道流程图.md @@ -0,0 +1,181 @@ +# 四角色泳道图:产品 · 开发 · 测试 · 发版 + +> 用途:一张图看清从建需求到发版关闭,四条泳道各自做什么、在哪交棒。 +> 依据:产品部流转 · 开发侧草案 · 生产线手册(测试/发版) +> 日期:2026-07-24 +> 说明:开发泳道含「AI 用例门禁」草案;测试泳道为正式提测后的人/计划执行。 + +--- + +## 1. 泳道总览(推荐主图) + +```mermaid +flowchart TB + subgraph PM["泳道① 产品"] + direction TB + P0["建需求 · 待处理
只写 AutoRDO"] + P1["受理确认 · 已确认"] + P2["分析中
建【交付】+【分析】"] + P3["设计中
建【设计】"] + P4["设计完成
AutoPRD 灌交付"] + P5["交棒 · 待开发
交付负责人→何斐"] + P6["业务验收后
关闭需求+交付"] + P0 --> P1 --> P2 --> P3 --> P4 --> P5 + end + + subgraph DEV["泳道② 开发"] + direction TB + D1["何斐:分配任务
建【开发】SUB→交付"] + D2["开发人:开始开发
任务处理中 · 需求开发中"] + D3["写代码 / 提 MR"] + D4["AI 按同标题测试计划自测
不过→提缺陷并修"] + D5["开发任务已完成
需求→开发完成"] + D6["完成开发(含自测)
建【测试】· 需求待测试"] + D1 --> D2 --> D3 --> D4 --> D5 --> D6 + end + + subgraph QA["泳道③ 测试"] + direction TB + T1["接【测试】任务
需求=待测试"] + T2["建/挂测试计划与用例
(建议按需求编号分包)"] + T3["测试中 · 执行用例"] + T4["缺陷回流开发修复"] + T5["本需求用例全绿
需求→测试完成"] + T1 --> T2 --> T3 + T3 -.->|失败| T4 + T4 -.->|再测| T3 + T3 -->|通过| T5 + end + + subgraph REL["泳道④ 发版"] + direction TB + R1["建【发版】任务
挂迭代+范围内需求"] + R2["汇总更新说明"] + R3["发布中"] + R4{"发布结果"} + R5["发布完成"] + R6["发布失败 · 可重试"] + R1 --> R2 --> R3 --> R4 + R4 -->|成功| R5 + R4 -->|失败| R6 + R6 -.-> R3 + end + + P5 -->|"交棒契约
需求编号+交付编号"| D1 + D6 -->|"提测
【测试】已挂交付"| T1 + T5 -->|"交棒发版"| R1 + R5 -->|"业务可验收"| P6 +``` + +--- + +## 2. 横向时间轴版(谁在哪一阶段动) + +从左到右是需求状态主链;上下是四个角色。空格表示该角色此阶段无主动动作。 + +```mermaid +flowchart LR + subgraph S_PM["产品阶段"] + direction TB + sp0["待处理→已确认"] + sp1["分析/设计/设计完成"] + sp2["待开发交棒"] + sp3["已关闭"] + end + + subgraph S_DEV["开发阶段"] + direction TB + sd0["拆【开发】"] + sd1["开发中"] + sd2["AI门禁→开发完成"] + sd3["建【测试】→待测试"] + end + + subgraph S_QA["测试阶段"] + direction TB + sq0["待测试"] + sq1["测试中"] + sq2["测试完成"] + end + + subgraph S_REL["发版阶段"] + direction TB + sr0["建【发版】"] + sr1["发布中"] + sr2["完成/失败"] + end + + sp0 --> sp1 --> sp2 + sp2 --> sd0 --> sd1 --> sd2 --> sd3 + sd3 --> sq0 --> sq1 --> sq2 + sq2 --> sr0 --> sr1 --> sr2 + sr2 --> sp3 +``` + +--- + +## 3. 交接点一览 + +| # | 从 → 到 | 交出什么 | 需求状态落点 | +|---|---|---|---| +| 1 | 产品 → 开发 | 需求编号 +【交付】编号;交付负责人=何斐 | **待开发** | +| 2 | 开发 → 测试 | 【测试】任务(子项挂交付);用例计划可先备好 | **待测试** | +| 3 | 测试 → 发版 | 本需求用例全绿;范围内需求清单 | **测试完成** | +| 4 | 发版 → 产品 | 发布完成证据 / 更新说明 | **发布完成** → 产品关单 **已关闭** | + +--- + +## 4. 各泳道职责边界(一句话) + +| 角色 | 管什么 | 不管什么 | +|---|---|---| +| **产品** | 建需求、【交付】【分析】【设计】、AutoPRD、交棒待开发、验收关闭 | 不建【开发】【测试】【发版】、不开分支 | +| **开发** | 拆【开发】、写码、AI 自测门禁、建【测试】提测 | 不改写产品 AutoRDO;不建第二套【交付】 | +| **测试** | 接【测试】、跑计划用例、缺陷回流、判测试完成 | 不擅自改需求状态到开发中;不建【发版】(可催) | +| **发版** | 建【发版】、挂迭代与范围、发布与回写成败 | 不替代测试判绿;不关闭需求(交给产品) | + +--- + +## 5. 云效工作项树(跨角色) + +```mermaid +flowchart TB + REQ["需求 · 状态=阶段真相"] + DEL["【交付】ASSOCIATED→需求"] + AN["【分析】"] + DE["【设计】"] + DV["【开发】"] + TE["【测试】"] + RE["【发版】ASSOCIATED→迭代/需求"] + + DEL --> REQ + AN -->|TASK_SUB| DEL + DE -->|TASK_SUB| DEL + DV -->|TASK_SUB| DEL + TE -->|TASK_SUB| DEL + DV -.->|ASSOCIATED| REQ + TE -.->|ASSOCIATED| REQ + RE -.->|关联范围| REQ +``` + +--- + +## 6. 典型负责人(手册约定,可按项目改) + +| 泳道 | 典型对接人 | +|---|---| +| 产品 | 王冕 · YunxiaoPMapp / AI | +| 开发 | 何斐(拆任务)→ 王雨昊 / 李振等(执行) | +| 测试 | 谢佳伟 | +| 发版 | 时生亮 | + +--- + +## 相关文档 + +| 文档 | 说明 | +|---|---| +| `docs-全流程分步说明.md` | **整条链路逐步说明(推荐先读)** | +| `docs-产品部流转流程图.md` | 产品 0→5 细图 | +| `docs-开发侧流转草案.md` | 开发 D1–D5 细图 | +| `yunxiao-pipeline-handbook` | 全链路配置与规则 | diff --git a/.claude/skills/YunxiaoPM/docs-开发侧流转草案.md b/.claude/skills/YunxiaoPM/docs-开发侧流转草案.md new file mode 100644 index 0000000..27e99f6 --- /dev/null +++ b/.claude/skills/YunxiaoPM/docs-开发侧流转草案.md @@ -0,0 +1,230 @@ +# 开发侧流转草案(产品交棒之后) + +> 来源:产品经理口述规则整理(2026-07-24) +> 上游终点:产品部 `docs-产品部流转流程图.md` → 需求=**待开发**,【交付】负责人=**何斐** +> 本文件目标:把「何斐拆任务 → 开发执行 → AI 用例门禁 → 提测」画清,供后续 **YunxiaoDevapp** Skill 定稿 +> 状态:**草案**(文末有待拍板项) + +--- + +## 1. 总览流程图 + +```mermaid +flowchart TB + subgraph handoff [产品已交棒] + Ready["需求=待开发\n【交付】负责人=何斐"] + end + + subgraph d1 [D1 分配开发] + Assign["何斐:分配任务 + 交付任务编号"] + CreateDev["自动建【开发】+需求标题\n描述人工补\n指定开发人\n写计划时间"] + Link["子项 TASK_SUB→交付\n关联项 ASSOCIATED→需求"] + StReady["需求保持/确认为 待开发"] + end + + subgraph d2 [D2 开始开发] + Start["开发人:开始开发 + 开发任务编号"] + DevDoing["开发任务→处理中"] + ReqDoing["需求→开发中"] + end + + subgraph d3 [D3 AI 用例门禁] + CodeDone["代码开发完成触发"] + FindPlan["按需求同标题找测试计划"] + AITest["AI 按用例自测"] + Fail["未通过→自动提缺陷\n标题含开发任务编号\nAI 写标题描述"] + Fix["自动修复→再测"] + PassGate["用例全部通过"] + end + + subgraph d4 [D4 开发任务完成] + DevDone["该条【开发】→已完成"] + ReqDevDone["需求→开发完成"] + end + + subgraph d5 [D5 完成开发·提测] + Finish["开发人:完成开发\n含自测完成"] + CreateTest["自动建【测试】+任务名\n子项→交付"] + PendingTest["需求→待测试"] + end + + Ready --> Assign --> CreateDev --> Link --> StReady + StReady --> Start --> DevDoing --> ReqDoing + ReqDoing --> CodeDone --> FindPlan --> AITest + AITest -->|未通过| Fail --> Fix --> AITest + AITest -->|全通过| PassGate --> DevDone --> ReqDevDone + ReqDevDone --> Finish --> CreateTest --> PendingTest +``` + +--- + +## 2. 分步说明(按你的 5 条) + +### D1|何斐分配任务 + +| 项 | 规则 | +|---|---| +| 触发 | Skill 口令:`分配任务` + **交付任务编号** | +| 新建 | 【开发】任务;标题 = `【开发】` + **需求标题** | +| 描述 | **人工添加**(Skill 不代写正文,或只留空模板) | +| 负责人 | 口令指定开发人员 | +| 计划时间 | **自动创建**计划开始/计划完成(算法待定:阶段日历工时 or 人力预估,见待拍板) | +| 关联 | **子项** `TASK_SUB` →【交付】;**关联项** `ASSOCIATED` → 需求 | +| 需求状态 | 改为 / 确认为 **待开发**(见待拍板:与产品交棒是否重复) | + +### D2|开始开发 + +| 项 | 规则 | +|---|---| +| 触发 | `开始开发` + **开发任务编号** | +| 开发任务 | → **处理中** | +| 需求 | → **开发中**(有 ≥1 条开发进入处理中即可,或本条触发即改,见待拍板) | + +### D3|代码完成后 · AI 按测试计划自测 + +| 项 | 规则 | +|---|---| +| 触发 | 「代码开发完成」(MR 合并 / 口令 / Webhook,触发源待定) | +| 找计划 | **自动寻找与需求同标题的测试计划** | +| 执行 | AI 按该计划中用例自行测试 | +| 失败 | 自动提缺陷:标题含 **开发任务编号** + AI 生成标题/描述;再自动修复,循环直到用例全过 | + +### D4|用例全通过 + +| 项 | 规则 | +|---|---| +| 该条【开发】 | → **已完成** | +| 需求 | → **开发完成** | + +### D5|开发人「完成开发」(自测也完成) + +| 项 | 规则 | +|---|---| +| 触发 | `完成开发` +(建议带)开发任务编号 | +| 新建 | 【测试】任务;标题 = `【测试】` + 任务名称(建议=需求标题) | +| 关联 | 子项 →【交付】(建议同时 ASSOCIATED→需求) | +| 需求 | → **待测试** | + +--- + +## 3. 与产品部的衔接 + +```mermaid +flowchart LR + PM["产品 YunxiaoPMapp\n待开发 + 交付→何斐"] --> DevSkill["开发 Skill 草案\n本文 D1–D5"] + DevSkill --> QA["测试执行人/Skill\n待测试之后"] +``` + +| 上游已具备 | 开发侧依赖 | +|---|---| +| 需求编号 +【交付】编号 | D1 入口只认编号,不按标题拆第二套交付 | +| 交付 ASSOCIATED→需求 | D1 开发任务再 ASSOCIATED→需求、SUB→交付 | +| 产品不建【开发】【测试】 | 仅开发 Skill 建这两类 | + +--- + +## 4. 待拍板(建议你先定这几条) + +### 4.1 状态顺序是否自洽 + +你现在的顺序是: + +```text +D3 AI 用例全过 → 开发任务已完成 + 需求「开发完成」 +D5 完成开发 → 建【测试】+ 需求「待测试」 +``` + +与现有手册常见顺序对比: + +```text +手册常见:开发人完成自测 → 待测试 →(测试执行)→ 测试完成 +你草案: AI 先跑测试计划用例过关 → 开发完成 → 人再点完成开发 → 待测试 +``` + +需要确认: +1. D3 的「测试计划」是 **开发自测门禁**,正式【测试】任务仍留给谢佳伟? +2. 还是 D3 已等同正式测试,那 D5 再建【测试】是否重复? + +### 4.2 「需求→待开发」写在 D1 + +产品交棒时需求**已经是待开发**。D1 再改一次通常多余。建议改为: +- D1 **校验**需求已是待开发;若不是则停下或只允许从设计完成快轨补交棒 +- 有开发任务进入处理中时,需求才变为 **开发中**(即你的 D2) + +### 4.3 「与需求同标题的测试计划」 + +标题易改、易撞名。更稳的是: +- 测试计划关联 **需求编号 / 迭代**,或 +- 用例包命名 `CP-{需求编号}-…` + +否则同名需求/改标题会导致找错计划。 + +### 4.4 AI 自动提缺陷并自动修复 + +风险高:无上限循环、误报、改回旧逻辑。建议门禁: +- 最多 N 轮;失败转人工 +- 缺陷必须关联 **开发任务编号 + 需求编号 + 用例 ID** +- 与「防回归」清单联动,禁止静默改无关模块 + +### 4.5 多条【开发】时需求状态 + +| 场景 | 建议 | +|---|---| +| 任一条开始开发 | 需求 → 开发中 | +| 需求 → 开发完成 | **全部**未取消【开发】均为已完成(与手册 Y21 一致) | +| 需求 → 待测试 | 全部【开发】完成后,由末位「完成开发」或系统自动建【测试】 | + +你原文 D4 写「该条」通过就把需求改成开发完成——多开发人并行时会过早。建议改成「全员开发任务完成」。 + +### 4.6 关联写法(与产品侧一致) + +同一 create 只能带一条 `createWorkitemRelationInfo`: +- 优先保证【开发】/【测试】**子项挂在交付**(交付详情「子项」可见) +- ASSOCIATED→需求若同 create 互斥,需约定:先 SUB,再补 ASSOCIATED,或 OpenAPI 双挂 + +--- + +## 5. 建议口令面(草案) + +```text +分配任务:交付任务=ONEOS-a;开发人=张三,李四;计划开始=…;计划完成=… +开始开发:开发任务=ONEOS-d +代码完成:开发任务=ONEOS-d → 触发 AI 用例门禁(或 Webhook) +完成开发:开发任务=ONEOS-d → 建【测试】;需求→待测试 +``` + +--- + +## 6. 推荐定稿后的状态主链(供你勾选) + +**方案 A(贴近你原文,AI 测作开发门禁)** + +```text +待开发 → 开发中 →(AI 用例门禁)→ 开发完成 → 待测试 → 测试中 → 测试完成 → … +``` + +**方案 B(贴近现有生产线手册)** + +```text +待开发 → 开发中 → 开发完成(人自测+MR)→ 待测试 → 测试中(人/AI 跑计划)→ 测试完成 → … +``` + +AI 跑测试计划放在 **待测试之后**,与【测试】任务负责人(如谢佳伟)对齐。 + +--- + +## 7. 下一步 + +1. 你拍板:**方案 A 或 B**,以及多【开发】时「开发完成 / 待测试」的聚合规则 +2. 我按定稿输出 `YunxiaoDevapp` 的 `SKILL.md` 骨架 + 与产品交棒契约对齐段落 +3. 再补一版「仅开发侧」验收清单(10 条以内) + +--- + +## 相关文档 + +| 文档 | 路径 | +|---|---| +| 产品部流转 | `docs-产品部流转流程图.md` | +| 产品↔开发契约 | `docs-YunxiaoPMapp-实现原理-开发Skill对接.md` | +| 全链路手册 | `src/prototypes/yunxiao-pipeline-handbook/` | diff --git a/.claude/skills/YunxiaoPM/fast-track.md b/.claude/skills/YunxiaoPM/fast-track.md new file mode 100644 index 0000000..4ae6a81 --- /dev/null +++ b/.claude/skills/YunxiaoPM/fast-track.md @@ -0,0 +1,47 @@ +# 无单快轨到待开发 + +适用:口令明确「快轨」或「推进至待开发」且当前需求尚无标准分析路径任务(或用户确认跳过分析)。 + +## 动作表 + +| 对象 | 动作 | +|---|---| +| 需求 | 状态 → **待开发**;**预计工时=2**、**实际工时=2**(覆盖同日 8h 日历工时默认);标签按口令;更新编号区块 | +| 【交付】 | 无则新建;ASSOCIATED→需求(勿用 PARENT);负责人→何斐;**计划开始=创建当日**(`79`);**不写**计划完成;**描述双路径**(见下);**标签与需求相同**;更新编号区块 | +| 【设计】 | **必须新建**;**TASK_SUB→交付**(交付「子项」须可见);**描述=复制需求当前描述正文**;计划开始/完成均=当日(create 后 `field/value` 写 `79`+`80`);任务→完成态;**标签与需求相同**;建后再尝试 **ASSOCIATED→原始需求**(Cookie 常失败,见 live-api;失败须标红,**子项优先于关联项**);更新编号区块 | +| 【分析】 | **禁止创建** | + +## 交付描述双路径(A1) + +| 口令/材料 | 【交付】描述 | +|---|---| +| **手工描述**(无指定原型) | 同步需求当前手工/AutoRDO 正文(与设计一致可复制 document) | +| **指定原型页面** | 调用 `$oneos-autoprd`,从原型生成 AutoPRD「产品说明」写入【交付】 | +| 既无手工可同步正文、又无原型 | 才允许占位 `等待设计任务完成后自动填入`,Plan 勾选交棒占位风险,回报首行标红 | + +有手工或原型时**禁止**交棒占位。 + +## AutoPRD + +- 快轨默认可不跑 AutoPRD;**例外**:口令指定原型 → 交付走 AutoPRD 路径(上表)。 +- 口令同时「设计完成 + 原型」时仍按步骤 4 灌需求产品说明段。 + +## 查重 + +只认任务编号。已有分析单不得因「快轨」被删除。 + +## 回报必含 + +- 需求 / 交付 / 设计编号 +- 「快轨:未建分析;设计已同步需求描述并当日收口」 +- 交付描述来源:手工同步 / AutoPRD / 占位(若占位则首行标红) +- 需求预计工时=2、实际工时=2 +- 设计关联项含需求编号;交付子项含设计编号 + +## 与标准 / 编号直推对比 + +```text +标准:分析中→交付+分析 → 设计中→设计+收口分析 → 设计完成→收口设计 → 待开发交棒 +快轨(无既有阶段单):→ 待开发:新建交付+设计+交棒何斐(无分析;设计当日收口;需求工时 2+2) +编号直推:已有分析/设计编号 → 收口空计划完成 → 待开发+交付交棒(不新建冗余单) +``` diff --git a/.claude/skills/YunxiaoPM/live-api.md b/.claude/skills/YunxiaoPM/live-api.md new file mode 100644 index 0000000..f2bab69 --- /dev/null +++ b/.claude/skills/YunxiaoPM/live-api.md @@ -0,0 +1,242 @@ +# 云效实写 API(YunxiaoPMapp 已验证) + +`verified_at`: 2026-07-25 · **项目须门禁 PJ 点选**(见 [project-selection.md](project-selection.md));历史验证样本项目为 `01_ONEOS` / 原「统一运营管理平台」(`last_selected.spaceIdentifier` 见 `assets/runtime-ids.json`,禁止未点选即使用)。 + +本文件只记**已跑通**的写法;禁止再盲试 `updateStatus` / 错误 `updateFieldValue` POST。 + +## 认证 + +- Cookie:Chrome 域 `.aliyun.com` / `devops.aliyun.com`(`browser_cookie3` 或 Playwright storage) +- Header:`x-xsrf-token` = cookie `XSRF-TOKEN`(URL 解码后) +- `Origin` / `Referer`:`https://devops.aliyun.com` + +## 建单 + +`POST|PUT /projex/api/workitem/workitem?_input_charset=utf-8` + +创建响应 `result.identifier` / `serialNumber` 即编号真相;**禁止按标题查重**。 + +## 改负责人(已通) + +```http +PATCH /projex/api/workitem/workitem/{id}?_input_charset=utf-8 +{"propertyKey":"assignedTo","propertyValue":"","operateType":"COVER"} +``` + +交棒:【交付】`propertyValue` = 何斐 ID。 + +## 打标签(已通) + +```http +PATCH /projex/api/workitem/workitem/{id}?_input_charset=utf-8 +{"workitemIdentifier":"{id}","propertyKey":"tag","propertyValue":"[,]","operateType":"COVER"} +``` + +## 改状态(已通 · 唯一推荐) + +```http +POST /projex/api/workitem/workitem/{id}/status/transit?_input_charset=utf-8 +{"fromStatus":"<当前status.identifier>","toStatus":"<目标status.identifier>"} +``` + +成功:`code=200` 且 `result=true`。失败时 `errorMsg` 含「不能流转」。 + +### 需求状态 ID(本项目) + +| 显示名 | identifier | +|---|---| +| 待处理 | `100005` | +| 已确认 | `32` | +| 分析中 | `154395` | +| 设计中 | `156603` | +| 设计完成 | `307012` | +| 待开发 | `1582fc929d429111b925309493` | + +### 任务状态 ID + +| 显示名 | identifier | +|---|---| +| 待处理 | `100005` | +| 已完成 | `100014` | + +### 极速交棒跳转(工作流允许) + +从「待处理」菜单可见直达「设计完成」;推荐最少跳: + +```text +待处理 → 设计完成 → 待开发 +``` + +标准路径若需看板留痕,可走完整链:已确认→分析中→设计中→设计完成→待开发(仍用本 API,勿开 UI)。 + +### 禁止(已证伪) + +| 写法 | 结果 | +|---|---| +| `PATCH …/updateStatus` + `statusIdentifier` | `400 不能为空` | +| `PATCH …/{id}` + `propertyKey=status` | `property not found` | +| Playwright 点左侧/列表上的状态色块(`x<1100`) | 假成功、状态不落库 | + +仅当 `status/transit` 不可用时,才用 UI:右侧详情状态钮(`getBoundingClientRect().x > 1100`)+ `.next-menu-item`。 + +## 计划开始/完成 · 提交部门/人 · 预计工时(2026-07-27 修订) + +**通用字段写入(已通):** + +```http +POST /projex/api/workitem/workitem/field/value/{workitemId}?_input_charset=utf-8 +Content-Type: application/x-www-form-urlencoded + +fieldValueList=[{"fieldIdentifier":"79","value":"2026-07-27 12:00:00"},{"fieldIdentifier":"3132597a9718d1c282b7ba5a0c","value":"业务管理部"},{"fieldIdentifier":"9e01269e96f91fbb97d36bf5b3","value":"何苗苗"}] +``` + +| 字段 | fieldIdentifier | value | +|---|---|---| +| 计划开始 | `79` | `YYYY-MM-DD HH:mm:ss`(推荐正午)或 epoch ms 字符串 | +| 计划完成 | `80` | 同上 | +| 提交部门 | `3132597a9718d1c282b7ba5a0c` | 纯文本 | +| 提交人 | `9e01269e96f91fbb97d36bf5b3` | 纯文本 | + +**预计工时 `101586`:禁止直接改字段**(报「不可直接修改」)。须登记: + +```http +POST /projex/api/workitem/workitem/time/estimate?_input_charset=utf-8 +{"workitemIdentifier":"","spentTime":8,"type":"develop","description":"阶段日历工时","recordUserIdentifier":"","forCreate":false,"containsRestDay":false} +``` + +删除多余预估:`DELETE /projex/api/workitem/workitem/time/estimate/{workitemId}/{estimateId}` +列表:`GET …/time/estimate/list?workitemIdentifier=` + +旧写法 `PATCH …/updateWorkitemFieldValue` 对上述自定义字段常 `400 不能为空`,勿再优先使用。 + +## 父子 / 子项 / 关联项(已通 · 2026-07-23 修订 · 子项优先) + +### 关联项(【交付】强制 · ASSOCIATED) + +任务详情「关联项」只认 `ASSOCIATED`。**仅【交付】**建单时 `createWorkitemRelationInfo` 必须指向**需求**: + +```json +{ + "createWorkitemRelationInfo": { + "relatedWorkitemIdentifier": "<需求id>", + "relatedToRelationIdentifier": "ASSOCIATED" + } +} +``` + +校验: + +```http +GET /projex/api/workitem/v2/workitem/{交付id}/relation/workitem/list/by-relation-category?category=ASSOCIATED&isForward=true +``` + +`result` 含该需求即通过。 + +### 禁止(关联项) + +| 写法 | 结果 | +|---|---| +| `relatedToRelationIdentifier=PARENT` 把交付挂需求 | 详情可能有 parent,**关联项仍为空** | +| 分析/设计只用 `ASSOCIATED→需求` + `parentIdentifier` | 关联项可能有,**交付子项仍为空**(ONEOS-246/247) | +| 建后再 `POST …/relation/record` 补关系 | Cookie 路径下常报「不能关联相同的工作项」 | +| `createWorkitemRelationList` | 不落 ASSOCIATED | + +### 子项(【分析】/【设计】强制 · TASK_SUB) + +「子项」页读 `PARENT_SUB` / `TASK_SUB`,分析/设计**必须**: + +```json +{ + "parent": "<交付id>", + "parentIdentifier": "<交付id>", + "createWorkitemRelationInfo": { + "relatedWorkitemIdentifier": "<交付id>", + "relatedToRelationIdentifier": "TASK_SUB" + } +} +``` + +同一 create 只能带一条 `createWorkitemRelationInfo`。 +`ASSOCIATED→需求` 与 `TASK_SUB→交付` **不能同时写**。 + +**产品优先级:交付「子项」tab > 阶段任务「关联项」。** +交付本身仍必须 `ASSOCIATED→需求`。标准路径下分析/设计的「关联项」允许为空。 + +**无单快轨例外(设计双挂):** create 用 `TASK_SUB→交付` 后,须再补 **ASSOCIATED→原始需求**,使设计详情「关联项」可见需求。 + +```http +POST /projex/api/workitem/workitem/{设计id}/relation/record?_input_charset=utf-8 +{"relationIdentifier":"ASSOCIATED","toWorkitemIdentifier":"<需求id>"} +``` + +**已证伪(Cookie · 2026-07-27):** 凡工作项**已创建**后再 `relation/record` 补挂(含 ASSOCIATED / TASK_SUB),常报「不能关联相同的工作项」;与是否已有父项无关。`createWorkitemRelationList` 亦不落 ASSOCIATED。 +**可行路径:** + +| 目标 | 做法 | +|---|---| +| 交付「子项」可见设计(优先) | create 带 `TASK_SUB→交付` | +| 设计「关联项」可见需求 | create 带 `ASSOCIATED→需求`(与上互斥,同 create 只能一条) | +| 双挂 | 需个人 `x-yunxiao-token` OpenAPI;Cookie 路径**不得**声称成功 | + +产品默认:**子项优先**;关联项失败须在回报中标红并列出缺项。 + +校验子项: + +```http +GET /projex/api/workitem/v2/workitem/{交付id}/relation/workitem/list/by-relation-category?category=PARENT_SUB&isForward=true +``` + +结果须含对应分析/设计 identifier。 + +校验设计关联项: + +```http +GET /projex/api/workitem/v2/workitem/{设计id}/relation/workitem/list/by-relation-category?category=ASSOCIATED&isForward=true +``` + +`result` 须含原始需求 identifier。 + +### 无单快轨字段默认(2026-07-27) + +| 对象 | 规则 | +|---|---| +| 【设计】描述 | 复制需求 document HTML | +| 【设计】`79`/`80` | 当日 `12:00:00` / `23:59:59`,create 后 `field/value`(勿 create 同时带 79+80) | +| 【交付】描述 | 手工同步需求正文,或原型→AutoPRD;禁止无故占位 | +| 【交付】`79` | 创建当日;不写 `80` | +| 【交付】/【设计】标签 | 与需求相同,`PATCH propertyKey=tag` | +| 需求预计工时 | `time/estimate` **spentTime=2**(先删多余预估) | +| 需求实际工时 | `POST …/workitem/time`,body 用 **`actualTime`**(非 spentTime)+ `gmtStart`/`gmtEnd` epoch ms 字符串;见 `runtime-ids.json` `fields.actual_hours` | +| 描述更新 | `PATCH …/workitem/{id}/document`,`{"content":"","formatType":"RICHTEXT"}` | + +## 迭代挂接(已通 · 2026-07-27 · 只挂交付) + +创建迭代:`POST /projex/api/workspace/sprint`(必填 `staffIds`;可写 `capacityHours`)。 + +挂【交付】到迭代: + +```http +PATCH /projex/api/workitem/workitem/{交付id}?_input_charset=utf-8 +{"workitemIdentifier":"{交付id}","propertyKey":"sprint","propertyValue":"{sprintId}","operateType":"COVER"} +``` + +清空误挂(如需求):`propertyValue:""` + `operateType:"COVER"`。 + +**校验**必须读 `/extra`(详情主接口常不含 sprint 字段,禁止据此判失败): + +```http +GET /projex/api/workitem/workitem/{id}/extra?_input_charset=utf-8 +→ result.sprint[].identifier / name +``` + +产品规则:**只挂【交付】**;需求 / 分析 / 设计默认不挂(除非口令显式)。 + +### 极速建单注意 + +1. Cookie 只刷一次;全程纯 HTTP,默认**不开浏览器**。 +2. 交付建完后,【分析】与【设计】**并行**创建(均 TASK_SUB→交付);标准/快轨两树可并行。 +3. 状态用 `transit` + **本地追踪 fromStatus**(禁止每次 GET);负责人在交棒场景下**创建时即何斐**。 +4. 建单 `fieldValueList` 可带计划开始 `79`;**不要**在 create 同时写 `79+80`(同日会 400)。 +5. 标签必须 PATCH(create 带 tag 不落库);可与建子任务重叠;快轨交付/设计须与需求同标签。 +6. `requests.Session` keep-alive;**禁止**对共享 opener 加全局锁。 +7. 脚本入口:`scripts/live_create_fast.py`(v5:快轨描述/计划/标签/工时 2+2/设计 ASSOCIATED 补挂)。 diff --git a/.claude/skills/YunxiaoPM/live_create_fast.py b/.claude/skills/YunxiaoPM/live_create_fast.py new file mode 100644 index 0000000..4e5be22 --- /dev/null +++ b/.claude/skills/YunxiaoPM/live_create_fast.py @@ -0,0 +1,660 @@ +#!/usr/bin/env python3 +"""YunxiaoPM 极速真实建单 v5:快轨描述/计划/标签/工时 2+2/设计 ASSOCIATED 补挂。""" +from __future__ import annotations + +import json +import time +import urllib.parse +from concurrent.futures import ThreadPoolExecutor, as_completed +from datetime import datetime, timedelta, timezone +from pathlib import Path +from typing import Any + +try: + import browser_cookie3 +except ImportError: # pragma: no cover + browser_cookie3 = None + +import requests + +ROOT = Path(__file__).resolve().parents[1] +RUNTIME = json.loads((ROOT / "assets" / "runtime-ids.json").read_text()) +STATUS = RUNTIME["status"]["req"] +TASK_STATUS = RUNTIME["status"]["task"] +FAST = RUNTIME.get("fields", {}).get("fast_track") or {} + +SPACE = ( + RUNTIME.get("project", {}).get("spaceIdentifier") + or (RUNTIME.get("project", {}).get("last_selected") or {}).get("spaceIdentifier") +) +if not SPACE: + raise RuntimeError( + "未选定项目 spaceIdentifier:须先走门禁 PJ 点选,或设置 runtime project.last_selected" + ) +REQ_TYPE = RUNTIME["workitem_types"]["product_req"]["identifier"] +TASK_TYPE = RUNTIME["workitem_types"]["task"]["identifier"] +HEFEI = RUNTIME["people"]["hefei"]["identifier"] +WANG = RUNTIME["people"].get("wangmian", {}).get("identifier", "6811df000601d2fea60144a9") +PRI = RUNTIME["priority"]["中"] +TAG_FAULT = RUNTIME.get("tags", {}).get("故障管理", "ceb526a7343995577645317e9a") +PLACEHOLDER = RUNTIME["delivery_placeholder"] +PROTO = "https://prototype.lnoneos.com/vehicle-fault-handling/index.html" +TZ = timezone(timedelta(hours=8)) +TODAY = datetime.now(TZ).strftime("%Y-%m-%d") +NOON = f"{TODAY} 12:00:00" +EOD = f"{TODAY} 23:59:59" +NOON_MS = str( + int(datetime.now(TZ).replace(hour=12, minute=0, second=0, microsecond=0).timestamp() * 1000) +) +START_MS = str( + int(datetime.now(TZ).replace(hour=9, minute=0, second=0, microsecond=0).timestamp() * 1000) +) +END_MS = str( + int(datetime.now(TZ).replace(hour=11, minute=0, second=0, microsecond=0).timestamp() * 1000) +) +FAST_EST = int(FAST.get("req_estimated_hours", 2)) +FAST_ACT = int(FAST.get("req_actual_hours", 2)) + +PENDING = STATUS["待处理"] +DESIGN_DONE = STATUS["设计完成"] +PENDING_DEV = STATUS["待开发"] +TASK_DONE = TASK_STATUS["已完成"] + +_COOKIE = "" +_XSRF = "" + + +def load_auth() -> None: + global _COOKIE, _XSRF + jar: dict[str, str] = {} + if browser_cookie3: + for domain in (".aliyun.com", "devops.aliyun.com", ".devops.aliyun.com"): + try: + for c in browser_cookie3.chrome(domain_name=domain): + jar[c.name] = c.value + except Exception: + pass + if not jar: + p = Path("/tmp/yunxiao_cookies.json") + if p.exists(): + raw = json.loads(p.read_text()) + jar = ( + raw + if isinstance(raw, dict) and "XSRF-TOKEN" in raw + else {c["name"]: c["value"] for c in raw.get("cookies", [])} + ) + _COOKIE = "; ".join(f"{k}={v}" for k, v in jar.items()) + x = jar.get("XSRF-TOKEN", "") + _XSRF = urllib.parse.unquote(x) if "%" in x else x + if not _XSRF: + raise RuntimeError("缺少 XSRF-TOKEN:请先在 Chrome 登录 devops.aliyun.com") + + +def session() -> requests.Session: + s = requests.Session() + s.headers.update( + { + "Content-Type": "application/json", + "Cookie": _COOKIE, + "x-xsrf-token": _XSRF, + "X-XSRF-TOKEN": _XSRF, + "Origin": "https://devops.aliyun.com", + "Referer": f"https://devops.aliyun.com/projex/project/{SPACE}/req", + "accept": "application/json", + "User-Agent": "YunxiaoPM-live_create_fast/5.0", + "Connection": "keep-alive", + } + ) + return s + + +def api(s: requests.Session, method: str, url: str, body: Any = None) -> dict: + r = s.request(method, url, json=body, timeout=60) + try: + return r.json() + except Exception: + r.raise_for_status() + raise + + +def create(s: requests.Session, payload: dict) -> dict: + url = "https://devops.aliyun.com/projex/api/workitem/workitem?_input_charset=utf-8" + j = api(s, "POST", url, payload) + r = j.get("result") or {} + if j.get("code") == 200 and isinstance(r, dict) and r.get("identifier"): + return r + j = api(s, "PUT", url, payload) + r = j.get("result") or {} + if j.get("code") == 200 and isinstance(r, dict) and r.get("identifier"): + return r + raise RuntimeError(f"create failed: {j}") + + +def get(s: requests.Session, wid: str) -> dict: + return api( + s, + "GET", + f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}?_input_charset=utf-8", + )["result"] + + +def apply_tag(s: requests.Session, wid: str, tag_id: str = TAG_FAULT) -> None: + j = api( + s, + "PATCH", + f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}?_input_charset=utf-8", + { + "workitemIdentifier": wid, + "propertyKey": "tag", + "propertyValue": tag_id, + "operateType": "COVER", + }, + ) + if j.get("code") != 200: + raise RuntimeError(f"tag failed {wid}: {j}") + + +def set_document(s: requests.Session, wid: str, html: str) -> None: + j = api( + s, + "PATCH", + f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}/document?_input_charset=utf-8", + {"content": html, "formatType": "RICHTEXT"}, + ) + if j.get("code") != 200 or j.get("errorMsg"): + raise RuntimeError(f"document failed {wid}: {j}") + + +def set_fields(s: requests.Session, wid: str, pairs: list[tuple[str, str]]) -> None: + data = urllib.parse.urlencode( + { + "fieldValueList": json.dumps( + [{"fieldIdentifier": k, "value": v} for k, v in pairs], ensure_ascii=False + ) + } + ) + r = s.post( + f"https://devops.aliyun.com/projex/api/workitem/workitem/field/value/{wid}?_input_charset=utf-8", + data=data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + timeout=60, + ) + j = r.json() + if j.get("code") != 200: + raise RuntimeError(f"field/value failed {wid}: {j}") + + +def set_estimate_hours(s: requests.Session, wid: str, hours: int, user: str = WANG) -> None: + est = api( + s, + "GET", + f"https://devops.aliyun.com/projex/api/workitem/workitem/time/estimate/list?workitemIdentifier={wid}", + ).get("result") or [] + for row in est: + eid = row.get("identifier") + if eid: + api( + s, + "DELETE", + f"https://devops.aliyun.com/projex/api/workitem/workitem/time/estimate/{wid}/{eid}", + ) + j = api( + s, + "POST", + "https://devops.aliyun.com/projex/api/workitem/workitem/time/estimate?_input_charset=utf-8", + { + "workitemIdentifier": wid, + "spentTime": hours, + "type": "develop", + "description": "快轨默认预计工时", + "recordUserIdentifier": user, + "forCreate": False, + "containsRestDay": False, + }, + ) + if j.get("code") != 200: + raise RuntimeError(f"estimate failed {wid}: {j}") + + +def set_actual_hours(s: requests.Session, wid: str, hours: int, user: str = WANG) -> None: + j = api( + s, + "POST", + "https://devops.aliyun.com/projex/api/workitem/workitem/time?_input_charset=utf-8", + { + "workitemIdentifier": wid, + "actualTime": hours, + "type": "develop", + "description": "快轨默认实际工时", + "recordUserIdentifier": user, + "gmtStart": START_MS, + "gmtEnd": END_MS, + }, + ) + if j.get("code") != 200 or not j.get("result"): + raise RuntimeError(f"actual time failed {wid}: {j}") + + +def try_associate_to_req(s: requests.Session, stage_id: str, req_id: str) -> bool: + """建后补 ASSOCIATED→需求。Cookie 下常失败,成功返回 True。""" + bodies = [ + {"relationIdentifier": "ASSOCIATED", "toWorkitemIdentifier": req_id}, + { + "relationIdentifier": "ASSOCIATED", + "fromWorkitemIdentifier": stage_id, + "toWorkitemIdentifier": req_id, + }, + ] + for body in bodies: + for url in ( + f"https://devops.aliyun.com/projex/api/workitem/workitem/{stage_id}/relation/record?_input_charset=utf-8", + f"https://devops.aliyun.com/projex/api/workitem/v2/workitem/{stage_id}/relation/record?_input_charset=utf-8", + ): + j = api(s, "POST", url, body) + if j.get("code") == 200 and j.get("result") not in (None, False): + if not (isinstance(j.get("result"), dict) and j["result"].get("status") in (404, 405)): + rows = list_associated(s, stage_id) + if req_id in {r.get("identifier") for r in rows}: + return True + return False + + +def transit(s: requests.Session, wid: str, from_status: str, to_status: str) -> None: + if from_status == to_status: + return + j = api( + s, + "POST", + f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}/status/transit?_input_charset=utf-8", + {"fromStatus": from_status, "toStatus": to_status}, + ) + if not (j.get("code") == 200 and j.get("result") is True): + raise RuntimeError(f"transit {wid} {from_status}->{to_status}: {j}") + + +def md_to_html(md: str) -> str: + parts = [] + for line in md.splitlines(): + if line.startswith("## "): + parts.append(f"

{line[3:]}

") + elif line.startswith("### "): + parts.append(f"

{line[4:]}

") + elif line.startswith("- "): + parts.append(f"

• {line[2:]}

") + elif line.strip(): + parts.append(f"

{line}

") + return "".join(parts) + + +def req_document_html(s: requests.Session, rid: str) -> str: + w = get(s, rid) + return ((w.get("document") or {}).get("content") or w.get("description") or "").strip() + + +def req_payload(subject: str, html: str) -> dict: + return { + "subject": subject, + "description": html, + "formatType": "RICHTEXT", + "document": {"content": html, "formatType": "RICHTEXT"}, + "spaceIdentifier": SPACE, + "space": SPACE, + "spaceType": "Project", + "workitemTypeIdentifier": REQ_TYPE, + "workitemType": REQ_TYPE, + "categoryIdentifier": "Req", + "category": "Req", + "assignedTo": WANG, + "fieldValueList": [ + {"fieldIdentifier": "priority", "value": PRI}, + {"fieldIdentifier": "assignedTo", "value": WANG}, + ], + "attachmentIdList": [], + "cloneFrom": None, + "createWorkitemRelationList": [], + } + + +def task_payload( + subject: str, + html: str, + assignee: str, + *, + plan_start: bool = True, + associated_req: str | None = None, + parent_delivery: str | None = None, +) -> dict: + fvl = [ + {"fieldIdentifier": "priority", "value": PRI}, + {"fieldIdentifier": "assignedTo", "value": assignee}, + ] + if plan_start: + fvl.append({"fieldIdentifier": "79", "value": NOON_MS}) + payload: dict[str, Any] = { + "subject": subject, + "description": html, + "formatType": "RICHTEXT", + "spaceIdentifier": SPACE, + "space": SPACE, + "spaceType": "Project", + "workitemTypeIdentifier": TASK_TYPE, + "workitemType": TASK_TYPE, + "categoryIdentifier": "Task", + "category": "Task", + "assignedTo": assignee, + "fieldValueList": fvl, + "attachmentIdList": [], + "cloneFrom": None, + } + if parent_delivery: + payload["parent"] = parent_delivery + payload["parentIdentifier"] = parent_delivery + payload["createWorkitemRelationInfo"] = { + "relatedWorkitemIdentifier": parent_delivery, + "relatedToRelationIdentifier": "TASK_SUB", + } + else: + if not associated_req: + raise ValueError("associated_req required for delivery: ASSOCIATED→需求") + payload["createWorkitemRelationInfo"] = { + "relatedWorkitemIdentifier": associated_req, + "relatedToRelationIdentifier": "ASSOCIATED", + } + return payload + + +def list_associated(s: requests.Session, wid: str) -> list[dict]: + j = api( + s, + "GET", + f"https://devops.aliyun.com/projex/api/workitem/v2/workitem/{wid}/relation/workitem/list/by-relation-category?category=ASSOCIATED&isForward=true", + ) + return j.get("result") or [] + + +def assert_associated_to_req(s: requests.Session, wid: str, req_id: str, label: str) -> None: + rows = list_associated(s, wid) + ids = {r.get("identifier") for r in rows} + if req_id not in ids: + raise RuntimeError( + f"{label} 关联项未挂需求:期望 {req_id},实际 {[r.get('serialNumber') for r in rows]}" + ) + + +AUTO_RDO = """## 原始诉求(AutoRDO) + +运维需在故障处置页承接机器人上报的故障,完成处置、挂起与归档,并保留证据链;工作台相关统计口径需与处置页一致 + +待确认: +- 本期是否含真实短信/邮件通道(现口径一般为演示模板) + +## 工作项编号(系统) +- 交付:待建 +- 分析:待建 +- 设计:待建 +""" + + +def summarize_from_create(w: dict, *, status: str, assignee_name: str) -> dict: + return { + "serial": w.get("serialNumber"), + "id": w.get("identifier"), + "subject": w.get("subject"), + "status": status, + "assignee": assignee_name, + "parent": w.get("parentIdentifier"), + } + + +def build_normal() -> dict: + t0 = time.perf_counter() + s = session() + title = "【新增】故障处置(YunxiaoPMapp标准·极速v2)" + req = create(s, req_payload(title, md_to_html(AUTO_RDO + f"\n原型:{PROTO}\n"))) + rid = req["identifier"] + + with ThreadPoolExecutor(max_workers=2) as pool: + f_tag_r = pool.submit(apply_tag, session(), rid) + f_deliv = pool.submit( + create, + session(), + task_payload( + f"【交付】{title}", + f"

{PLACEHOLDER}

", + HEFEI, + associated_req=rid, + ), + ) + deliv = f_deliv.result() + f_tag_r.result() + did = deliv["identifier"] + + with ThreadPoolExecutor(max_workers=3) as pool: + f_tag_d = pool.submit(apply_tag, session(), did) + f_ana = pool.submit( + create, + session(), + task_payload( + f"【分析】{title}", + "

分析阶段:故障处置台账、挂起归档与证据链。

", + WANG, + associated_req=rid, + parent_delivery=did, + ), + ) + f_des = pool.submit( + create, + session(), + task_payload( + f"【设计】{title}", + f"

设计阶段:对齐原型 {PROTO}

", + WANG, + associated_req=rid, + parent_delivery=did, + ), + ) + ana = f_ana.result() + des = f_des.result() + f_tag_d.result() + + with ThreadPoolExecutor(max_workers=3) as pool: + list( + as_completed( + [ + pool.submit(transit, session(), rid, PENDING, DESIGN_DONE), + pool.submit(transit, session(), ana["identifier"], PENDING, TASK_DONE), + pool.submit(transit, session(), des["identifier"], PENDING, TASK_DONE), + ] + ) + ) + transit(s, rid, DESIGN_DONE, PENDING_DEV) + + return { + "path": "normal", + "elapsed_s": round(time.perf_counter() - t0, 3), + "req": summarize_from_create(req, status="待开发", assignee_name="王冕"), + "delivery": summarize_from_create(deliv, status="待处理", assignee_name="何斐"), + "analysis": summarize_from_create(ana, status="已完成", assignee_name="王冕"), + "design": summarize_from_create(des, status="已完成", assignee_name="王冕"), + } + + +def build_fast( + *, + tag_id: str = TAG_FAULT, + has_prototype: bool = True, + delivery_html: str | None = None, +) -> dict: + """无单快轨。 + + - 设计描述 = 需求 document + - 交付描述 = 手工同步需求正文(无原型)或传入 AutoPRD HTML;禁止无故占位 + - 设计 79/80=当日;交付 79=当日 + - 需求/交付/设计同标签 + - 需求预计/实际工时各 2 + - 设计 TASK_SUB→交付后尝试 ASSOCIATED→需求 + """ + t0 = time.perf_counter() + s = session() + title = "【新增】故障处置(YunxiaoPM快轨·极速v5)" + req_html = md_to_html(AUTO_RDO + (f"\n原型:{PROTO}\n" if has_prototype else "\n")) + req = create(s, req_payload(title, req_html)) + rid = req["identifier"] + # 以落库 document 为准(与手动建需后读需求一致) + req_html = req_document_html(s, rid) or req_html + + if delivery_html: + deliv_html = delivery_html + desc_source = "autoprd" + elif req_html.strip(): + deliv_html = req_html + desc_source = "manual_sync" + else: + deliv_html = f"

{PLACEHOLDER}

" + desc_source = "placeholder" + + with ThreadPoolExecutor(max_workers=2) as pool: + f_tag_r = pool.submit(apply_tag, session(), rid, tag_id) + f_deliv = pool.submit( + create, + session(), + task_payload( + f"【交付】{title}", + deliv_html, + HEFEI, + associated_req=rid, + ), + ) + deliv = f_deliv.result() + f_tag_r.result() + did = deliv["identifier"] + + with ThreadPoolExecutor(max_workers=2) as pool: + f_tag_d = pool.submit(apply_tag, session(), did, tag_id) + f_des = pool.submit( + create, + session(), + task_payload( + f"【设计】{title}", + req_html, + WANG, + parent_delivery=did, + ), + ) + des = f_des.result() + f_tag_d.result() + des_id = des["identifier"] + apply_tag(s, des_id, tag_id) + + # 计划时间:设计起止当日;交付开始当日(create 已带 79,再 field/value 加固) + set_fields(s, des_id, [("79", NOON), ("80", EOD)]) + set_fields(s, did, [("79", NOON)]) + + # 设计关联项补挂需求(可能失败,回报 risk) + design_assoc_ok = try_associate_to_req(s, des_id, rid) + + with ThreadPoolExecutor(max_workers=2) as pool: + list( + as_completed( + [ + pool.submit(transit, session(), rid, PENDING, DESIGN_DONE), + pool.submit(transit, session(), des_id, PENDING, TASK_DONE), + ] + ) + ) + transit(s, rid, DESIGN_DONE, PENDING_DEV) + + set_estimate_hours(s, rid, FAST_EST) + set_actual_hours(s, rid, FAST_ACT) + + risk = None + if desc_source == "placeholder": + risk = "交付描述仍为占位" + if not design_assoc_ok: + risk = (risk + ";" if risk else "") + "设计 ASSOCIATED→需求补挂失败(Cookie),须 UI/OpenAPI 兜底" + + return { + "path": "fast", + "elapsed_s": round(time.perf_counter() - t0, 3), + "req": summarize_from_create(req, status="待开发", assignee_name="王冕"), + "delivery": summarize_from_create(deliv, status="待处理", assignee_name="何斐"), + "design": summarize_from_create(des, status="已完成", assignee_name="王冕"), + "analysis": None, + "delivery_desc_source": desc_source, + "design_associated_ok": design_assoc_ok, + "risk": risk, + } + + +def main() -> None: + auth0 = time.perf_counter() + load_auth() + auth_s = round(time.perf_counter() - auth0, 3) + + wall0 = time.perf_counter() + with ThreadPoolExecutor(max_workers=2) as pool: + f_fast = pool.submit(build_fast) + f_normal = pool.submit(build_normal) + fast = f_fast.result() + normal = f_normal.result() + build_s = round(time.perf_counter() - wall0, 3) + + s = session() + assert_associated_to_req(s, normal["delivery"]["id"], normal["req"]["id"], "normal.delivery") + assert_associated_to_req(s, fast["delivery"]["id"], fast["req"]["id"], "fast.delivery") + + def assert_sub(delivery_id: str, child_id: str, label: str) -> None: + rows = api( + s, + "GET", + f"https://devops.aliyun.com/projex/api/workitem/v2/workitem/{delivery_id}/relation/workitem/list/by-relation-category?category=PARENT_SUB&isForward=true", + ).get("result") or [] + ids = {r.get("identifier") for r in rows} + if child_id not in ids: + raise RuntimeError( + f"{label} 未出现在交付子项:期望 {child_id},实际 {[r.get('serialNumber') for r in rows]}" + ) + + assert_sub(normal["delivery"]["id"], normal["analysis"]["id"], "normal.analysis") + assert_sub(normal["delivery"]["id"], normal["design"]["id"], "normal.design") + assert_sub(fast["delivery"]["id"], fast["design"]["id"], "fast.design") + verify = { + "normal_req": get(s, normal["req"]["id"])["status"]["displayName"], + "fast_req": get(s, fast["req"]["id"])["status"]["displayName"], + "normal_delivery_assignee": ( + get(s, normal["delivery"]["id"]).get("assignedTo") or {} + ).get("displayName"), + "fast_delivery_assignee": (get(s, fast["delivery"]["id"]).get("assignedTo") or {}).get( + "displayName" + ), + "delivery_associated_ok": True, + "stage_tasks_as_sub_ok": True, + "fast_design_associated_ok": fast.get("design_associated_ok"), + } + out = { + "normal": normal, + "fast": fast, + "verify": verify, + "auth_elapsed_s": auth_s, + "wall_elapsed_s": build_s, + "created_at": datetime.now(TZ).isoformat(), + "mode": "live_create_fast_v5_fast_track_rules", + "opts": { + "create_includes_plan_start": True, + "skip_plan_end_on_create": True, + "tracked_transit_no_get": True, + "delivery_assignee_hefei_at_create": True, + "overlap_tag_with_create": True, + "fast_req_hours": f"{FAST_EST}+{FAST_ACT}", + "relation": "delivery ASSOCIATED→req; analysis/design TASK_SUB→delivery; design post ASSOCIATED→req", + "http": "requests.Session keep-alive", + }, + } + Path("/tmp/yunxiao_pmapp_fast_v2_result.json").write_text( + json.dumps(out, ensure_ascii=False, indent=2) + ) + print(json.dumps(out, ensure_ascii=False, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/.claude/skills/YunxiaoPM/model.md b/.claude/skills/YunxiaoPM/model.md new file mode 100644 index 0000000..a59f700 --- /dev/null +++ b/.claude/skills/YunxiaoPM/model.md @@ -0,0 +1,60 @@ +# 交付树模型与关联约定 + +## 真相源 + +```text +需求状态 = 阶段看板唯一真相(分析中/设计中/待开发…) +【交付】任务 = 该需求的交付容器(每需求最多 1 条) +【分析】/【设计】 = YunxiaoPMapp 在交付下建的阶段子项 +【开发】/【测试】 = 另属开发 Skill / 测试 Skill,不进本包 +ASSOCIATED = 横向挂钩(【交付】 ↔ 需求) +SUB / TASK_SUB = 纵向拆解(交付 → 分析/设计) +``` + +## 关系验收 + +| 关系 | 用在哪里 | 验收 | 优先级 | +|---|---|---|---| +| ASSOCIATED | **仅【交付】** ↔ 需求 | 交付详情「关联项」能看到需求(**强制**) | 交付必过 | +| TASK_SUB | 【分析】/【设计】 → 【交付】 | 交付详情「子项」能看到阶段任务(**强制**) | 阶段任务必过 | + +建单规则(Cookie 路径 · 同一 create 只能一条 `createWorkitemRelationInfo`): + +| 对象 | `createWorkitemRelationInfo` | 另写字段 | +|---|---|---| +| 【交付】 | `ASSOCIATED` → 需求 | — | +| 【分析】/【设计】 | `TASK_SUB` → 交付 | `parent` + `parentIdentifier` = 交付 | + +**禁止**对分析/设计只写 `parentIdentifier` + `ASSOCIATED→需求`:关联项可能有,但交付「子项」仍为空(ONEOS-246/247 已复现)。 +勿用 `PARENT` 冒充关联项。细则见 [live-api.md](live-api.md)。 + +## 禁止 + +| 禁止 | 说明 | +|---|---| +| 创建或描述【开发】/【测试】 | 口令与回报均不出现 | +| 分析/设计未挂成交付子项(无 TASK_SUB) | 交付「子项」为 0,交棒验收失败 | +| 用任务名称/标题做唯一性或查重 | 不得 `subject` 匹配复用 | +| 同一需求认定多个交付任务编号 | 列出编号请人合并后再继续 | +| 功能模块标签用「分析/设计/交付」 | 模块标签打在需求(可选交付)上 | + +## 任务标题(仅展示) + +- 【交付】+ 需求标题 +- 【分析】+ 需求标题 +- 【设计】+ 需求标题 + +标题可改,**不参与查重**。 + +## 任务编号唯一性 + +**唯一允许的查重/复用渠道:云效任务编号**(如 `ONEOS-99` / `serialNumber`)。 + +| 场景 | 正确 | 错误 | +|---|---|---| +| 首次创建【交付】 | 建单成功后回报并写入「工作项编号(系统)」 | 下次用标题搜 | +| 复用【交付】 | 口令或编号区块带 `交付任务=ONEOS-xx` | list 后按 subject 相等 | +| 判断是否已有交付 | 编号区块或交付 ASSOCIATED 编号集合 | 数【交付】开头标题 | +| 分析/设计幂等 | 仅已登记编号 | 按【分析】+需求标题 | + +按编号命中则复用;**计划开始已有值禁止改写**。 diff --git a/.claude/skills/YunxiaoPM/references/acceptance.md b/.claude/skills/YunxiaoPM/references/acceptance.md new file mode 100644 index 0000000..d677730 --- /dev/null +++ b/.claude/skills/YunxiaoPM/references/acceptance.md @@ -0,0 +1,49 @@ +# 验收清单与回报 + +## apply 后必须自检 + +| # | 项 | 通过标准 | +|---|---|---| +| 1 | 需求编号 | 回报含 ONEOS-xx | +| 2 | 任务编号 | 交付/分析/设计凡新建或操作均回报编号;禁止只报标题 | +| 3 | 编号区块 | 需求「工作项编号(系统)」与实际一致 | +| 4 | ASSOCIATED | **仅交付**详情关联项可见需求(`createWorkitemRelationInfo=ASSOCIATED`;禁止 PARENT 冒充) | +| 5 | SUB | **必过**:分析/设计 create 用 `TASK_SUB→交付`(含 `parent`+`parentIdentifier`);交付「子项」tab 须可见。与 ASSOCIATED 同 create 互斥,阶段任务关联项可空(见 live-api.md) | +| 6 | 计划开始 | 未误覆盖已有值 | +| 7 | 阶段日历工时 | 有计划完成时已写入;脚注存在;回报用语正确 | +| 8 | 交棒 | 需求=待开发;交付负责人=何斐(交棒场景) | +| 9 | 占位风险 | 交付仍占位时首行标红 | +| 10 | 负向 | 本轮**无**【开发】/【测试】任务;未按标题查重 | + +设计完成额外:AutoPRD 段已写;交付非占位(除非失败停下);ZIP+截图已挂或已列出缺项。 + +迭代额外:只挂【交付】(需求不挂);版本号符合递增规则。 + +## 回报模板(精简) + +```text +【YunxiaoPM】 +风险:(若有占位交棒则首行标红) +需求:ONEOS-xx | 状态=… +交付:ONEOS-a | 负责人=… +分析:ONEOS-b | …(无则写无) +设计:ONEOS-c | …(无则写无) +阶段日历工时:分析 Hh / 设计 Hh(若本轮写入) +附件:…(若本轮) +迭代:…(若本轮) +下一步:请技术经理使用开发 Skill(交棒后) +``` + +## 适合全自动 vs 人工门禁 + +| 适合全自动 | 建议半自动/人工门禁 | +|---|---| +| 建单、打标签、挂迭代、写描述、建交付树、改状态、交棒负责人、幂等查重 | **PJ 云效项目点选**、受理(已确认)、是否快轨、设计完成是否真可开发、跨需求优先级与迭代容量、回退与取消 | + +## 实写性能(2026-07-23 复盘) + +| 项 | 要求 | +|---|---| +| 改状态 | 只用 `status/transit`(见 [live-api.md](live-api.md)) | +| 改【交付】负责人 | 只用 `PATCH …/{id}` + `propertyKey=assignedTo` | +| 极速复测 | `scripts/live_create_fast.py`;默认不开浏览器 | diff --git a/.claude/skills/YunxiaoPM/references/commands.md b/.claude/skills/YunxiaoPM/references/commands.md new file mode 100644 index 0000000..571bd06 --- /dev/null +++ b/.claude/skills/YunxiaoPM/references/commands.md @@ -0,0 +1,22 @@ +# 口令面 + +```text +记录需求:…;项目=(必选·Plan 点选);优先级=紧急|高|中|低;标签=…;提交部门=…;提交人=…;推进至=暂不推进|已确认|分析中|设计中|设计完成|待开发|待开发(快轨) +受理确认:ONEOS-xx +开始分析:ONEOS-xx → 【交付】+【分析】并回报编号 +开始设计:ONEOS-xx;交付任务=…;分析任务=… → 【设计】+收口分析 +设计完成:ONEOS-xx;设计任务=…;原型=… +交棒开发:ONEOS-xx;交付任务=… → 待开发 + 该编号负责人=何斐 +快轨待开发:ONEOS-xx → 待开发 +【交付】+【设计】(无【分析】)+ 交付负责人=何斐 +编号直推:分析任务=ONEOS-b / 设计任务=ONEOS-c → 收口未完成计划完成 + 需求待开发 + 交付交棒何斐 +创建迭代:版本类型=主|副|子;交付任务=ONEOS-a,ONEOS-b,…;名称前缀=… +AutoRDO:…(粘贴聊天/附录音)→ 再记录需求 +``` + +版本自动规则:取同前缀下最大 `Vx.y.z`(旧 `Vx.y` 视为 `Vx.y.0`)再按主/副/子递增;迭代名=`{前缀}{新版本}`。 + +**PJ 项目**:新建需求/迭代前须点选云效项目(实时列表);禁止默认直指。见 [project-selection.md](project-selection.md)。 + +后续推进口令**优先显式带任务编号**;未带则读「工作项编号(系统)」;仍无则询问;**禁止按标题补全**。 + +YunxiaoPM 回报止于交棒;可一句「请技术经理使用开发 Skill」。 diff --git a/.claude/skills/YunxiaoPM/references/compact-select.md b/.claude/skills/YunxiaoPM/references/compact-select.md new file mode 100644 index 0000000..03756b8 --- /dev/null +++ b/.claude/skills/YunxiaoPM/references/compact-select.md @@ -0,0 +1,95 @@ +# 压缩点选(`1a2b3a4d`) + +记录需求 Plan **默认**用编号题 + 字母选项;用户可用一行压缩答复确认。 + +## Plan 展示模板 + +```text +请回复压缩点选,例:1a2b3a4d +(数字=题号,字母=选项;大小写不敏感;可写 1A2B3A4D) + +1. 类型 + A. 新增 + B. 优化 + +2. 项目(云效实时列表;建议项可标★) + A. 01_ONEOS(ONEOS)★ + B. 02_小羚羚APP(XLLAPP) + C. … + +3. 优先级 + A. 紧急 + B. 高 + C. 中 + D. 低 + +4. 标签(云效候选;建议项可标★) + A. 故障管理★ + B. 还车应结款 + C. … +``` + +可选续题(口令已齐则可写入清单、压缩串可不含): + +```text +5. 推进至 … +6. 迭代 … +``` + +## 解析规则 + +| 规则 | 说明 | +|---|---| +| 形态 | `(题号)(字母)` 连续拼接,如 `1a2b3a4d` | +| 题号 | `1`=类型 `2`=项目 `3`=优先级 `4`=标签;可扩展 `5` `6` | +| 字母 | `a`→第 1 项 … `z`→第 26 项;超出选项数 → 非法,停 | +| 缺题 | 该题若口令已唯一预填且列表唯一命中,可用预填;否则停并追问缺题 | +| 多答同题 | 后写覆盖先写 | +| 非法字符 | 停,重贴模板 | + +解析成功后 Plan 回显人话确认一行,例如: + +```text +已解析 1a2b3a4d → 类型=新增;项目=02_小羚羚APP;优先级=紧急;标签=(D 项名) +确认后回复「执行」 +``` + +用户再回「执行」才 apply(仍守 Plan 门禁)。 + +## 与口令预填 + +口令已写 `类型:新增;项目:01_ONEOS;优先级:高;标签:故障管理` 时: + +- 对应选项标 ★,并给出**建议压缩串**(如 `1a2a3b4a`),用户可直接改字母或整段重答。 +- **不得**因有预填而跳过展示字母表;压缩确认(或逐题点选)仍是门禁。 + +## 标签未命中 → 自动重拉一次候选并重生选项 + +「标签无法对应」:口令标签名在**当前标签候选**中 0 命中(或多命中无法唯一)。 + +| 步骤 | 动作 | +|---|---| +| 1 | **自动重拉一次**标签候选(见下「标签候选来源」;强制刷新,不用旧缓存) | +| 2 | 用新列表**重新生成 4.A/B/C…** 选择项 | +| 3 | 回报:`已自动重拉标签列表(因无法对应)`;请用户重答 `4x` 或整串 | +| 4 | 仍无法对应 → 停;**禁止**第 3 次空转;**禁止**猜 tagId | + +> 说明:此处重拉的是**标签候选列表**,不是项目列表。项目无法对应仍走 [project-selection.md](project-selection.md) 的「自动重拉一次」。 + +### 标签候选来源(当前已验证路径) + +1. `runtime-ids.json` → `tags` +2. 目标项目(或预填项目)下近期工作项 `tag` 字段聚合去重 +3. 重拉 = 再请求工作项列表聚合 + 合并 runtime;命中后可回写 `tags` 缓存 + +(专用 `tag/list` Cookie API 尚未稳定;有稳定端点后改写入 `live-api.md` 并切换。) + +## 项目列表顺序 + +生成 2. 选项时:口令预填/建议项目置 **A** 并标 ★,其余按云效返回顺序接 B/C/…。 + +## Agent 义务 + +1. 记录需求进 Plan 时必须输出本模板(至少 1–4 题)。 +2. 收到压缩串先解析回显,再等「执行」。 +3. 标签/项目无法对应:各自动重拉**一次**并刷新对应题选项。 diff --git a/.claude/skills/YunxiaoPM/references/description-split.md b/.claude/skills/YunxiaoPM/references/description-split.md new file mode 100644 index 0000000..6683bb5 --- /dev/null +++ b/.claude/skills/YunxiaoPM/references/description-split.md @@ -0,0 +1,69 @@ +# 需求描述 vs 交付描述 + +两类描述职责不同;禁止混写;禁止建【交付】时把 AutoPRD 六大块提前塞进交付描述。 + +## A. 需求描述 · AutoRDO 清洗 + +| 项 | 规则 | +|---|---| +| 触发 | 对话建需求 / 碎片材料入库 | +| 调用 | **必须**使用独立 Skill「**`$AutoRDO`**」(`AutoRDO/SKILL.md`;本 Skill 不内嵌清洗细则) | +| 输入 | 聊天记录、录音、口述材料 | +| 处理 | 保留原意;书面化;去口头禅;**去除结尾句号**(细则在 AutoRDO `references/rules.md`) | +| 输出 | 写入 `## 原始诉求(AutoRDO)` | +| 不做 | 本阶段不写 AutoPRD 六大块;不要求已有原型;不覆盖已有「产品说明」 | + +口令:`AutoRDO:…` → 确认后 `记录需求:【新增】标题;描述=整理稿;推进至=…` + +## B. 交付描述 + +### B1. 标准路径(分析中起建交付) + +| 时机 | 【交付】描述 | +|---|---| +| 首次创建起至设计完成前 | 固定文案:`等待设计任务完成后自动填入` | +| 设计完成时 | 用 AutoPRD「产品说明」正文替换占位 | + +### B2. 无单快轨建交付(覆盖 B1) + +| 口令/材料 | 【交付】描述 | +|---|---| +| 手工描述(无指定原型) | **同步需求**当前手工/AutoRDO 正文;禁止占位 | +| 指定原型页面 | `$oneos-autoprd` 从原型生成写入;禁止占位 | +| 既无正文又无原型 | 才允许占位并标红风险 | + +快轨【设计】描述始终复制需求当前描述正文(见 [fast-track.md](fast-track.md))。 + +## C. 需求描述双段模板(不可互相覆盖) + +```markdown +## 原始诉求(AutoRDO) +(清洗稿;设计完成也不删除) + +## 产品说明(AutoPRD) +(六大块+对象存储链接;未设计完成前可无此节或写「待设计完成后填入」) + +## 工作项编号(系统) +- 交付:… +- 分析:… +- 设计:… +``` + +## D. 设计完成 · AutoPRD + 附件 + +前提:设计任务完成且已关联对应原型页。 + +1. 调用 **`$oneos-autoprd`(AutoPRD)**,产出并落盘 `.spec/requirements-prd.md` + 标注同步: + - 对象存储预览链接:`{baseUrl}/{prototype-id}/index.html`(禁止加 `prototypes/` 前缀、禁止去掉 `index.html`) + - 产品说明 Markdown(总览/角色/流程/状态/风险/交付等,见 AutoPRD 模板) +2. 写入需求 `## 产品说明(AutoPRD)`;**禁止覆盖** `## 原始诉求(AutoRDO)` / `## 工作项编号(系统)`。 +3. 【交付】描述:按**交付任务编号**用产品说明正文**替换**占位(细则见 AutoPRD `references/yunxiao-delivery-sync.md`);可附「原始诉求见需求描述」。**创建【交付】时不得提前灌 MD。** +4. 附件(需求 +【交付】均挂;失败则不得声称成功): + - Make「导出 HTML(含源码)」ZIP → [make-export-attach.md](make-export-attach.md) + - Make「复制截图」全交互页 → 同上 + +缺原型 / AutoPRD 失败 / 导出或截图失败 → **不得**报到设计完成并宣称附件齐全;停下并列出缺项。 + +执行顺序:先 AutoPRD 落盘与附件就绪 → 再改需求/交付描述与设计计划完成 → 最后改需求状态。 + +**禁止**加载 `yunxiao-requirement-lifecycle`;阶段任务树只由本 Skill(YunxiaoPMapp)创建。 diff --git a/.claude/skills/YunxiaoPM/references/fast-track.md b/.claude/skills/YunxiaoPM/references/fast-track.md new file mode 100644 index 0000000..4ae6a81 --- /dev/null +++ b/.claude/skills/YunxiaoPM/references/fast-track.md @@ -0,0 +1,47 @@ +# 无单快轨到待开发 + +适用:口令明确「快轨」或「推进至待开发」且当前需求尚无标准分析路径任务(或用户确认跳过分析)。 + +## 动作表 + +| 对象 | 动作 | +|---|---| +| 需求 | 状态 → **待开发**;**预计工时=2**、**实际工时=2**(覆盖同日 8h 日历工时默认);标签按口令;更新编号区块 | +| 【交付】 | 无则新建;ASSOCIATED→需求(勿用 PARENT);负责人→何斐;**计划开始=创建当日**(`79`);**不写**计划完成;**描述双路径**(见下);**标签与需求相同**;更新编号区块 | +| 【设计】 | **必须新建**;**TASK_SUB→交付**(交付「子项」须可见);**描述=复制需求当前描述正文**;计划开始/完成均=当日(create 后 `field/value` 写 `79`+`80`);任务→完成态;**标签与需求相同**;建后再尝试 **ASSOCIATED→原始需求**(Cookie 常失败,见 live-api;失败须标红,**子项优先于关联项**);更新编号区块 | +| 【分析】 | **禁止创建** | + +## 交付描述双路径(A1) + +| 口令/材料 | 【交付】描述 | +|---|---| +| **手工描述**(无指定原型) | 同步需求当前手工/AutoRDO 正文(与设计一致可复制 document) | +| **指定原型页面** | 调用 `$oneos-autoprd`,从原型生成 AutoPRD「产品说明」写入【交付】 | +| 既无手工可同步正文、又无原型 | 才允许占位 `等待设计任务完成后自动填入`,Plan 勾选交棒占位风险,回报首行标红 | + +有手工或原型时**禁止**交棒占位。 + +## AutoPRD + +- 快轨默认可不跑 AutoPRD;**例外**:口令指定原型 → 交付走 AutoPRD 路径(上表)。 +- 口令同时「设计完成 + 原型」时仍按步骤 4 灌需求产品说明段。 + +## 查重 + +只认任务编号。已有分析单不得因「快轨」被删除。 + +## 回报必含 + +- 需求 / 交付 / 设计编号 +- 「快轨:未建分析;设计已同步需求描述并当日收口」 +- 交付描述来源:手工同步 / AutoPRD / 占位(若占位则首行标红) +- 需求预计工时=2、实际工时=2 +- 设计关联项含需求编号;交付子项含设计编号 + +## 与标准 / 编号直推对比 + +```text +标准:分析中→交付+分析 → 设计中→设计+收口分析 → 设计完成→收口设计 → 待开发交棒 +快轨(无既有阶段单):→ 待开发:新建交付+设计+交棒何斐(无分析;设计当日收口;需求工时 2+2) +编号直推:已有分析/设计编号 → 收口空计划完成 → 待开发+交付交棒(不新建冗余单) +``` diff --git a/.claude/skills/YunxiaoPM/references/handoff-and-rollback.md b/.claude/skills/YunxiaoPM/references/handoff-and-rollback.md new file mode 100644 index 0000000..44edcf6 --- /dev/null +++ b/.claude/skills/YunxiaoPM/references/handoff-and-rollback.md @@ -0,0 +1,20 @@ +# 交棒门禁与回退最小集 + +## 交棒门禁(待开发) + +| 情形 | 规则 | +|---|---| +| 【交付】描述已是 AutoPRD 正式说明(非占位) | 允许交棒;Plan 正常列交付任务编号 + 负责人→何斐 | +| 【交付】描述仍为 `等待设计任务完成后自动填入` | **仍允许交棒**,但 Plan **必须**勾选风险项「交付描述仍为占位,技术侧仅可凭需求 AutoRDO 稿理解」;回报**首行标红**该风险;**禁止**静默交棒假装材料齐全 | +| 口令同时带原型且要求设计完成 | 先走步骤 4(AutoPRD+附件)成功,再交棒,不再勾占位风险 | + +## 回退 / 变更(最小集 · 兼容计划开始不篡改) + +| 场景 | 规则 | +|---|---| +| 待开发 → 退回设计中 | 需求状态回退;【交付】负责人可改回创建人/产品;**交付计划开始不改**;若需重做设计 → **新开**设计任务编号(旧设计保持已收口,不改其计划开始);更新「工作项编号(系统)」中的设计编号为新号 | +| 设计完成后需求大变 | 不自动改状态;Plan 询问是否回退设计中并新开设计编号;更新 AutoPRD 段;AutoRDO 段保留并追加「变更纪要」 | +| 取消需求 | 关联任务标取消/废止;不删编号区块;不改已写计划开始 | +| 度量含义 | 交付计划开始保留 = 全周期仍从首次开工起算;回退重做会拉长日历工时——回报注明「含回退重做」 | + +凡写云效的回退仍须走 Plan 门禁。 diff --git a/.claude/skills/YunxiaoPM/references/handoff-contract.md b/.claude/skills/YunxiaoPM/references/handoff-contract.md new file mode 100644 index 0000000..ab9c712 --- /dev/null +++ b/.claude/skills/YunxiaoPM/references/handoff-contract.md @@ -0,0 +1,14 @@ +# 交接契约(开发 Skill 入口) + +YunxiaoPMapp 与开发 Skill **不要**互相 include 全文;仅认下列契约。 + +```text +PM 完成交棒 → 需求=待开发;【交付】任务编号=ONEOS-xx;负责人=何斐;ASSOCIATED 需求 +若交付描述仍为占位 → 回报已标红风险 +开发 Skill 入口 → 认「需求编号 + 交付任务编号」 +占位时只可信需求「原始诉求(AutoRDO)」并有权要求产品补设计完成 +``` + +共享常量(项目 ID、何斐 ID、节假日日历)可引用本 Skill 的 `assets/` 短路径,勿加载整份对方规则。 + +测试 Skill 另开;本契约不覆盖提测/缺陷。 diff --git a/.claude/skills/YunxiaoPM/references/live-api.md b/.claude/skills/YunxiaoPM/references/live-api.md new file mode 100644 index 0000000..f2bab69 --- /dev/null +++ b/.claude/skills/YunxiaoPM/references/live-api.md @@ -0,0 +1,242 @@ +# 云效实写 API(YunxiaoPMapp 已验证) + +`verified_at`: 2026-07-25 · **项目须门禁 PJ 点选**(见 [project-selection.md](project-selection.md));历史验证样本项目为 `01_ONEOS` / 原「统一运营管理平台」(`last_selected.spaceIdentifier` 见 `assets/runtime-ids.json`,禁止未点选即使用)。 + +本文件只记**已跑通**的写法;禁止再盲试 `updateStatus` / 错误 `updateFieldValue` POST。 + +## 认证 + +- Cookie:Chrome 域 `.aliyun.com` / `devops.aliyun.com`(`browser_cookie3` 或 Playwright storage) +- Header:`x-xsrf-token` = cookie `XSRF-TOKEN`(URL 解码后) +- `Origin` / `Referer`:`https://devops.aliyun.com` + +## 建单 + +`POST|PUT /projex/api/workitem/workitem?_input_charset=utf-8` + +创建响应 `result.identifier` / `serialNumber` 即编号真相;**禁止按标题查重**。 + +## 改负责人(已通) + +```http +PATCH /projex/api/workitem/workitem/{id}?_input_charset=utf-8 +{"propertyKey":"assignedTo","propertyValue":"","operateType":"COVER"} +``` + +交棒:【交付】`propertyValue` = 何斐 ID。 + +## 打标签(已通) + +```http +PATCH /projex/api/workitem/workitem/{id}?_input_charset=utf-8 +{"workitemIdentifier":"{id}","propertyKey":"tag","propertyValue":"[,]","operateType":"COVER"} +``` + +## 改状态(已通 · 唯一推荐) + +```http +POST /projex/api/workitem/workitem/{id}/status/transit?_input_charset=utf-8 +{"fromStatus":"<当前status.identifier>","toStatus":"<目标status.identifier>"} +``` + +成功:`code=200` 且 `result=true`。失败时 `errorMsg` 含「不能流转」。 + +### 需求状态 ID(本项目) + +| 显示名 | identifier | +|---|---| +| 待处理 | `100005` | +| 已确认 | `32` | +| 分析中 | `154395` | +| 设计中 | `156603` | +| 设计完成 | `307012` | +| 待开发 | `1582fc929d429111b925309493` | + +### 任务状态 ID + +| 显示名 | identifier | +|---|---| +| 待处理 | `100005` | +| 已完成 | `100014` | + +### 极速交棒跳转(工作流允许) + +从「待处理」菜单可见直达「设计完成」;推荐最少跳: + +```text +待处理 → 设计完成 → 待开发 +``` + +标准路径若需看板留痕,可走完整链:已确认→分析中→设计中→设计完成→待开发(仍用本 API,勿开 UI)。 + +### 禁止(已证伪) + +| 写法 | 结果 | +|---|---| +| `PATCH …/updateStatus` + `statusIdentifier` | `400 不能为空` | +| `PATCH …/{id}` + `propertyKey=status` | `property not found` | +| Playwright 点左侧/列表上的状态色块(`x<1100`) | 假成功、状态不落库 | + +仅当 `status/transit` 不可用时,才用 UI:右侧详情状态钮(`getBoundingClientRect().x > 1100`)+ `.next-menu-item`。 + +## 计划开始/完成 · 提交部门/人 · 预计工时(2026-07-27 修订) + +**通用字段写入(已通):** + +```http +POST /projex/api/workitem/workitem/field/value/{workitemId}?_input_charset=utf-8 +Content-Type: application/x-www-form-urlencoded + +fieldValueList=[{"fieldIdentifier":"79","value":"2026-07-27 12:00:00"},{"fieldIdentifier":"3132597a9718d1c282b7ba5a0c","value":"业务管理部"},{"fieldIdentifier":"9e01269e96f91fbb97d36bf5b3","value":"何苗苗"}] +``` + +| 字段 | fieldIdentifier | value | +|---|---|---| +| 计划开始 | `79` | `YYYY-MM-DD HH:mm:ss`(推荐正午)或 epoch ms 字符串 | +| 计划完成 | `80` | 同上 | +| 提交部门 | `3132597a9718d1c282b7ba5a0c` | 纯文本 | +| 提交人 | `9e01269e96f91fbb97d36bf5b3` | 纯文本 | + +**预计工时 `101586`:禁止直接改字段**(报「不可直接修改」)。须登记: + +```http +POST /projex/api/workitem/workitem/time/estimate?_input_charset=utf-8 +{"workitemIdentifier":"","spentTime":8,"type":"develop","description":"阶段日历工时","recordUserIdentifier":"","forCreate":false,"containsRestDay":false} +``` + +删除多余预估:`DELETE /projex/api/workitem/workitem/time/estimate/{workitemId}/{estimateId}` +列表:`GET …/time/estimate/list?workitemIdentifier=` + +旧写法 `PATCH …/updateWorkitemFieldValue` 对上述自定义字段常 `400 不能为空`,勿再优先使用。 + +## 父子 / 子项 / 关联项(已通 · 2026-07-23 修订 · 子项优先) + +### 关联项(【交付】强制 · ASSOCIATED) + +任务详情「关联项」只认 `ASSOCIATED`。**仅【交付】**建单时 `createWorkitemRelationInfo` 必须指向**需求**: + +```json +{ + "createWorkitemRelationInfo": { + "relatedWorkitemIdentifier": "<需求id>", + "relatedToRelationIdentifier": "ASSOCIATED" + } +} +``` + +校验: + +```http +GET /projex/api/workitem/v2/workitem/{交付id}/relation/workitem/list/by-relation-category?category=ASSOCIATED&isForward=true +``` + +`result` 含该需求即通过。 + +### 禁止(关联项) + +| 写法 | 结果 | +|---|---| +| `relatedToRelationIdentifier=PARENT` 把交付挂需求 | 详情可能有 parent,**关联项仍为空** | +| 分析/设计只用 `ASSOCIATED→需求` + `parentIdentifier` | 关联项可能有,**交付子项仍为空**(ONEOS-246/247) | +| 建后再 `POST …/relation/record` 补关系 | Cookie 路径下常报「不能关联相同的工作项」 | +| `createWorkitemRelationList` | 不落 ASSOCIATED | + +### 子项(【分析】/【设计】强制 · TASK_SUB) + +「子项」页读 `PARENT_SUB` / `TASK_SUB`,分析/设计**必须**: + +```json +{ + "parent": "<交付id>", + "parentIdentifier": "<交付id>", + "createWorkitemRelationInfo": { + "relatedWorkitemIdentifier": "<交付id>", + "relatedToRelationIdentifier": "TASK_SUB" + } +} +``` + +同一 create 只能带一条 `createWorkitemRelationInfo`。 +`ASSOCIATED→需求` 与 `TASK_SUB→交付` **不能同时写**。 + +**产品优先级:交付「子项」tab > 阶段任务「关联项」。** +交付本身仍必须 `ASSOCIATED→需求`。标准路径下分析/设计的「关联项」允许为空。 + +**无单快轨例外(设计双挂):** create 用 `TASK_SUB→交付` 后,须再补 **ASSOCIATED→原始需求**,使设计详情「关联项」可见需求。 + +```http +POST /projex/api/workitem/workitem/{设计id}/relation/record?_input_charset=utf-8 +{"relationIdentifier":"ASSOCIATED","toWorkitemIdentifier":"<需求id>"} +``` + +**已证伪(Cookie · 2026-07-27):** 凡工作项**已创建**后再 `relation/record` 补挂(含 ASSOCIATED / TASK_SUB),常报「不能关联相同的工作项」;与是否已有父项无关。`createWorkitemRelationList` 亦不落 ASSOCIATED。 +**可行路径:** + +| 目标 | 做法 | +|---|---| +| 交付「子项」可见设计(优先) | create 带 `TASK_SUB→交付` | +| 设计「关联项」可见需求 | create 带 `ASSOCIATED→需求`(与上互斥,同 create 只能一条) | +| 双挂 | 需个人 `x-yunxiao-token` OpenAPI;Cookie 路径**不得**声称成功 | + +产品默认:**子项优先**;关联项失败须在回报中标红并列出缺项。 + +校验子项: + +```http +GET /projex/api/workitem/v2/workitem/{交付id}/relation/workitem/list/by-relation-category?category=PARENT_SUB&isForward=true +``` + +结果须含对应分析/设计 identifier。 + +校验设计关联项: + +```http +GET /projex/api/workitem/v2/workitem/{设计id}/relation/workitem/list/by-relation-category?category=ASSOCIATED&isForward=true +``` + +`result` 须含原始需求 identifier。 + +### 无单快轨字段默认(2026-07-27) + +| 对象 | 规则 | +|---|---| +| 【设计】描述 | 复制需求 document HTML | +| 【设计】`79`/`80` | 当日 `12:00:00` / `23:59:59`,create 后 `field/value`(勿 create 同时带 79+80) | +| 【交付】描述 | 手工同步需求正文,或原型→AutoPRD;禁止无故占位 | +| 【交付】`79` | 创建当日;不写 `80` | +| 【交付】/【设计】标签 | 与需求相同,`PATCH propertyKey=tag` | +| 需求预计工时 | `time/estimate` **spentTime=2**(先删多余预估) | +| 需求实际工时 | `POST …/workitem/time`,body 用 **`actualTime`**(非 spentTime)+ `gmtStart`/`gmtEnd` epoch ms 字符串;见 `runtime-ids.json` `fields.actual_hours` | +| 描述更新 | `PATCH …/workitem/{id}/document`,`{"content":"","formatType":"RICHTEXT"}` | + +## 迭代挂接(已通 · 2026-07-27 · 只挂交付) + +创建迭代:`POST /projex/api/workspace/sprint`(必填 `staffIds`;可写 `capacityHours`)。 + +挂【交付】到迭代: + +```http +PATCH /projex/api/workitem/workitem/{交付id}?_input_charset=utf-8 +{"workitemIdentifier":"{交付id}","propertyKey":"sprint","propertyValue":"{sprintId}","operateType":"COVER"} +``` + +清空误挂(如需求):`propertyValue:""` + `operateType:"COVER"`。 + +**校验**必须读 `/extra`(详情主接口常不含 sprint 字段,禁止据此判失败): + +```http +GET /projex/api/workitem/workitem/{id}/extra?_input_charset=utf-8 +→ result.sprint[].identifier / name +``` + +产品规则:**只挂【交付】**;需求 / 分析 / 设计默认不挂(除非口令显式)。 + +### 极速建单注意 + +1. Cookie 只刷一次;全程纯 HTTP,默认**不开浏览器**。 +2. 交付建完后,【分析】与【设计】**并行**创建(均 TASK_SUB→交付);标准/快轨两树可并行。 +3. 状态用 `transit` + **本地追踪 fromStatus**(禁止每次 GET);负责人在交棒场景下**创建时即何斐**。 +4. 建单 `fieldValueList` 可带计划开始 `79`;**不要**在 create 同时写 `79+80`(同日会 400)。 +5. 标签必须 PATCH(create 带 tag 不落库);可与建子任务重叠;快轨交付/设计须与需求同标签。 +6. `requests.Session` keep-alive;**禁止**对共享 opener 加全局锁。 +7. 脚本入口:`scripts/live_create_fast.py`(v5:快轨描述/计划/标签/工时 2+2/设计 ASSOCIATED 补挂)。 diff --git a/.claude/skills/YunxiaoPM/references/live-perf-2026-07-23.md b/.claude/skills/YunxiaoPM/references/live-perf-2026-07-23.md new file mode 100644 index 0000000..a0f97d8 --- /dev/null +++ b/.claude/skills/YunxiaoPM/references/live-perf-2026-07-23.md @@ -0,0 +1,59 @@ +# 2026-07-23 真实建单复盘与极速优化 + +## 测试结论 + +标准 + 快轨均可建到「待开发」交棒;交付树、标签「故障管理」、快轨无【分析】均正确。 + +| 轮次 | 编号 | +|---|---| +| 第一轮(探测+UI) | ONEOS-141~147 | +| 极速 v1 | ONEOS-148~154 | +| 极速 v2 | ONEOS-164~177(含中间探针) | + +## 过程问题(已修) + +| # | 问题 | 根因 | 修复 | +|---|---|---|---| +| 1 | 状态改不动 / 假成功 | 误用 `updateStatus`;UI 点列表区 | `POST …/status/transit` | +| 2 | 负责人改不成 | `updateFieldValue` 错 | `PATCH …/{id}` + `assignedTo` | +| 3 | 状态 ID 不全 | 未抓网络 | 写入 `runtime-ids.json` | +| 4 | 极慢 | Playwright + 串行探测 | 纯 HTTP + 并行 | +| 5 | v1 仍偏慢 | 多余 GET、串行 PATCH 计划、交棒再改负责人 | 见下节 v2 | + +## 性能对比 + +| 口径 | 优化前(第一轮) | 极速 v1 | 极速 v2(本轮) | +|---|---|---|---| +| 会话墙钟 | ≈ **17.9 分钟** | — | — | +| 两路径并行建单墙钟 | ≈4.1 分钟自动化 | **4.4 s** | **2.2 s** | +| 标准单路径 | 失败反复 | 3.7 s | **2.2 s** | +| 快轨单路径 | 失败反复 | 3.7 s | **2.1 s** | +| 浏览器 | 多次 | 无 | 无 | + +相对第一轮会话 ≈ **490×**;相对 v1 再快约 **2×**。 + +## v2 压榨点(已落地) + +1. `status/transit` **本地追踪** `fromStatus`,跳过每次 GET。 +2. 建单 `fieldValueList` 带**计划开始(79)**;计划完成不在 create 写(同日会 400)。 +3. 【交付】创建时负责人直接 **何斐**(省 PATCH)。 +4. `requests.Session` keep-alive;**去掉全局锁**(否则并行失效)。 +5. 打标签与建子任务 **重叠**;两路径并行。 +6. 交棒后汇总用创建响应,校验 GET 移出主路径计时。 + +## 探针结论(未采用) + +| 尝试 | 结果 | +|---|---| +| create `fieldValueList` 带 tag | 建单成功但标签不落库 → 仍须 PATCH | +| create 同时写 79+80 | `400 计划…转化异常` | +| urllib 全局 lock + 单 opener | 并行被串行化,单路径 ≈4 s | + +## 仍可再压(收益变小) + +1. 标签 API 若将来支持 create 落库,可再少 2~4 次 PATCH。 +2. HTTP/2 或连接预热(DNS/TLS 复用到进程级)。 +3. 业务允许「建单即待开发」且平台支持初始状态 → 少 2 次 transit。 +4. 计划完成改为交棒后异步补写(当前故意跳过)。 + +脚本:`scripts/live_create_fast.py`(mode=`live_create_fast_v2`)。 diff --git a/.claude/skills/YunxiaoPM/references/make-export-attach.md b/.claude/skills/YunxiaoPM/references/make-export-attach.md new file mode 100644 index 0000000..e6bc09f --- /dev/null +++ b/.claude/skills/YunxiaoPM/references/make-export-attach.md @@ -0,0 +1,53 @@ +# Make 导出与截图附件 + +设计完成(步骤 4)且已关联原型页时:需求与【交付】均挂附件。任一侧失败 → 不得声称设计完成附件齐全。 + +## 1. 导出 HTML(含源码)ZIP + +与 Make「发布 → 导出 HTML(含源码)」对齐。 + +优先本地 Make Admin: + +```http +GET {adminOrigin}/api/export-html?path={prototypePath}&projectId={projectId}&includeSource=true +``` + +| 参数 | 要求 | +|---|---| +| `path` | 原型路径(如 `prototypes/oneos-h5-vehicle-assets`) | +| `includeSource` | **必须** `true` | +| 响应 | ZIP(`PK` 头);文件名建议 `{prototype-id}-html-source.zip` | + +失败时:提示用户在 Make 客户端对该原型执行「发布 → 导出 HTML(含源码)」,把 ZIP 路径发回。 +**禁止**用「仅对象存储链接」冒充已附带源码包。 + +## 2. 复制截图(全交互页) + +1. Make「发布 → **复制截图**」(或等价:导出该原型主界面及所有交互页截图)。 +2. 上传到需求附件与【交付】附件。 +3. 缺页/失败 → 列出缺项,不得报成功。 + +## 3. 挂载范围 + +同一 ZIP / 同一批截图: + +1. 上传到需求 identifier +2. 上传到【交付】任务 identifier + +优先 API;未知 upload 端点时用已登录浏览器在详情页「附件」上传。 + +## 4. 与对象存储的关系 + +| 产物 | 用途 | +|---|---| +| `{baseUrl}/{id}/index.html` | AutoPRD 描述内可点预览链接 | +| 导出 ZIP(含源码) | 附件,供开发离线打开 | +| 交互页截图 | 附件,供评审/开发对照 | + +三者职责不同,不可互相替代。 + +## 负向 + +- 不得导出错误原型页(须 Plan 确认原型路径/名称)。 +- 不得把别的需求的旧 ZIP 复用到新需求。 +- 快轨交棒默认不跑本附件流程(除非口令同时设计完成+原型)。 diff --git a/.claude/skills/YunxiaoPM/references/model.md b/.claude/skills/YunxiaoPM/references/model.md new file mode 100644 index 0000000..a59f700 --- /dev/null +++ b/.claude/skills/YunxiaoPM/references/model.md @@ -0,0 +1,60 @@ +# 交付树模型与关联约定 + +## 真相源 + +```text +需求状态 = 阶段看板唯一真相(分析中/设计中/待开发…) +【交付】任务 = 该需求的交付容器(每需求最多 1 条) +【分析】/【设计】 = YunxiaoPMapp 在交付下建的阶段子项 +【开发】/【测试】 = 另属开发 Skill / 测试 Skill,不进本包 +ASSOCIATED = 横向挂钩(【交付】 ↔ 需求) +SUB / TASK_SUB = 纵向拆解(交付 → 分析/设计) +``` + +## 关系验收 + +| 关系 | 用在哪里 | 验收 | 优先级 | +|---|---|---|---| +| ASSOCIATED | **仅【交付】** ↔ 需求 | 交付详情「关联项」能看到需求(**强制**) | 交付必过 | +| TASK_SUB | 【分析】/【设计】 → 【交付】 | 交付详情「子项」能看到阶段任务(**强制**) | 阶段任务必过 | + +建单规则(Cookie 路径 · 同一 create 只能一条 `createWorkitemRelationInfo`): + +| 对象 | `createWorkitemRelationInfo` | 另写字段 | +|---|---|---| +| 【交付】 | `ASSOCIATED` → 需求 | — | +| 【分析】/【设计】 | `TASK_SUB` → 交付 | `parent` + `parentIdentifier` = 交付 | + +**禁止**对分析/设计只写 `parentIdentifier` + `ASSOCIATED→需求`:关联项可能有,但交付「子项」仍为空(ONEOS-246/247 已复现)。 +勿用 `PARENT` 冒充关联项。细则见 [live-api.md](live-api.md)。 + +## 禁止 + +| 禁止 | 说明 | +|---|---| +| 创建或描述【开发】/【测试】 | 口令与回报均不出现 | +| 分析/设计未挂成交付子项(无 TASK_SUB) | 交付「子项」为 0,交棒验收失败 | +| 用任务名称/标题做唯一性或查重 | 不得 `subject` 匹配复用 | +| 同一需求认定多个交付任务编号 | 列出编号请人合并后再继续 | +| 功能模块标签用「分析/设计/交付」 | 模块标签打在需求(可选交付)上 | + +## 任务标题(仅展示) + +- 【交付】+ 需求标题 +- 【分析】+ 需求标题 +- 【设计】+ 需求标题 + +标题可改,**不参与查重**。 + +## 任务编号唯一性 + +**唯一允许的查重/复用渠道:云效任务编号**(如 `ONEOS-99` / `serialNumber`)。 + +| 场景 | 正确 | 错误 | +|---|---|---| +| 首次创建【交付】 | 建单成功后回报并写入「工作项编号(系统)」 | 下次用标题搜 | +| 复用【交付】 | 口令或编号区块带 `交付任务=ONEOS-xx` | list 后按 subject 相等 | +| 判断是否已有交付 | 编号区块或交付 ASSOCIATED 编号集合 | 数【交付】开头标题 | +| 分析/设计幂等 | 仅已登记编号 | 按【分析】+需求标题 | + +按编号命中则复用;**计划开始已有值禁止改写**。 diff --git a/.claude/skills/YunxiaoPM/references/number-push.md b/.claude/skills/YunxiaoPM/references/number-push.md new file mode 100644 index 0000000..284f91d --- /dev/null +++ b/.claude/skills/YunxiaoPM/references/number-push.md @@ -0,0 +1,35 @@ +# 编号直推到待开发 + +适用:标准路径已建过【分析】和/或【设计】,用**任务编号**一口气推到待开发交棒。 + +## 入口(只认编号) + +```text +交棒开发:需求=ONEOS-xx;交付任务=ONEOS-a;分析任务=ONEOS-b;设计任务=ONEOS-c +交棒开发:分析任务=ONEOS-b → 由分析 ASSOCIATED/SUB 反查需求与交付 +交棒开发:设计任务=ONEOS-c → 同上反查 +``` + +禁止用标题猜。编号对不上关联树 → 停下列出关联,不瞎改。 + +## 动作表 + +| 对象 | 动作 | +|---|---| +| 需求 | 状态 → **待开发**(按云效连跳) | +| 【交付】 | **必须**用交付任务编号定位;负责人→何斐;不改计划开始;不写计划完成;描述仍占位则保持(除非同时设计完成+原型+AutoPRD) | +| 【分析】 | 若有编号:计划完成若为空 → 写当日并推工时;**不新建**第二条 | +| 【设计】 | 若有编号:计划完成若为空 → 写当日并推工时;**不新建**第二条 | +| 仅有分析、尚无设计 | **不强制补建【设计】**(与无单快轨区分);回报注明「无设计任务编号」 | +| 仅有设计、无分析 | 收口设计 + 交棒;**不补建分析** | + +## 与无单快轨区别 + +| | 无单快轨 | 编号直推 | +|---|---|---| +| 前提 | 尚无分析/设计阶段单(或明确跳过分析) | 已有分析和/或设计**任务编号** | +| 【分析】 | 禁止创建 | 有编号则收口,无则不建 | +| 【设计】 | 必须新建且当日收口 | 有编号则收口;仅分析无设计时不强制新建 | +| 定位 | 新建后登记编号 | **仅任务编号**定位/反查 | + +仍不创建【开发】/【测试】。交棒占位风险见 [handoff-and-rollback.md](handoff-and-rollback.md)。 diff --git a/.claude/skills/YunxiaoPM/references/project-selection.md b/.claude/skills/YunxiaoPM/references/project-selection.md new file mode 100644 index 0000000..2a8e516 --- /dev/null +++ b/.claude/skills/YunxiaoPM/references/project-selection.md @@ -0,0 +1,73 @@ +# 门禁 PJ · 云效项目选择(强制点选) + +凡 **新建需求 / 新建任务树 / 创建迭代 / 其它写入依赖 `spaceIdentifier`** 的操作,项目必须由用户从云效实时列表**点选**。禁止静默使用 `runtime-ids.json` 里的默认 `project.spaceIdentifier`。 + +## 何时触发 + +| 场景 | 是否必须选项目 | +|---|---| +| 记录需求 / 无单快轨新建 | **必须** | +| 创建迭代(新挂项目空间) | **必须**(与需求同项目时沿用已锁定项,仍须在 Plan 写出项目名+ID) | +| 仅推进已有编号(受理确认、开始分析…) | **不必重选**;以该编号所在项目为准,Plan 写出项目名 | +| 只读查询 | 不强制 | + +## 执行顺序(建单前) + +1. **实时拉取**云效项目列表(Cookie + XSRF): + +```http +GET /projex/api/workspace/project/search/list + ?category=Project&scope=all&toPage=1&pageSize=100 + &conditions={"conditionGroups":[[]]} + &extraConditions=…(可与页面一致:当前用户参与 + public) + &orderBy={"fieldIdentifier":"gmtCreate","format":"input","order":"desc","className":"date"} + &_input_charset=utf-8 +``` + +2. 将结果整理为单选列表:`{name}({customCode})`;选项 id 用 `identifier`(spaceId)。 +3. **AskQuestion / Plan 单选**「PJ. 云效项目」;未点选 → **禁止 create**。 +4. 「批准 Plan」≠ 已选项目;Plan 里仍是空选项或写「默认 01_ONEOS」而未点选 → 停。 +5. 用户点选后:本轮 apply 全程使用该 `spaceIdentifier`;回报写清项目名 + customCode + spaceId。 +6. 拉取成功后可回写 `assets/runtime-ids.json` → `projects_catalog`(缓存);**缓存不得当作已选项**。 + +## 无法对应 → 自动重拉一次 + +「无法对应」任一成立即触发: + +| # | 情形 | +|---|---| +| 1 | 口令/预填的项目名、customCode、别名在**当前列表**中 0 命中 | +| 2 | 口令给出的 spaceId / identifier 不在当前列表 | +| 3 | 用户点选的选项 id 在 apply 前校验时已不在列表(列表过期) | +| 4 | 仅命中 `projects_catalog` 缓存、与本次实时列表不一致 | +| 5 | 首次拉取失败后改用了缓存,用户按缓存点选后 create 报项目/空间无效 | + +**动作(固定一次):** + +1. **立即再调一次**同一 `project/search/list`(强制网络,不用缓存)。 +2. 用新列表重新做匹配 / 刷新 Plan 单选选项;可回写 `projects_catalog`。 +3. 回报注明:`已自动重拉项目列表(因无法对应)`。 +4. **仍无法对应** → 停,展示最新列表请用户重选;**禁止**再自动拉第 3 次;**禁止**猜一个 spaceId 继续建单。 + +匹配规则(名/码):忽略大小写;`name` 全等或包含;`customCode` 全等;`name_aliases` 全等。多命中视为无法唯一对应 → 重拉后仍多命中则请用户点选,不自动选定。 + +## 禁止 + +- 未询问就写入 `project.spaceIdentifier`(即便 catalog 标了 `suggested`) +- 按口令里的「统一运营管理平台」字符串静默映射而不展示列表点选(口令仅作**预填建议**,仍须用户确认点选) +- API 失败时擅自沿用上次默认;应展示 `projects_catalog` 缓存并标明「离线缓存,请确认」,仍须点选;用户确认后若仍无法对应 → 走上一节「自动重拉一次」 +- 多项目并行建单却共用一个未确认的 spaceId +- 「无法对应」时循环重拉超过 1 次,或跳过点选直接建单 + +## 口令预填 + +```text +记录需求:…;项目=01_ONEOS;… +``` + +若口令已含项目名/编号前缀且在实时列表中**唯一命中**:Plan 可预勾该选项,但仍须用户确认;0 命中或多命中 → **先自动重拉一次**再匹配;仍 0/多 → 不预勾,只展示最新列表。 + +## 脚本 + +`scripts/list_projects.py`:stdout JSON `projects[]`。 +带查询时:`python3 scripts/list_projects.py --match '01_ONEOS'` —— 0 命中/多命中则自动重拉一次再匹配(`refetched: true`)。 diff --git a/.claude/skills/YunxiaoPM/references/record-meta-fields.md b/.claude/skills/YunxiaoPM/references/record-meta-fields.md new file mode 100644 index 0000000..90469f9 --- /dev/null +++ b/.claude/skills/YunxiaoPM/references/record-meta-fields.md @@ -0,0 +1,58 @@ +# 记录需求 · 元字段(优先级 / 标签 / 提交部门 / 提交人) + +建单前消费网页 / AutoRDO / 口令中的四类元信息。细则与 `assets/runtime-ids.json` 对齐;**禁止臆造未验证的 fieldIdentifier**。 + +## 口令形态 + +```text +记录需求:…;优先级=紧急|高|中|低;标签=…;提交部门=…;提交人=…;推进至=… +``` + +四字段均可选出现;网页或 AutoRDO 提示里已带的值与口令等价。 + +## Plan 是否追问 + +记录需求 **一律**走 [compact-select.md](compact-select.md) 字母表(类型/项目/优先级/标签),即使用户口令已写齐;给出建议压缩串,等用户回 `1a2b3a4d`(或改字母)并「执行」。 + +| 条件 | 行为 | +|---|---| +| 口令已含类型/项目/优先级/标签 | 对应选项标 ★ + 建议压缩串;**仍须**压缩确认或显式点选 | +| 标签名 0 命中 / 多命中 | **自动重拉一次**标签候选并重生 4.A/B/C…;仍失败则停 | +| 仅缺提交部门/提交人 | 压缩 1–4 确认后,缺则追问或写入描述页脚 | +| 仅缺「推进至」等 | 按既有口令规则;可作 5. 题字母表 | + +## 写入策略 + +### 优先级(已验证) + +- 映射:`runtime-ids.json` → `priority`(紧急 / 高 / 中 / 低)。 +- create 时写入 `fieldIdentifier: "priority"`(与 [live-api.md](live-api.md) / `live_create_fast.py` 一致)。 +- 未给优先级时:Plan 追问;脚本默认「中」仅作既有兜底,口令/网页有值时以用户值为准。 + +### 标签(已验证) + +- 映射:`runtime-ids.json` → `tags`(按显示名取 tagId)。 +- 标签须 **PATCH** `propertyKey: "tag"`(create 带 tag 不落库)。 +- 口令/网页给出的标签名若不在候选:按 [compact-select.md](compact-select.md) **自动重拉一次**标签列表并重生选项;仍无则停在 Plan,勿猜 ID。 + +### 提交部门 / 提交人(已验证 · 2026-07-27 · ONEOS-293) + +| 字段 | fieldIdentifier | +|---|---| +| 提交部门 | `3132597a9718d1c282b7ba5a0c` | +| 提交人 | `9e01269e96f91fbb97d36bf5b3` | + +| 规则 | 说明 | +|---|---| +| **写入** | `POST /projex/api/workitem/workitem/field/value/{workitemId}`,`Content-Type: application/x-www-form-urlencoded`,参数 `fieldValueList` = JSON 数组字符串 `[{"fieldIdentifier","value"}]` | +| **形态** | 均为普通文本 input(非人员选择器);口令值原样写入 | +| **页脚** | 仍可在描述页脚重复一份,便于检索;**不能替代**字段写入 | +| **计划开始** | 字段 `79`,同一 `field/value` API;推荐 `YYYY-MM-DD 12:00:00`。快轨:【交付】/【设计】创建当日写入;【设计】另写计划完成 `80`=`当日 23:59:59` | +| **预计工时** | 字段 `101586` **禁止**直接 PATCH;用 `POST …/time/estimate`(`spentTime`)登记;删多余用 `DELETE …/time/estimate/{workitemId}/{estimateId}` | +| **实际工时** | `POST …/workitem/time`,body **`actualTime`**(非 spentTime)+ `gmtStart`/`gmtEnd`(epoch ms 字符串);快轨待开发需求默认 **2** | +| **描述更新** | `PATCH …/workitem/{id}/document`,`{"content","formatType":"RICHTEXT"}` | +| **快轨标签** | 【交付】【设计】建单后 `PATCH propertyKey=tag`,`propertyValue` 与需求 tagId 一致(多标签逗号拼接) | + +## 与描述双段的关系 + +页脚元信息写在「原始诉求」段末或整篇描述末尾均可,**不得覆盖** `## 产品说明(AutoPRD)` / `## 工作项编号(系统)` 区块(见 [description-split.md](description-split.md))。 diff --git a/.claude/skills/YunxiaoPM/references/sprint.md b/.claude/skills/YunxiaoPM/references/sprint.md new file mode 100644 index 0000000..4506e39 --- /dev/null +++ b/.claude/skills/YunxiaoPM/references/sprint.md @@ -0,0 +1,63 @@ +# 创建迭代并关联交付 + +产品经理可将已交棒(或已有编号)的多条【交付】任务打进同一云效迭代。 + +## 口令 + +```text +创建迭代:版本类型=副;交付任务=ONEOS-a,ONEOS-b,ONEOS-c;名称前缀=统一运营管理平台PC端 +创建迭代:版本类型=子;交付任务=ONEOS-99;名称前缀=统一运营管理平台PC端 +``` + +| 参数 | 规则 | +|---|---| +| 交付任务 | **1..N 个任务编号**(逗号分隔);禁止用标题凑数 | +| 版本类型 | 主 / 副 / 子;未点选则停下询问,禁止默认猜 | +| 名称前缀 | 可选;缺省 `统一运营管理平台PC端`。最终名=`{前缀}{版本号}` | + +## 版本号 `V{主}.{副}.{子}` + +| 版本类型 | 含义 | 递增 | +|---|---|---| +| **主** | 功能重制 | 主+1,副→0,子→0(V1.3.2→V2.0.0) | +| **副** | 新功能上线 | 副+1,子→0(V1.3.2→V1.4.0) | +| **子** | bug/优化 | 子+1(V1.3.2→V1.3.3) | + +### 取基线再递增 + +1. 拉取当前项目云效迭代列表。 +2. 从迭代**名称**解析 `V数字.数字.数字`;旧名 `V数字.数字` 视为 `.0`。 +3. 同名称前缀内取**最大**版本为基线(主→副→子比较);避免 PC 与小程序串号。 +4. 按用户点选类型递增,写入新迭代名称。 +5. 无一可解析版本:基线 `V0.0.0` 再递增(主→V1.0.0,副→V0.1.0,子→V0.0.1);或口令显式给起始版本。 + +禁止:手填与规则冲突的版本号却声称自动生成;禁止用交付标题推断版本类型。 + +## 创建与关联 + +| 步骤 | 动作 | +|---|---| +| 1 | 算出新版本 → 拼迭代全名 | +| 2 | 创建迭代(起止日期口令未给则询问或用项目默认,禁止瞎填) | +| 3 | **只挂【交付】**:N 个交付任务挂迭代;交付失败不得报完成 | +| 4 | 幂等:同名迭代已存在 → 不新建,补挂尚未关联的交付,回报「复用迭代」 | +| 5 | 回报:迭代名、版本号、identifier、已关联交付编号、失败编号 | + +**禁止**把需求挂进迭代(需求侧「迭代」字段保持空)。不挂【分析】/【设计】除非口令显式点名。 +**不**在本步创建【开发】/【测试】。仍须 Plan 门禁。 + +### 挂接 API(已通 · 校验走 `/extra`) + +```http +PATCH /projex/api/workitem/workitem/{交付id}?_input_charset=utf-8 +{"workitemIdentifier":"{交付id}","propertyKey":"sprint","propertyValue":"{sprintId}","operateType":"COVER"} +``` + +校验(详情主接口**不**含 sprint,勿用其判空): + +```http +GET /projex/api/workitem/workitem/{id}/extra?_input_charset=utf-8 +→ result.sprint[].identifier +``` + +清空误挂的需求迭代:同上 PATCH,`propertyValue:""` + `operateType:"COVER"`(或 `CLEAR`)。 diff --git a/.claude/skills/YunxiaoPM/references/stage-flow.md b/.claude/skills/YunxiaoPM/references/stage-flow.md new file mode 100644 index 0000000..e782f18 --- /dev/null +++ b/.claude/skills/YunxiaoPM/references/stage-flow.md @@ -0,0 +1,63 @@ +# 标准路径:待处理 → 待开发交棒 + +人员/状态等常量见 [../assets/runtime-ids.json](../assets/runtime-ids.json)。**项目空间**须按 [project-selection.md](project-selection.md) 点选,禁止默认直指。查重只用任务编号。 + +## 步骤 0|创建需求 + +| 云效动作 | 落地 | +|---|---| +| 新建产品类需求 | POST 建单;标题 `【新增】`/`【优化】` | +| 需求描述 | **先** AutoRDO 清洗再写入 `## 原始诉求(AutoRDO)`;本步不写 AutoPRD | +| 默认状态 | **待处理**;不建任务、不改负责人 | +| 可选推进 | 口令点选目标状态;未选则停在待处理 | + +## 步骤 1|受理 → 已确认 + +| 云效动作 | 落地 | +|---|---| +| 状态 | 待处理 → **已确认** | +| 任务 | **仍不建**交付/分析/设计 | + +若用户只「创建」未受理:只提示「受理后请推进至已确认」,不自动跳。 + +## 步骤 2|分析中 + +| 对象 | 动作 | +|---|---| +| 【交付】 | 无则新建;标题 `【交付】`+需求标题;**ASSOCIATED→需求**(勿用 PARENT);负责人=创建人/产品;**计划开始仅空时写当日**;不写计划完成;描述=`等待设计任务完成后自动填入`;更新编号区块 | +| 【分析】 | 新建;**TASK_SUB→交付**(`parent`+`parentIdentifier`+`createWorkitemRelationInfo=TASK_SUB`);交付「子项」须可见;计划开始=当日(仅空时);负责人默认可=创建人;更新编号区块 | + +| 需求 | 状态=`分析中` | + +## 步骤 3|设计中 + +| 对象 | 动作 | +|---|---| +| 【交付】 | 已存在则按编号复用;没有则补建;**不改交付计划开始**;不写交付计划完成 | +| 【设计】 | 新建;**TASK_SUB→交付**(同分析);计划开始=当日(仅空时);更新编号区块 | +| 【分析】 | **计划完成**=状态更新日;推算阶段日历工时(见 work-hours) | +| 需求 | 状态=`设计中` | + +## 步骤 4|设计完成 + +| 对象 | 动作 | +|---|---| +| 【设计】 | 计划完成=当日;推算工时;任务→完成态 | +| 需求 | AutoPRD 写入 `## 产品说明`;挂 ZIP+截图;状态=`设计完成` | +| 【交付】 | 不换负责人;不改计划开始;不写计划完成;描述改为 AutoPRD 正文;同样挂附件 | + +顺序与失败门禁见 [description-split.md](description-split.md)。 + +## 步骤 5|待开发交棒(本 Skill 终点 · 标准路径) + +| 对象 | 动作 | +|---|---| +| 需求 | 状态=`待开发` | +| 【交付】 | 按**任务编号**定位;**负责人→何斐**;不改计划开始;不写计划完成 | +| 【分析】/【设计】 | 不新建;不改已有计划开始;前序未收口按步骤 3/4 处理 | + +执行 [handoff-and-rollback.md](handoff-and-rollback.md) §交棒门禁。 + +**不做:** 【开发】/【测试】、仓库、分支、提测。 + +快轨 / 编号直推见专文。创建迭代见 [sprint.md](sprint.md)。 diff --git a/.claude/skills/YunxiaoPM/references/work-hours.md b/.claude/skills/YunxiaoPM/references/work-hours.md new file mode 100644 index 0000000..b121161 --- /dev/null +++ b/.claude/skills/YunxiaoPM/references/work-hours.md @@ -0,0 +1,73 @@ +# 计划时间与阶段日历工时 + +适用于【交付】/【分析】/【设计】统一口径。 + +## 用途 + +| 对象 | 计划开始→计划完成 | +|---|---| +| 【交付】 | 需求开工→上线全周期(本 Skill **不**写交付计划完成;上线由后续段) | +| 【分析】 | 分析阶段时长 | +| 【设计】 | 设计阶段时长 | + +## 计划开始(不可篡改) + +| 对象 | 首次写入 | 之后 | +|---|---|---| +| 【交付】 | 首次进入分析中且字段为空 → 当日 | **禁止覆盖** | +| 【分析】 | 首次创建且为空 → 当日 | **禁止覆盖** | +| 【设计】 | 首次创建且为空 → 当日 | **禁止覆盖** | + +日期写入形态:中国时区正午 epoch ms 字符串,见 [../assets/runtime-ids.json](../assets/runtime-ids.json) `fields.plan_start`。 + +## 计划完成 + +| 环节 | 写计划完成的对象 | +|---|---| +| 推进到设计中 | 【分析】= 状态更新日 | +| 推进到设计完成 | 【设计】= 状态更新日 | +| 无单快轨建设计 | 【设计】= 当日(同轮收口) | +| 编号直推收口 | 口令涉及的分析/设计若计划完成为空 → 当日 | +| 上线/关闭 | 【交付】计划完成(**本 Skill 暂不写**) | + +## 预计工时 = 阶段日历工时(Lead Time) + +有计划完成且已有计划开始时,写入该对象云效「预计工时」: + +```text +workdays = 0 +从计划开始日到计划完成日(含首尾)逐日: + 周六或周日 → 跳过(除非该日是调休补班) + 法定放假日 → 跳过 + 调休补班日(含周末上班)→ 计入 + 其余周一~周五 → 计入 +预计工时(小时) = workdays × 8 +``` + +**禁止**用「(结束日 − 开始日 + 1)× 8」日历天数算法。 + +### 语义与脚注(强制) + +- 正式名称:**阶段日历工时**,**不是**投入人天/排期负荷。 +- 任务描述末尾固定脚注: + `【系统】预计工时=阶段日历工时(工作日×8),非人力投入预估` +- 回报写「阶段日历工时 Hh」,**禁止**写「需投入 Hh」。 +- 本 Skill **不自动写**「人力投入预估」。 + +### 日历数据源 + +- 使用 [../assets/cn-workday-calendar.json](../assets/cn-workday-calendar.json)(按自然年;跨年须覆盖起止两侧年份)。 +- 缺当年日历 → **停下**提示补日历;**禁止**静默按「仅去周末」算完并报成功。 +- 计算脚本:[../scripts/workday_hours.py](../scripts/workday_hours.py) + +```bash +python3 scripts/workday_hours.py 2026-03-06 2026-03-09 +``` + +缺计划开始则只写结束、不推工时,回报提示。 + +## 示例 + +- 开始=周五、结束=下周一(无节假日)→ 五+一 → 16h +- 区间全在法定放假内 → 0h +- 某周日为调休补班且落在区间内 → 该日计入 8h diff --git a/.claude/skills/YunxiaoPM/references/workitem-ids.md b/.claude/skills/YunxiaoPM/references/workitem-ids.md new file mode 100644 index 0000000..e35fb26 --- /dev/null +++ b/.claude/skills/YunxiaoPM/references/workitem-ids.md @@ -0,0 +1,22 @@ +# 工作项编号(系统) + +## 权威源 + +需求描述固定区块(机器可解析;人工只读即可): + +```markdown +## 工作项编号(系统) +- 交付:ONEOS-xx +- 分析:ONEOS-yy(无则写无) +- 设计:ONEOS-zz(无则写无) +``` + +## 规则 + +1. **禁止**仅靠本地会话映射当唯一真相。 +2. 每次新建/登记交付·分析·设计编号后,**立即 PATCH 更新该区块**(只改本区块,不动 AutoRDO / AutoPRD 正文)。 +3. 操作顺序:口令显式编号 > 读本区块 > ASSOCIATED/SUB 反查校验。 +4. 三者冲突 → 停下人工;**仍禁止按标题猜**。 +5. 本地缓存仅加速,启动以云效该区块为准。 +6. 区块出现两个交付编号 → 停止并列号请人合并。 +7. 取消需求:关联任务标取消/废止;**不删**本区块(留痕)。 diff --git a/.claude/skills/YunxiaoPM/runtime-ids.json b/.claude/skills/YunxiaoPM/runtime-ids.json new file mode 100644 index 0000000..d4579ac --- /dev/null +++ b/.claude/skills/YunxiaoPM/runtime-ids.json @@ -0,0 +1,221 @@ +{ + "schema_version": 1, + "skill": "YunxiaoPM", + "verified_at": "2026-07-25", + "note": "YunxiaoPM constants. Project space MUST be user-selected (PJ gate); project.spaceIdentifier is last-known cache only — never auto-apply. See references/project-selection.md.", + "project": { + "selection_mode": "user_pick_required", + "name": null, + "name_aliases": [ + "01_ONEOS", + "ONEOS", + "统一运营管理平台", + "统一运营管理平台PC端", + "统一运营管理平台 PC 端" + ], + "spaceIdentifier": null, + "customCode": null, + "spaceType": "Project", + "organizationIdentifier": "697c54a19df7fdfa65466405", + "default_sprint_name_prefix": "统一运营管理平台PC端", + "list_api": "GET /projex/api/workspace/project/search/list", + "last_selected": { + "name": "01_ONEOS", + "spaceIdentifier": "1280be963a5a2cc126a4118dca", + "customCode": "ONEOS", + "note": "历史常用项;仅作预填建议,禁止未点选即使用" + }, + "refreshed_at": "2026-07-25" + }, + "projects_catalog": [ + { + "name": "05_羚牛碳资产平台", + "identifier": "ff104a3bce09463da136a97999", + "customCode": "CARBON" + }, + { + "name": "06_对外客户项目", + "identifier": "baca8b4d676c5af67e475caec0", + "customCode": "CUST" + }, + { + "name": "07_LNBOX", + "identifier": "35e1e915a052379bbdfb993aac", + "customCode": "LNBOX" + }, + { + "name": "04_AI应用", + "identifier": "d9002ea72c6c3a1a97b02be750", + "customCode": "AIAPP" + }, + { + "name": "03_数据中台", + "identifier": "a801cef5c9a68fa051c07432c7", + "customCode": "DATA" + }, + { + "name": "02_小羚羚APP", + "identifier": "db771a2cca07bed43b369af077", + "customCode": "XLLAPP" + }, + { + "name": "01_ONEOS", + "identifier": "1280be963a5a2cc126a4118dca", + "customCode": "ONEOS" + }, + { + "name": "敏捷研发示例项目", + "identifier": "65eca0c2e16a23939081e19e14", + "customCode": "DEMO" + } + ], + "people": { + "wangmian": { + "displayName": "王冕", + "identifier": "6811df000601d2fea60144a9", + "role": "产品创建人/默认负责人" + }, + "hefei": { + "displayName": "何斐", + "identifier": "695f0400562f09713f9c3a93", + "role": "待开发交棒【交付】负责人" + } + }, + "tags": { + "故障管理": "ceb526a7343995577645317e9a", + "还车应结款": "4204960ce658c94b17abb7c5f6", + "工作台": "b62d21beac55c0b43389915eab", + "交车管理": "b6947b5aca82a8c759612ba039", + "合同管理": "23873be81931cb0dc1bf87a456", + "安全培训": "76988b5f73ef515de5930d59b9", + "证照管理": "11f4c2ec65901a8f3199c81706", + "还车管理": "1e0fce2d6929ff4ac13b310e97" + }, + "status": { + "req": { + "待处理": "100005", + "已确认": "32", + "分析中": "154395", + "设计中": "156603", + "设计完成": "307012", + "待开发": "1582fc929d429111b925309493" + }, + "task": { + "待处理": "100005", + "已完成": "100014" + }, + "transit_api": "POST /projex/api/workitem/workitem/{id}/status/transit", + "fast_handoff_hops": [ + "设计完成", + "待开发" + ], + "note": "见 references/live-api.md;禁止再用 updateStatus" + }, + "workitem_types": { + "product_req": { + "name": "产品类需求", + "category": "Req", + "identifier": "9uy29901re573f561d69jn40" + }, + "task": { + "name": "任务", + "category": "Task", + "identifier": "ba102e46bc6a8483d9b7f25c" + } + }, + "priority": { + "紧急": "646004e97f54bb77fec7b455df", + "高": "95b89e0a524d9693e1f335ffe5", + "中": "fa155d1214f9f8db222d39db3b", + "低": "92924feff9c1085891e7511872" + }, + "fields": { + "plan_start": { + "fieldIdentifier": "79", + "name": "计划开始时间", + "value_shape": "YYYY-MM-DD HH:mm:ss China wall time preferred; epoch ms string also accepted", + "example": "2026-07-27 12:00:00", + "update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON" + }, + "plan_end": { + "fieldIdentifier": "80", + "name": "计划完成时间", + "value_shape": "YYYY-MM-DD HH:mm:ss or epoch ms string", + "update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON" + }, + "estimated_hours": { + "fieldIdentifier": "101586", + "name": "预计工时", + "note": "不可直接改字段;须工时预估登记", + "create_api": "POST /projex/api/workitem/workitem/time/estimate body spentTime,type,recordUserIdentifier,workitemIdentifier", + "delete_api": "DELETE /projex/api/workitem/workitem/time/estimate/{workitemId}/{estimateId}", + "list_api": "GET /projex/api/workitem/workitem/time/estimate/list?workitemIdentifier=" + }, + "actual_hours": { + "name": "实际工时", + "note": "快轨待开发默认 2", + "create_api": "POST /projex/api/workitem/workitem/time body actualTime,type,recordUserIdentifier,workitemIdentifier,gmtStart,gmtEnd (epoch ms string)", + "list_api": "GET /projex/api/workitem/workitem/time/list?workitemIdentifier=", + "fast_track_default": 2, + "verified_at": "2026-07-27", + "verified_on": "ONEOS-293" + }, + "fast_track": { + "req_estimated_hours": 2, + "req_actual_hours": 2, + "design_plan_start_end_same_day": true, + "design_description": "copy_req_document", + "delivery_description": "manual_sync_or_autoprd", + "delivery_design_same_tags_as_req": true, + "design_associated_to_req_after_task_sub": true, + "document_update_api": "PATCH /projex/api/workitem/workitem/{id}/document {content,formatType:RICHTEXT}" + }, + "submit_department": { + "fieldIdentifier": "3132597a9718d1c282b7ba5a0c", + "name": "提交部门", + "format": "string input", + "update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON", + "verified_at": "2026-07-27", + "verified_on": "ONEOS-293" + }, + "submitter": { + "fieldIdentifier": "9e01269e96f91fbb97d36bf5b3", + "name": "提交人", + "format": "string input", + "update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON", + "verified_at": "2026-07-27", + "verified_on": "ONEOS-293" + }, + "tag": { + "fieldIdentifier": "tag", + "name": "标签", + "propertyKey": "tag" + } + }, + "assignee_rules": { + "待开发_交付任务": "hefei", + "分析中_交付与分析": "creator", + "设计中_设计": "creator" + }, + "relation": { + "delivery_to_req": "ASSOCIATED", + "stage_to_delivery": "TASK_SUB", + "create_field": "createWorkitemRelationInfo", + "forbid": [ + "PARENT_as_关联项", + "ASSOCIATED_only_for_stage_tasks_without_TASK_SUB" + ], + "note": "交付必须 ASSOCIATED→需求。分析/设计默认 TASK_SUB→交付(子项 tab)。二者同 create 互斥。见 references/live-api.md" + }, + "delivery_placeholder": "等待设计任务完成后自动填入", + "task_title_prefixes": { + "delivery": "【交付】", + "analysis": "【分析】", + "design": "【设计】" + }, + "publish_url": { + "shape": "{baseUrl}/{prototype-id}/index.html", + "forbid_prototypes_prefix": true, + "require_index_html": true + } +} diff --git a/.claude/skills/YunxiaoPM/scripts/list_projects.py b/.claude/skills/YunxiaoPM/scripts/list_projects.py new file mode 100644 index 0000000..9a7efd2 --- /dev/null +++ b/.claude/skills/YunxiaoPM/scripts/list_projects.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +"""拉取云效项目列表(供 YunxiaoPMapp 门禁 PJ 点选)。stdout = JSON。 + +无法对应时自动重拉一次: + python3 scripts/list_projects.py --match '01_ONEOS' + python3 scripts/list_projects.py --match-id 1280be963a5a2cc126a4118dca +""" +from __future__ import annotations + +import argparse +import json +import urllib.parse +from datetime import datetime, timedelta, timezone +from pathlib import Path +from typing import Any + +import requests + +ROOT = Path(__file__).resolve().parents[1] +RUNTIME = json.loads((ROOT / "assets" / "runtime-ids.json").read_text()) +ORG = RUNTIME.get("project", {}).get("organizationIdentifier", "697c54a19df7fdfa65466405") +WANG = RUNTIME.get("people", {}).get("wangmian", {}).get("identifier", "6811df000601d2fea60144a9") +ALIASES = [a.lower() for a in (RUNTIME.get("project", {}).get("name_aliases") or [])] +TZ = timezone(timedelta(hours=8)) + + +def load_jar() -> dict[str, str]: + jar: dict[str, str] = {} + try: + import browser_cookie3 + + for domain in (".aliyun.com", "devops.aliyun.com", ".devops.aliyun.com"): + try: + for c in browser_cookie3.chrome(domain_name=domain): + jar[c.name] = c.value + except Exception: + pass + except Exception: + pass + if not jar.get("XSRF-TOKEN"): + p = Path("/tmp/yunxiao_cookies.json") + if p.exists(): + raw = json.loads(p.read_text()) + jar = ( + raw + if isinstance(raw, dict) and "XSRF-TOKEN" in raw + else {c["name"]: c["value"] for c in raw.get("cookies", [])} + ) + if not jar.get("XSRF-TOKEN"): + raise RuntimeError("缺少 XSRF-TOKEN:请先在 Chrome 登录 devops.aliyun.com") + return jar + + +def fetch_projects() -> list[dict[str, Any]]: + jar = load_jar() + x = jar.get("XSRF-TOKEN", "") + xsrf = urllib.parse.unquote(x) if "%" in x else x + cookie = "; ".join(f"{k}={v}" for k, v in jar.items()) + headers = { + "Cookie": cookie, + "x-xsrf-token": xsrf, + "X-XSRF-TOKEN": xsrf, + "Origin": "https://devops.aliyun.com", + "Referer": "https://devops.aliyun.com/projex", + "accept": "application/json", + } + extra = json.dumps( + { + "conditionGroups": [ + [ + { + "className": "user", + "fieldIdentifier": "users", + "format": "multiList", + "operator": "CONTAINS", + "value": [WANG], + } + ], + [ + { + "className": "string", + "fieldIdentifier": "scope", + "format": "list", + "operator": "CONTAINS", + "value": ["public"], + } + ], + ] + }, + separators=(",", ":"), + ) + params = { + "extraConditions": extra, + "conditions": json.dumps({"conditionGroups": [[]]}, separators=(",", ":")), + "orderBy": json.dumps( + { + "fieldIdentifier": "gmtCreate", + "format": "input", + "order": "desc", + "className": "date", + }, + separators=(",", ":"), + ), + "scope": "all", + "category": "Project", + "toPage": 1, + "pageSize": 100, + "_input_charset": "utf-8", + } + r = requests.get( + "https://devops.aliyun.com/projex/api/workspace/project/search/list", + headers=headers, + params=params, + timeout=30, + ) + r.raise_for_status() + rows = r.json().get("result") or [] + return [ + { + "name": p.get("name"), + "identifier": p.get("identifier"), + "customCode": p.get("customCode"), + "logicalStatus": p.get("logicalStatus"), + "status": (p.get("status") or {}).get("displayName"), + "scope": p.get("scope"), + } + for p in rows + ] + + +def match_projects( + projects: list[dict[str, Any]], + *, + query: str | None = None, + space_id: str | None = None, +) -> list[dict[str, Any]]: + if space_id: + sid = space_id.strip() + return [p for p in projects if p.get("identifier") == sid] + if not query: + return [] + q = query.strip().lower() + hits: list[dict[str, Any]] = [] + for p in projects: + name = (p.get("name") or "").lower() + code = (p.get("customCode") or "").lower() + if q == name or q == code or q in name or name in q: + hits.append(p) + continue + if q in ALIASES and (code == "oneos" or "oneos" in name or "运营" in name): + hits.append(p) + # de-dupe by identifier + seen: set[str] = set() + out: list[dict[str, Any]] = [] + for p in hits: + i = p.get("identifier") or "" + if i in seen: + continue + seen.add(i) + out.append(p) + return out + + +def list_projects( + *, + match: str | None = None, + match_id: str | None = None, +) -> dict: + projects = fetch_projects() + payload: dict[str, Any] = { + "fetched_at": datetime.now(TZ).isoformat(), + "organizationIdentifier": ORG, + "count": len(projects), + "projects": projects, + "selection_required": True, + "refetched": False, + "note": "禁止默认直指;须 AskQuestion/Plan 点选后再写入 spaceIdentifier", + } + if not match and not match_id: + return payload + + hits = match_projects(projects, query=match, space_id=match_id) + if len(hits) == 1: + payload["matches"] = hits + payload["match_status"] = "unique" + return payload + + # 无法对应(0 或多)→ 自动重拉一次 + projects2 = fetch_projects() + hits2 = match_projects(projects2, query=match, space_id=match_id) + payload["projects"] = projects2 + payload["count"] = len(projects2) + payload["refetched"] = True + payload["refetch_reason"] = "无法对应" if len(hits) != 1 else "unexpected" + if len(hits) == 0: + payload["refetch_reason"] = "首次0命中" + elif len(hits) > 1: + payload["refetch_reason"] = "首次多命中" + payload["matches"] = hits2 + if len(hits2) == 1: + payload["match_status"] = "unique_after_refetch" + elif len(hits2) == 0: + payload["match_status"] = "none_after_refetch" + payload["note"] = "已自动重拉一次仍无法对应;请用户从最新列表点选;禁止再拉第3次" + else: + payload["match_status"] = "ambiguous_after_refetch" + payload["note"] = "已自动重拉一次仍多命中;请用户点选;禁止自动选定" + payload["fetched_at"] = datetime.now(TZ).isoformat() + return payload + + +def main() -> None: + ap = argparse.ArgumentParser(description="YunxiaoPMapp 项目列表 / 匹配(无法对应则重拉一次)") + ap.add_argument("--match", help="按项目名或 customCode 匹配") + ap.add_argument("--match-id", help="按 spaceIdentifier 匹配") + args = ap.parse_args() + print( + json.dumps( + list_projects(match=args.match, match_id=args.match_id), + ensure_ascii=False, + indent=2, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/.claude/skills/YunxiaoPM/scripts/list_tags.py b/.claude/skills/YunxiaoPM/scripts/list_tags.py new file mode 100644 index 0000000..858c18e --- /dev/null +++ b/.claude/skills/YunxiaoPM/scripts/list_tags.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 +"""标签候选列表(聚合工作项 tag + runtime);未命中可 --match 后自动重拉一次。""" +from __future__ import annotations + +import argparse +import json +import urllib.parse +from datetime import datetime, timedelta, timezone +from pathlib import Path +from typing import Any + +import requests + +ROOT = Path(__file__).resolve().parents[1] +RUNTIME = json.loads((ROOT / "assets" / "runtime-ids.json").read_text()) +TZ = timezone(timedelta(hours=8)) + + +def load_jar() -> dict[str, str]: + jar: dict[str, str] = {} + try: + import browser_cookie3 + + for domain in (".aliyun.com", "devops.aliyun.com", ".devops.aliyun.com"): + try: + for c in browser_cookie3.chrome(domain_name=domain): + jar[c.name] = c.value + except Exception: + pass + except Exception: + pass + if not jar.get("XSRF-TOKEN"): + p = Path("/tmp/yunxiao_cookies.json") + if p.exists(): + raw = json.loads(p.read_text()) + jar = ( + raw + if isinstance(raw, dict) and "XSRF-TOKEN" in raw + else {c["name"]: c["value"] for c in raw.get("cookies", [])} + ) + if not jar.get("XSRF-TOKEN"): + raise RuntimeError("缺少 XSRF-TOKEN") + return jar + + +def session() -> requests.Session: + jar = load_jar() + x = jar.get("XSRF-TOKEN", "") + xsrf = urllib.parse.unquote(x) if "%" in x else x + s = requests.Session() + s.headers.update( + { + "Cookie": "; ".join(f"{k}={v}" for k, v in jar.items()), + "x-xsrf-token": xsrf, + "X-XSRF-TOKEN": xsrf, + "Origin": "https://devops.aliyun.com", + "Referer": "https://devops.aliyun.com/projex", + "accept": "application/json", + "Content-Type": "application/json", + } + ) + return s + + +def fetch_tags(space_id: str) -> list[dict[str, str]]: + s = session() + by_id: dict[str, str] = {} + for name, tid in (RUNTIME.get("tags") or {}).items(): + by_id[tid] = name + body = { + "spaceIdentifier": space_id, + "spaceType": "Project", + "category": "Req", + "toPage": 1, + "pageSize": 100, + "conditions": json.dumps({"conditionGroups": [[]]}), + } + rows = ( + s.post( + "https://devops.aliyun.com/projex/api/workitem/workitem/list?_input_charset=utf-8", + json=body, + timeout=30, + ) + .json() + .get("result") + or [] + ) + for row in rows: + for t in row.get("tag") or []: + tid = t.get("identifier") + name = t.get("name") or t.get("displayName") + if tid and name: + by_id[tid] = name + # Task 再扫一页,补标签 + body["category"] = "Task" + rows = ( + s.post( + "https://devops.aliyun.com/projex/api/workitem/workitem/list?_input_charset=utf-8", + json=body, + timeout=30, + ) + .json() + .get("result") + or [] + ) + for row in rows: + for t in row.get("tag") or []: + tid = t.get("identifier") + name = t.get("name") or t.get("displayName") + if tid and name: + by_id[tid] = name + return [{"name": n, "identifier": i} for i, n in sorted(by_id.items(), key=lambda x: x[1])] + + +def match_tags(tags: list[dict[str, str]], query: str) -> list[dict[str, str]]: + q = query.strip().lower() + hits = [] + for t in tags: + name = (t.get("name") or "").lower() + if q == name or q in name or name in q: + hits.append(t) + return hits + + +def list_tags(*, space_id: str, match: str | None = None, prefer: str | None = None) -> dict[str, Any]: + tags = fetch_tags(space_id) + # prefer 置顶 + if prefer: + prefer_l = prefer.strip().lower() + tags = sorted( + tags, + key=lambda t: (0 if (t.get("name") or "").lower() == prefer_l else 1, t.get("name") or ""), + ) + out: dict[str, Any] = { + "fetched_at": datetime.now(TZ).isoformat(), + "spaceIdentifier": space_id, + "count": len(tags), + "tags": tags, + "refetched": False, + "note": "标签未命中须自动重拉一次并重生 4.A/B/C 选项;见 compact-select.md", + } + if not match: + return out + hits = match_tags(tags, match) + if len(hits) == 1: + out["matches"] = hits + out["match_status"] = "unique" + return out + # 无法对应 → 重拉一次 + tags2 = fetch_tags(space_id) + if prefer: + prefer_l = prefer.strip().lower() + tags2 = sorted( + tags2, + key=lambda t: (0 if (t.get("name") or "").lower() == prefer_l else 1, t.get("name") or ""), + ) + hits2 = match_tags(tags2, match) + out["tags"] = tags2 + out["count"] = len(tags2) + out["refetched"] = True + out["refetch_reason"] = "首次0命中" if len(hits) == 0 else "首次多命中" + out["matches"] = hits2 + if len(hits2) == 1: + out["match_status"] = "unique_after_refetch" + elif len(hits2) == 0: + out["match_status"] = "none_after_refetch" + out["note"] = "已自动重拉标签列表仍无法对应;请用户点选 4.x;禁止再拉第3次" + else: + out["match_status"] = "ambiguous_after_refetch" + out["fetched_at"] = datetime.now(TZ).isoformat() + return out + + +def main() -> None: + ap = argparse.ArgumentParser() + ap.add_argument("--space", required=True, help="spaceIdentifier") + ap.add_argument("--match", help="标签名") + ap.add_argument("--prefer", help="置顶标签名") + args = ap.parse_args() + print( + json.dumps( + list_tags(space_id=args.space, match=args.match, prefer=args.prefer), + ensure_ascii=False, + indent=2, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/.claude/skills/YunxiaoPM/scripts/live_create_fast.py b/.claude/skills/YunxiaoPM/scripts/live_create_fast.py new file mode 100644 index 0000000..4e5be22 --- /dev/null +++ b/.claude/skills/YunxiaoPM/scripts/live_create_fast.py @@ -0,0 +1,660 @@ +#!/usr/bin/env python3 +"""YunxiaoPM 极速真实建单 v5:快轨描述/计划/标签/工时 2+2/设计 ASSOCIATED 补挂。""" +from __future__ import annotations + +import json +import time +import urllib.parse +from concurrent.futures import ThreadPoolExecutor, as_completed +from datetime import datetime, timedelta, timezone +from pathlib import Path +from typing import Any + +try: + import browser_cookie3 +except ImportError: # pragma: no cover + browser_cookie3 = None + +import requests + +ROOT = Path(__file__).resolve().parents[1] +RUNTIME = json.loads((ROOT / "assets" / "runtime-ids.json").read_text()) +STATUS = RUNTIME["status"]["req"] +TASK_STATUS = RUNTIME["status"]["task"] +FAST = RUNTIME.get("fields", {}).get("fast_track") or {} + +SPACE = ( + RUNTIME.get("project", {}).get("spaceIdentifier") + or (RUNTIME.get("project", {}).get("last_selected") or {}).get("spaceIdentifier") +) +if not SPACE: + raise RuntimeError( + "未选定项目 spaceIdentifier:须先走门禁 PJ 点选,或设置 runtime project.last_selected" + ) +REQ_TYPE = RUNTIME["workitem_types"]["product_req"]["identifier"] +TASK_TYPE = RUNTIME["workitem_types"]["task"]["identifier"] +HEFEI = RUNTIME["people"]["hefei"]["identifier"] +WANG = RUNTIME["people"].get("wangmian", {}).get("identifier", "6811df000601d2fea60144a9") +PRI = RUNTIME["priority"]["中"] +TAG_FAULT = RUNTIME.get("tags", {}).get("故障管理", "ceb526a7343995577645317e9a") +PLACEHOLDER = RUNTIME["delivery_placeholder"] +PROTO = "https://prototype.lnoneos.com/vehicle-fault-handling/index.html" +TZ = timezone(timedelta(hours=8)) +TODAY = datetime.now(TZ).strftime("%Y-%m-%d") +NOON = f"{TODAY} 12:00:00" +EOD = f"{TODAY} 23:59:59" +NOON_MS = str( + int(datetime.now(TZ).replace(hour=12, minute=0, second=0, microsecond=0).timestamp() * 1000) +) +START_MS = str( + int(datetime.now(TZ).replace(hour=9, minute=0, second=0, microsecond=0).timestamp() * 1000) +) +END_MS = str( + int(datetime.now(TZ).replace(hour=11, minute=0, second=0, microsecond=0).timestamp() * 1000) +) +FAST_EST = int(FAST.get("req_estimated_hours", 2)) +FAST_ACT = int(FAST.get("req_actual_hours", 2)) + +PENDING = STATUS["待处理"] +DESIGN_DONE = STATUS["设计完成"] +PENDING_DEV = STATUS["待开发"] +TASK_DONE = TASK_STATUS["已完成"] + +_COOKIE = "" +_XSRF = "" + + +def load_auth() -> None: + global _COOKIE, _XSRF + jar: dict[str, str] = {} + if browser_cookie3: + for domain in (".aliyun.com", "devops.aliyun.com", ".devops.aliyun.com"): + try: + for c in browser_cookie3.chrome(domain_name=domain): + jar[c.name] = c.value + except Exception: + pass + if not jar: + p = Path("/tmp/yunxiao_cookies.json") + if p.exists(): + raw = json.loads(p.read_text()) + jar = ( + raw + if isinstance(raw, dict) and "XSRF-TOKEN" in raw + else {c["name"]: c["value"] for c in raw.get("cookies", [])} + ) + _COOKIE = "; ".join(f"{k}={v}" for k, v in jar.items()) + x = jar.get("XSRF-TOKEN", "") + _XSRF = urllib.parse.unquote(x) if "%" in x else x + if not _XSRF: + raise RuntimeError("缺少 XSRF-TOKEN:请先在 Chrome 登录 devops.aliyun.com") + + +def session() -> requests.Session: + s = requests.Session() + s.headers.update( + { + "Content-Type": "application/json", + "Cookie": _COOKIE, + "x-xsrf-token": _XSRF, + "X-XSRF-TOKEN": _XSRF, + "Origin": "https://devops.aliyun.com", + "Referer": f"https://devops.aliyun.com/projex/project/{SPACE}/req", + "accept": "application/json", + "User-Agent": "YunxiaoPM-live_create_fast/5.0", + "Connection": "keep-alive", + } + ) + return s + + +def api(s: requests.Session, method: str, url: str, body: Any = None) -> dict: + r = s.request(method, url, json=body, timeout=60) + try: + return r.json() + except Exception: + r.raise_for_status() + raise + + +def create(s: requests.Session, payload: dict) -> dict: + url = "https://devops.aliyun.com/projex/api/workitem/workitem?_input_charset=utf-8" + j = api(s, "POST", url, payload) + r = j.get("result") or {} + if j.get("code") == 200 and isinstance(r, dict) and r.get("identifier"): + return r + j = api(s, "PUT", url, payload) + r = j.get("result") or {} + if j.get("code") == 200 and isinstance(r, dict) and r.get("identifier"): + return r + raise RuntimeError(f"create failed: {j}") + + +def get(s: requests.Session, wid: str) -> dict: + return api( + s, + "GET", + f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}?_input_charset=utf-8", + )["result"] + + +def apply_tag(s: requests.Session, wid: str, tag_id: str = TAG_FAULT) -> None: + j = api( + s, + "PATCH", + f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}?_input_charset=utf-8", + { + "workitemIdentifier": wid, + "propertyKey": "tag", + "propertyValue": tag_id, + "operateType": "COVER", + }, + ) + if j.get("code") != 200: + raise RuntimeError(f"tag failed {wid}: {j}") + + +def set_document(s: requests.Session, wid: str, html: str) -> None: + j = api( + s, + "PATCH", + f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}/document?_input_charset=utf-8", + {"content": html, "formatType": "RICHTEXT"}, + ) + if j.get("code") != 200 or j.get("errorMsg"): + raise RuntimeError(f"document failed {wid}: {j}") + + +def set_fields(s: requests.Session, wid: str, pairs: list[tuple[str, str]]) -> None: + data = urllib.parse.urlencode( + { + "fieldValueList": json.dumps( + [{"fieldIdentifier": k, "value": v} for k, v in pairs], ensure_ascii=False + ) + } + ) + r = s.post( + f"https://devops.aliyun.com/projex/api/workitem/workitem/field/value/{wid}?_input_charset=utf-8", + data=data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + timeout=60, + ) + j = r.json() + if j.get("code") != 200: + raise RuntimeError(f"field/value failed {wid}: {j}") + + +def set_estimate_hours(s: requests.Session, wid: str, hours: int, user: str = WANG) -> None: + est = api( + s, + "GET", + f"https://devops.aliyun.com/projex/api/workitem/workitem/time/estimate/list?workitemIdentifier={wid}", + ).get("result") or [] + for row in est: + eid = row.get("identifier") + if eid: + api( + s, + "DELETE", + f"https://devops.aliyun.com/projex/api/workitem/workitem/time/estimate/{wid}/{eid}", + ) + j = api( + s, + "POST", + "https://devops.aliyun.com/projex/api/workitem/workitem/time/estimate?_input_charset=utf-8", + { + "workitemIdentifier": wid, + "spentTime": hours, + "type": "develop", + "description": "快轨默认预计工时", + "recordUserIdentifier": user, + "forCreate": False, + "containsRestDay": False, + }, + ) + if j.get("code") != 200: + raise RuntimeError(f"estimate failed {wid}: {j}") + + +def set_actual_hours(s: requests.Session, wid: str, hours: int, user: str = WANG) -> None: + j = api( + s, + "POST", + "https://devops.aliyun.com/projex/api/workitem/workitem/time?_input_charset=utf-8", + { + "workitemIdentifier": wid, + "actualTime": hours, + "type": "develop", + "description": "快轨默认实际工时", + "recordUserIdentifier": user, + "gmtStart": START_MS, + "gmtEnd": END_MS, + }, + ) + if j.get("code") != 200 or not j.get("result"): + raise RuntimeError(f"actual time failed {wid}: {j}") + + +def try_associate_to_req(s: requests.Session, stage_id: str, req_id: str) -> bool: + """建后补 ASSOCIATED→需求。Cookie 下常失败,成功返回 True。""" + bodies = [ + {"relationIdentifier": "ASSOCIATED", "toWorkitemIdentifier": req_id}, + { + "relationIdentifier": "ASSOCIATED", + "fromWorkitemIdentifier": stage_id, + "toWorkitemIdentifier": req_id, + }, + ] + for body in bodies: + for url in ( + f"https://devops.aliyun.com/projex/api/workitem/workitem/{stage_id}/relation/record?_input_charset=utf-8", + f"https://devops.aliyun.com/projex/api/workitem/v2/workitem/{stage_id}/relation/record?_input_charset=utf-8", + ): + j = api(s, "POST", url, body) + if j.get("code") == 200 and j.get("result") not in (None, False): + if not (isinstance(j.get("result"), dict) and j["result"].get("status") in (404, 405)): + rows = list_associated(s, stage_id) + if req_id in {r.get("identifier") for r in rows}: + return True + return False + + +def transit(s: requests.Session, wid: str, from_status: str, to_status: str) -> None: + if from_status == to_status: + return + j = api( + s, + "POST", + f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}/status/transit?_input_charset=utf-8", + {"fromStatus": from_status, "toStatus": to_status}, + ) + if not (j.get("code") == 200 and j.get("result") is True): + raise RuntimeError(f"transit {wid} {from_status}->{to_status}: {j}") + + +def md_to_html(md: str) -> str: + parts = [] + for line in md.splitlines(): + if line.startswith("## "): + parts.append(f"

{line[3:]}

") + elif line.startswith("### "): + parts.append(f"

{line[4:]}

") + elif line.startswith("- "): + parts.append(f"

• {line[2:]}

") + elif line.strip(): + parts.append(f"

{line}

") + return "".join(parts) + + +def req_document_html(s: requests.Session, rid: str) -> str: + w = get(s, rid) + return ((w.get("document") or {}).get("content") or w.get("description") or "").strip() + + +def req_payload(subject: str, html: str) -> dict: + return { + "subject": subject, + "description": html, + "formatType": "RICHTEXT", + "document": {"content": html, "formatType": "RICHTEXT"}, + "spaceIdentifier": SPACE, + "space": SPACE, + "spaceType": "Project", + "workitemTypeIdentifier": REQ_TYPE, + "workitemType": REQ_TYPE, + "categoryIdentifier": "Req", + "category": "Req", + "assignedTo": WANG, + "fieldValueList": [ + {"fieldIdentifier": "priority", "value": PRI}, + {"fieldIdentifier": "assignedTo", "value": WANG}, + ], + "attachmentIdList": [], + "cloneFrom": None, + "createWorkitemRelationList": [], + } + + +def task_payload( + subject: str, + html: str, + assignee: str, + *, + plan_start: bool = True, + associated_req: str | None = None, + parent_delivery: str | None = None, +) -> dict: + fvl = [ + {"fieldIdentifier": "priority", "value": PRI}, + {"fieldIdentifier": "assignedTo", "value": assignee}, + ] + if plan_start: + fvl.append({"fieldIdentifier": "79", "value": NOON_MS}) + payload: dict[str, Any] = { + "subject": subject, + "description": html, + "formatType": "RICHTEXT", + "spaceIdentifier": SPACE, + "space": SPACE, + "spaceType": "Project", + "workitemTypeIdentifier": TASK_TYPE, + "workitemType": TASK_TYPE, + "categoryIdentifier": "Task", + "category": "Task", + "assignedTo": assignee, + "fieldValueList": fvl, + "attachmentIdList": [], + "cloneFrom": None, + } + if parent_delivery: + payload["parent"] = parent_delivery + payload["parentIdentifier"] = parent_delivery + payload["createWorkitemRelationInfo"] = { + "relatedWorkitemIdentifier": parent_delivery, + "relatedToRelationIdentifier": "TASK_SUB", + } + else: + if not associated_req: + raise ValueError("associated_req required for delivery: ASSOCIATED→需求") + payload["createWorkitemRelationInfo"] = { + "relatedWorkitemIdentifier": associated_req, + "relatedToRelationIdentifier": "ASSOCIATED", + } + return payload + + +def list_associated(s: requests.Session, wid: str) -> list[dict]: + j = api( + s, + "GET", + f"https://devops.aliyun.com/projex/api/workitem/v2/workitem/{wid}/relation/workitem/list/by-relation-category?category=ASSOCIATED&isForward=true", + ) + return j.get("result") or [] + + +def assert_associated_to_req(s: requests.Session, wid: str, req_id: str, label: str) -> None: + rows = list_associated(s, wid) + ids = {r.get("identifier") for r in rows} + if req_id not in ids: + raise RuntimeError( + f"{label} 关联项未挂需求:期望 {req_id},实际 {[r.get('serialNumber') for r in rows]}" + ) + + +AUTO_RDO = """## 原始诉求(AutoRDO) + +运维需在故障处置页承接机器人上报的故障,完成处置、挂起与归档,并保留证据链;工作台相关统计口径需与处置页一致 + +待确认: +- 本期是否含真实短信/邮件通道(现口径一般为演示模板) + +## 工作项编号(系统) +- 交付:待建 +- 分析:待建 +- 设计:待建 +""" + + +def summarize_from_create(w: dict, *, status: str, assignee_name: str) -> dict: + return { + "serial": w.get("serialNumber"), + "id": w.get("identifier"), + "subject": w.get("subject"), + "status": status, + "assignee": assignee_name, + "parent": w.get("parentIdentifier"), + } + + +def build_normal() -> dict: + t0 = time.perf_counter() + s = session() + title = "【新增】故障处置(YunxiaoPMapp标准·极速v2)" + req = create(s, req_payload(title, md_to_html(AUTO_RDO + f"\n原型:{PROTO}\n"))) + rid = req["identifier"] + + with ThreadPoolExecutor(max_workers=2) as pool: + f_tag_r = pool.submit(apply_tag, session(), rid) + f_deliv = pool.submit( + create, + session(), + task_payload( + f"【交付】{title}", + f"

{PLACEHOLDER}

", + HEFEI, + associated_req=rid, + ), + ) + deliv = f_deliv.result() + f_tag_r.result() + did = deliv["identifier"] + + with ThreadPoolExecutor(max_workers=3) as pool: + f_tag_d = pool.submit(apply_tag, session(), did) + f_ana = pool.submit( + create, + session(), + task_payload( + f"【分析】{title}", + "

分析阶段:故障处置台账、挂起归档与证据链。

", + WANG, + associated_req=rid, + parent_delivery=did, + ), + ) + f_des = pool.submit( + create, + session(), + task_payload( + f"【设计】{title}", + f"

设计阶段:对齐原型 {PROTO}

", + WANG, + associated_req=rid, + parent_delivery=did, + ), + ) + ana = f_ana.result() + des = f_des.result() + f_tag_d.result() + + with ThreadPoolExecutor(max_workers=3) as pool: + list( + as_completed( + [ + pool.submit(transit, session(), rid, PENDING, DESIGN_DONE), + pool.submit(transit, session(), ana["identifier"], PENDING, TASK_DONE), + pool.submit(transit, session(), des["identifier"], PENDING, TASK_DONE), + ] + ) + ) + transit(s, rid, DESIGN_DONE, PENDING_DEV) + + return { + "path": "normal", + "elapsed_s": round(time.perf_counter() - t0, 3), + "req": summarize_from_create(req, status="待开发", assignee_name="王冕"), + "delivery": summarize_from_create(deliv, status="待处理", assignee_name="何斐"), + "analysis": summarize_from_create(ana, status="已完成", assignee_name="王冕"), + "design": summarize_from_create(des, status="已完成", assignee_name="王冕"), + } + + +def build_fast( + *, + tag_id: str = TAG_FAULT, + has_prototype: bool = True, + delivery_html: str | None = None, +) -> dict: + """无单快轨。 + + - 设计描述 = 需求 document + - 交付描述 = 手工同步需求正文(无原型)或传入 AutoPRD HTML;禁止无故占位 + - 设计 79/80=当日;交付 79=当日 + - 需求/交付/设计同标签 + - 需求预计/实际工时各 2 + - 设计 TASK_SUB→交付后尝试 ASSOCIATED→需求 + """ + t0 = time.perf_counter() + s = session() + title = "【新增】故障处置(YunxiaoPM快轨·极速v5)" + req_html = md_to_html(AUTO_RDO + (f"\n原型:{PROTO}\n" if has_prototype else "\n")) + req = create(s, req_payload(title, req_html)) + rid = req["identifier"] + # 以落库 document 为准(与手动建需后读需求一致) + req_html = req_document_html(s, rid) or req_html + + if delivery_html: + deliv_html = delivery_html + desc_source = "autoprd" + elif req_html.strip(): + deliv_html = req_html + desc_source = "manual_sync" + else: + deliv_html = f"

{PLACEHOLDER}

" + desc_source = "placeholder" + + with ThreadPoolExecutor(max_workers=2) as pool: + f_tag_r = pool.submit(apply_tag, session(), rid, tag_id) + f_deliv = pool.submit( + create, + session(), + task_payload( + f"【交付】{title}", + deliv_html, + HEFEI, + associated_req=rid, + ), + ) + deliv = f_deliv.result() + f_tag_r.result() + did = deliv["identifier"] + + with ThreadPoolExecutor(max_workers=2) as pool: + f_tag_d = pool.submit(apply_tag, session(), did, tag_id) + f_des = pool.submit( + create, + session(), + task_payload( + f"【设计】{title}", + req_html, + WANG, + parent_delivery=did, + ), + ) + des = f_des.result() + f_tag_d.result() + des_id = des["identifier"] + apply_tag(s, des_id, tag_id) + + # 计划时间:设计起止当日;交付开始当日(create 已带 79,再 field/value 加固) + set_fields(s, des_id, [("79", NOON), ("80", EOD)]) + set_fields(s, did, [("79", NOON)]) + + # 设计关联项补挂需求(可能失败,回报 risk) + design_assoc_ok = try_associate_to_req(s, des_id, rid) + + with ThreadPoolExecutor(max_workers=2) as pool: + list( + as_completed( + [ + pool.submit(transit, session(), rid, PENDING, DESIGN_DONE), + pool.submit(transit, session(), des_id, PENDING, TASK_DONE), + ] + ) + ) + transit(s, rid, DESIGN_DONE, PENDING_DEV) + + set_estimate_hours(s, rid, FAST_EST) + set_actual_hours(s, rid, FAST_ACT) + + risk = None + if desc_source == "placeholder": + risk = "交付描述仍为占位" + if not design_assoc_ok: + risk = (risk + ";" if risk else "") + "设计 ASSOCIATED→需求补挂失败(Cookie),须 UI/OpenAPI 兜底" + + return { + "path": "fast", + "elapsed_s": round(time.perf_counter() - t0, 3), + "req": summarize_from_create(req, status="待开发", assignee_name="王冕"), + "delivery": summarize_from_create(deliv, status="待处理", assignee_name="何斐"), + "design": summarize_from_create(des, status="已完成", assignee_name="王冕"), + "analysis": None, + "delivery_desc_source": desc_source, + "design_associated_ok": design_assoc_ok, + "risk": risk, + } + + +def main() -> None: + auth0 = time.perf_counter() + load_auth() + auth_s = round(time.perf_counter() - auth0, 3) + + wall0 = time.perf_counter() + with ThreadPoolExecutor(max_workers=2) as pool: + f_fast = pool.submit(build_fast) + f_normal = pool.submit(build_normal) + fast = f_fast.result() + normal = f_normal.result() + build_s = round(time.perf_counter() - wall0, 3) + + s = session() + assert_associated_to_req(s, normal["delivery"]["id"], normal["req"]["id"], "normal.delivery") + assert_associated_to_req(s, fast["delivery"]["id"], fast["req"]["id"], "fast.delivery") + + def assert_sub(delivery_id: str, child_id: str, label: str) -> None: + rows = api( + s, + "GET", + f"https://devops.aliyun.com/projex/api/workitem/v2/workitem/{delivery_id}/relation/workitem/list/by-relation-category?category=PARENT_SUB&isForward=true", + ).get("result") or [] + ids = {r.get("identifier") for r in rows} + if child_id not in ids: + raise RuntimeError( + f"{label} 未出现在交付子项:期望 {child_id},实际 {[r.get('serialNumber') for r in rows]}" + ) + + assert_sub(normal["delivery"]["id"], normal["analysis"]["id"], "normal.analysis") + assert_sub(normal["delivery"]["id"], normal["design"]["id"], "normal.design") + assert_sub(fast["delivery"]["id"], fast["design"]["id"], "fast.design") + verify = { + "normal_req": get(s, normal["req"]["id"])["status"]["displayName"], + "fast_req": get(s, fast["req"]["id"])["status"]["displayName"], + "normal_delivery_assignee": ( + get(s, normal["delivery"]["id"]).get("assignedTo") or {} + ).get("displayName"), + "fast_delivery_assignee": (get(s, fast["delivery"]["id"]).get("assignedTo") or {}).get( + "displayName" + ), + "delivery_associated_ok": True, + "stage_tasks_as_sub_ok": True, + "fast_design_associated_ok": fast.get("design_associated_ok"), + } + out = { + "normal": normal, + "fast": fast, + "verify": verify, + "auth_elapsed_s": auth_s, + "wall_elapsed_s": build_s, + "created_at": datetime.now(TZ).isoformat(), + "mode": "live_create_fast_v5_fast_track_rules", + "opts": { + "create_includes_plan_start": True, + "skip_plan_end_on_create": True, + "tracked_transit_no_get": True, + "delivery_assignee_hefei_at_create": True, + "overlap_tag_with_create": True, + "fast_req_hours": f"{FAST_EST}+{FAST_ACT}", + "relation": "delivery ASSOCIATED→req; analysis/design TASK_SUB→delivery; design post ASSOCIATED→req", + "http": "requests.Session keep-alive", + }, + } + Path("/tmp/yunxiao_pmapp_fast_v2_result.json").write_text( + json.dumps(out, ensure_ascii=False, indent=2) + ) + print(json.dumps(out, ensure_ascii=False, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/.claude/skills/YunxiaoPM/scripts/workday_hours.py b/.claude/skills/YunxiaoPM/scripts/workday_hours.py new file mode 100755 index 0000000..289b867 --- /dev/null +++ b/.claude/skills/YunxiaoPM/scripts/workday_hours.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""Compute YunxiaoPMapp stage calendar hours = workdays × 8. + +Usage: + python3 workday_hours.py YYYY-MM-DD YYYY-MM-DD + python3 workday_hours.py YYYY-MM-DD YYYY-MM-DD --calendar /path/to/cn-workday-calendar.json +""" + +from __future__ import annotations + +import argparse +import json +import sys +from datetime import date, datetime, timedelta +from pathlib import Path + + +def parse_day(s: str) -> date: + return datetime.strptime(s, "%Y-%m-%d").date() + + +def load_calendar(path: Path) -> dict: + data = json.loads(path.read_text(encoding="utf-8")) + years = data.get("years") or {} + holidays: set[str] = set() + makeup: set[str] = set() + for _y, block in years.items(): + holidays.update(block.get("holidays") or []) + makeup.update(block.get("workdays_on_weekend") or []) + return {"holidays": holidays, "makeup": makeup, "years": set(years.keys())} + + +def ensure_years_covered(start: date, end: date, years: set[str]) -> None: + y = start.year + while y <= end.year: + if str(y) not in years: + raise SystemExit( + f"缺少 {y} 年日历:请在 assets/cn-workday-calendar.json 补全后再计算。" + "禁止仅去周末静默估算。" + ) + y += 1 + + +def is_workday(d: date, holidays: set[str], makeup: set[str]) -> bool: + key = d.isoformat() + if key in holidays: + return False + if key in makeup: + return True + return d.weekday() < 5 # Mon=0 .. Fri=4 + + +def count_workdays(start: date, end: date, holidays: set[str], makeup: set[str]) -> int: + if end < start: + raise SystemExit("结束日早于开始日") + n = 0 + cur = start + while cur <= end: + if is_workday(cur, holidays, makeup): + n += 1 + cur += timedelta(days=1) + return n + + +def main() -> None: + parser = argparse.ArgumentParser(description="阶段日历工时 = 工作日×8") + parser.add_argument("start", help="计划开始 YYYY-MM-DD") + parser.add_argument("end", help="计划完成 YYYY-MM-DD") + parser.add_argument( + "--calendar", + default=str(Path(__file__).resolve().parent.parent / "assets" / "cn-workday-calendar.json"), + help="日历 JSON 路径", + ) + args = parser.parse_args() + start = parse_day(args.start) + end = parse_day(args.end) + cal = load_calendar(Path(args.calendar)) + ensure_years_covered(start, end, cal["years"]) + days = count_workdays(start, end, cal["holidays"], cal["makeup"]) + hours = days * 8 + out = { + "start": start.isoformat(), + "end": end.isoformat(), + "workdays": days, + "stage_calendar_hours": hours, + "label": "阶段日历工时(工作日×8),非人力投入预估", + } + print(json.dumps(out, ensure_ascii=False, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/.claude/skills/YunxiaoPM/stage-flow.md b/.claude/skills/YunxiaoPM/stage-flow.md new file mode 100644 index 0000000..3c97b2f --- /dev/null +++ b/.claude/skills/YunxiaoPM/stage-flow.md @@ -0,0 +1,63 @@ +# 标准路径:待处理 → 待开发交棒 + +项目常量见 [../assets/runtime-ids.json](../assets/runtime-ids.json)。查重只用任务编号。 + +## 步骤 0|创建需求 + +| 云效动作 | 落地 | +|---|---| +| 新建产品类需求 | POST 建单;标题 `【新增】`/`【优化】` | +| 需求描述 | **先** AutoRDO 清洗再写入 `## 原始诉求(AutoRDO)`;本步不写 AutoPRD | +| 默认状态 | **待处理**;不建任务、不改负责人 | +| 可选推进 | 口令点选目标状态;未选则停在待处理 | + +## 步骤 1|受理 → 已确认 + +| 云效动作 | 落地 | +|---|---| +| 状态 | 待处理 → **已确认** | +| 任务 | **仍不建**交付/分析/设计 | + +若用户只「创建」未受理:只提示「受理后请推进至已确认」,不自动跳。 + +## 步骤 2|分析中 + +| 对象 | 动作 | +|---|---| +| 【交付】 | 无则新建;标题 `【交付】`+需求标题;**ASSOCIATED→需求**(勿用 PARENT);负责人=创建人/产品;**计划开始仅空时写当日**;不写计划完成;描述=`等待设计任务完成后自动填入`;更新编号区块 | +| 【分析】 | 新建;**TASK_SUB→交付**(`parent`+`parentIdentifier`+`createWorkitemRelationInfo=TASK_SUB`);交付「子项」须可见;计划开始=当日(仅空时);负责人默认可=创建人;更新编号区块 | + +| 需求 | 状态=`分析中` | + +## 步骤 3|设计中 + +| 对象 | 动作 | +|---|---| +| 【交付】 | 已存在则按编号复用;没有则补建;**不改交付计划开始**;不写交付计划完成 | +| 【设计】 | 新建;**TASK_SUB→交付**(同分析);计划开始=当日(仅空时);更新编号区块 | +| 【分析】 | **计划完成**=状态更新日;推算阶段日历工时(见 work-hours) | +| 需求 | 状态=`设计中` | + +## 步骤 4|设计完成 + +| 对象 | 动作 | +|---|---| +| 【设计】 | 计划完成=当日;推算工时;任务→完成态 | +| 需求 | AutoPRD 写入 `## 产品说明`;挂 ZIP+截图;状态=`设计完成` | +| 【交付】 | 不换负责人;不改计划开始;不写计划完成;描述改为 AutoPRD 正文;同样挂附件 | + +顺序与失败门禁见 [description-split.md](description-split.md)。 + +## 步骤 5|待开发交棒(本 Skill 终点 · 标准路径) + +| 对象 | 动作 | +|---|---| +| 需求 | 状态=`待开发` | +| 【交付】 | 按**任务编号**定位;**负责人→何斐**;不改计划开始;不写计划完成 | +| 【分析】/【设计】 | 不新建;不改已有计划开始;前序未收口按步骤 3/4 处理 | + +执行 [handoff-and-rollback.md](handoff-and-rollback.md) §交棒门禁。 + +**不做:** 【开发】/【测试】、仓库、分支、提测。 + +快轨 / 编号直推见专文。创建迭代见 [sprint.md](sprint.md)。 diff --git a/.claude/skills/oneos-autoprd/SKILL.md b/.claude/skills/oneos-autoprd/SKILL.md index a10d84f..6668282 100644 --- a/.claude/skills/oneos-autoprd/SKILL.md +++ b/.claude/skills/oneos-autoprd/SKILL.md @@ -1,146 +1,108 @@ --- name: oneos-autoprd description: >- - Generates and keeps in sync OneOS AutoPRD (PM-facing requirements) plus Axhub - Make annotation directory Markdown/PRD; on 需求定稿/定稿/确认定稿/本次定稿 appends - functional release changelog below the PRD since last baseline. When a Yunxiao - requirement advances to 分析中/设计中/待开发, creates a same-titled linked task - (tag+create-time follow the requirement; 分析中/设计中 assignee=creator, 待开发 - assignee=何斐). Use eagerly for prototypes, AutoPRD, or Yunxiao requirement - description/更新内容/stage advance—do not skip annotation sync, 定稿 changelog, - or stage-task creation. + OneOS AutoPRD: generates PM-facing requirements (总览/目标/边界/角色/用户故事 + 起点→运作→闭环/故事点/正逆向/流程图/关键逻辑/状态/风险/交付) and syncs Axhub Make + annotation PRD. On 需求定稿/本轮定稿 appends functional changelog with auto + V主.副.子 (user 主/副/子 override wins). With YunxiaoPMapp design-complete, + writes Markdown into 【交付】 by task number (not at create; placeholder until then). + Use for AutoPRD, prototypes, 产品说明, 本轮定稿. Never create same-titled stage + tasks; never load yunxiao-requirement-lifecycle—cloud combo is YunxiaoPMapp only. --- -# OneOS AutoPRD +# AutoPRD(oneos-autoprd) 为 OneOS 业务模块生成**产品经理可读、可评审、可排期**的需求说明,并**自动挂到 Axhub Make 标注工具 → 原型目录**。 -另支持:**需求定稿**时汇总功能/逻辑变更记录;与云效组合时提供「需求说明 + 更新内容」;需求进入**分析中 / 设计中 / 待开发**时**自动创建与需求同名的关联任务**(规则见下,写在本 Skill,不改云效 Skill)。 +另支持:**本轮定稿 / 需求定稿**时汇总功能变更并**自动递增 PRD 版本号**;与 **`$YunxiaoPMapp`** 组合时,在**设计完成**(或显式同步交付)把 Markdown 写入【交付】任务描述。 -颗粒度对齐「保险采购」全模块 PRD:讲清做什么、谁用、故事点、正逆向、流程图与关键业务逻辑;**不写**表结构、接口、字段代码名、文件路径、实现清单。 +**禁止加载** `yunxiao-requirement-lifecycle`。云效状态机与【交付】/【分析】/【设计】树**只**由 YunxiaoPMapp 负责;本 Skill **不**建同名无前缀阶段任务。 + +颗粒度:讲清做什么、谁用、故事点、正逆向、流程图与关键业务逻辑;**不写**表结构、接口、字段代码名、文件路径、实现清单。 ## 何时使用(含自动触发) -**主动调用时** +**主动调用** -- AutoPRD、OneOS 需求说明、整模块 PRD、故事点 + 流程图 +- AutoPRD、OneOS 需求说明、整模块 PRD、故事点 + 流程图、产品说明 -**改原型时必须自动跟进(全局规则)** +**改原型时必须自动跟进** -- 正在修改 `src/prototypes//` 下页面、交互、文案、判定、验收相关内容 -- 同一轮交付内同步更新 PRD Markdown + 标注目录,不得只改代码 +- 修改 `src/prototypes//` 下页面、交互、文案、判定、验收相关内容 +- 同一轮同步更新 PRD Markdown + 标注目录;**纯样式且无产品语义变化可跳过全量重写** **需求定稿时(强制)** -- 用户回复含:`需求定稿` / `定稿` / `确认定稿` / `本次定稿` -- 执行 [references/release-changelog.md](references/release-changelog.md):写第 10 章 + 更新基线 +- 关键字:`需求定稿` / `定稿` / `确认定稿` / `本次定稿` / **`本轮定稿`** +- 执行 [references/release-changelog.md](references/release-changelog.md):第 10 章 + `prdVersion` 基线 -**云效建需求 / 完善需求时(强制组合)** +**与 YunxiaoPMapp 组合(强制 · 唯一云效组合)** -- 与 `$yunxiao-requirement-lifecycle` 一起使用时:先跑本 Skill,再写云效描述 -- **需求说明** ← PRD 正文(或浓缩交付口径 + 关键章节) -- **更新内容** ← 第 10 章中**自上次定稿以来**的条目(无增量则「首版定稿 / 本轮无功能增量」) - -**云效需求推进至分析中 / 设计中 / 待开发时(强制)** - -- 无论口令来自本 Skill 还是云效 Skill,只要本轮把需求推到上述状态,就执行 [references/yunxiao-stage-tasks.md](references/yunxiao-stage-tasks.md) -- 自动建**与需求同名**的任务并正式关联;标签与创建时间口径沿用需求;分析中/设计中负责人=创建人,待开发负责人=**何斐** - -不要用本 Skill 替代:需求探索访谈、设计比稿、纯样式微调(无产品语义变化时可跳过全量重写)。 +- 设计完成或完善「产品说明」:先本 Skill 落盘 MD,再由 YunxiaoPMapp / 本 Skill 按 [yunxiao-description.md](references/yunxiao-description.md) 写入需求 `## 产品说明(AutoPRD)`(**不覆盖** `## 原始诉求(AutoRDO)` / `## 工作项编号(系统)`) +- **创建【交付】时不写 PRD 正文**(占位由 YunxiaoPMapp 写入);设计完成或口令「同步交付说明」时按 [yunxiao-delivery-sync.md](references/yunxiao-delivery-sync.md) 用任务**编号**回填【交付】 +- 入库前聊天/录音清洗:先 `$AutoRDO`,再 YunxiaoPMapp 记录需求 ## 工作流 ### 主流程(写/同步 PRD) -1. **定模块**:确认 OneOS 模块名与 `src/prototypes//`。 +1. **定模块**:OneOS 模块名与 `src/prototypes//`。 2. **读上下文(只取产品语义)** - 用户说明、已确认口径、原型标注、`.spec/`、业务条线说明(`lines.ts`) - 忽略实现细节;字段名/接口改写成业务语言。 3. **收敛边界**:做什么 / 不做什么、外部依赖、与其它模块关系。 -4. **按模板成文**:下方「输出结构」;缺关键信息最多问 1~2 个问题,其余写「假设」。 -5. **落盘 + 标注同步(强制)** — 见 [references/annotation-sync.md](references/annotation-sync.md)。 +4. **按模板成文**:见「输出结构」与 [references/template.md](references/template.md)。 +5. **落盘 + 标注同步(强制)** — [references/annotation-sync.md](references/annotation-sync.md)。 | 顺序 | 动作 | |------|------| | A | 写/更新 `src/prototypes//.spec/requirements-prd.md` | | B | 写/更新 `src/resources/prd/-autoprd.md` | - | C | 更新 `annotation-source.json` **顶层** `directory.nodes`(PRD 全文 + 推荐分章);禁止只写 `data.directory` | + | C | 更新 `annotation-source.json` **顶层** `directory.nodes`(PRD 全文 + 推荐分章) | | D | 若存在 `scripts/sync-annotation-directory.mjs`,执行之 | -6. **交付说明**:路径、标注目录入口、故事点合计、开放问题/假设。 +6. **交付说明**:路径、标注入口、故事点合计、开放问题/假设。 -### 定稿流程(关键字触发) +### 定稿流程 -见 [references/release-changelog.md](references/release-changelog.md)。摘要: +见 [references/release-changelog.md](references/release-changelog.md)。含自动 `V主.副.子`;**用户显式指定主/副/子时以用户为准**。 -1. 读 PRD + `.spec/autoprd-baseline.json` -2. 汇总自上次定稿以来的**功能/逻辑**变更(排除样式/UI/表结构) -3. 追加到 `## 10. 功能变更记录`(最新在上) -4. 更新基线 JSON + 标注目录 -5. 若同时发云效:本次定稿块 →「更新内容」;旧段 →「更新内容·历史」 +### 云效需求「产品说明」(与 YunxiaoPMapp 双段模板) -### 云效交接格式(给 yunxiao skill 直接粘贴) +见 [references/yunxiao-description.md](references/yunxiao-description.md)。 -```markdown -## 原型链接 -<对象存储或预览 URL> +### 云效【交付】回填(设计完成 · 非创建时) -## 需求说明 - - -## 更新内容 -<第 10 章本次定稿块;无则「首版定稿」或「本轮无功能/逻辑增量」> - -## 更新内容·历史 -<以往更新内容倒序,勿删除> -``` - -### 云效阶段任务(状态推进时强制) - -见 [references/yunxiao-stage-tasks.md](references/yunxiao-stage-tasks.md)。摘要: - -| 需求状态 | 任务标签 | 负责人 | 标题 | -|----------|----------|--------|------| -| 分析中 | 分析 | 需求创建人 | 与需求同名 | -| 设计中 | 设计 | 需求创建人 | 与需求同名 | -| 待开发 | 交付(或开发) | 何斐 | 与需求同名 | - -正式关联需求;创建时间口径沿用需求;同阶段未取消任务不重复建。 +见 [references/yunxiao-delivery-sync.md](references/yunxiao-delivery-sync.md)。 ## 写作硬约束 -**必须写** +**必须写(映射用户概要)** -- 一句话定位 + 目标 / 非目标 -- 模块边界(含 mermaid 总览更好) -- 角色与目标(角色名优先对齐业务条线说明) -- **用户故事**(业务条线说明口径)+ Epic 级**故事点(SP)**粗估 -- 分功能**正向**与**逆向/边界** -- 至少 1~2 个 **mermaid** 流程图 -- **关键业务逻辑**(业务话) -- 验收清单 + 「交付口径」一段话 -- 定稿后:**功能变更记录**(第 10 章) +| 概要能力 | 落在章节 | +|---|---| +| 总览 | §1 一句话与目标 | +| 目标 / 边界 | §1–2 | +| 角色 | §3 | +| 用户故事(起点→运作→闭环)+ 故事点 | §4 | +| 正逆向流程 | §5 | +| 关键逻辑 / 状态 / 风险 | §6(及验收相关) | +| 流程图 | §7 | +| 交付 | §9 交付口径 | +| 定稿变更 | §10 | + +另:验收清单 §8;对象存储预览链接形态 `{baseUrl}/{prototype-id}/index.html`(禁止加 `prototypes/` 前缀、禁止去掉 `index.html`)。 **禁止写** - 数据库表、字段名、接口路径、代码路径、组件名、存储 key -- 研发实现指令(可写「正式环境由审批中心回写」这类业务依赖) -- 在变更记录里写样式/UI/表结构优化 +- 变更记录里的样式/UI/表结构优化 +- 引导加载 `yunxiao-requirement-lifecycle` 或「同名阶段任务」建单 ## 用户故事口径(强制 · 对齐业务条线说明) -真相源:原型 **业务条线说明**(`lease-business-line-overview` / `lines.ts`)。 -每条能力用「责任部门 → **起点** → **怎么运作** → **闭环**」叙述;**不要**用「作为…我希望…」宽表作主叙述。 - -| 块 | 写什么 | -|----|--------| -| 角色 | 谁负责、谁协同 | -| 起点 | 谁在什么前提下启动 | -| 怎么运作 | 有序步骤,含跨角色协作 | -| 关键结果 | 可选标签 | -| 闭环 | 业务终点与可追溯性 | - -可选:`US-xx`、压缩句「作为…我想…以便…」、规模 S/M/L 或 SP(仅排期,不替代主叙述)。 +真相源:业务条线说明(`lease-business-line-overview` / `lines.ts`)。 +主叙述:**起点 → 怎么运作 → 闭环**(不要用「作为…我希望…」宽表作主叙述)。 ## 输出结构 @@ -158,26 +120,27 @@ description: >- ## 7. 总览流程图 ## 8. 验收清单 ## 9. 交付口径 -## 10. 功能变更记录 ← 定稿后维护;日常改原型不强制每改必写 +## 10. 功能变更记录 ← 定稿维护;含 V主.副.子 ``` ## 质量自检 - [ ] 产品经理不看代码也能评审 - [ ] 用户故事为起点 / 怎么运作 / 闭环 -- [ ] `.spec/requirements-prd.md` 已更新 -- [ ] 标注目录「产品需求说明(PRD)」已同步且正文一致 -- [ ] 定稿时:第 10 章 + `autoprd-baseline.json` 已更新 -- [ ] 推进至分析中/设计中/待开发时:同名任务已创建或复用,正式关联,负责人正确 -- [ ] 无表结构 / 接口 / 代码路径;变更记录无样式/UI 废话 +- [ ] `.spec/requirements-prd.md` + 标注目录已同步 +- [ ] 定稿时:第 10 章含版本号 + `autoprd-baseline.json` 含 `prdVersion` +- [ ] 与 YunxiaoPMapp 设计完成:【交付】按**编号**回填,创建时未提前灌 MD +- [ ] 全文无 `yunxiao-requirement-lifecycle` 引导;无同名阶段任务建单 +- [ ] 变更记录无样式/UI/表结构废话 ## 参考 -- 定稿变更日志:[references/release-changelog.md](references/release-changelog.md) -- 云效阶段任务:[references/yunxiao-stage-tasks.md](references/yunxiao-stage-tasks.md) -- 标注同步细则:[references/annotation-sync.md](references/annotation-sync.md) -- 章节模板:[references/template.md](references/template.md) +- 定稿与版本:[references/release-changelog.md](references/release-changelog.md) +- 云效产品说明:[references/yunxiao-description.md](references/yunxiao-description.md) +- 【交付】回填:[references/yunxiao-delivery-sync.md](references/yunxiao-delivery-sync.md) +- 标注同步:[references/annotation-sync.md](references/annotation-sync.md) +- 模板:[references/template.md](references/template.md) - 故事示例:[references/granularity-example.md](references/granularity-example.md) - 业务条线:`src/prototypes/lease-business-line-overview/lines.ts` -- 复杂判定规格:配合项目规则 `business-logic-documentation` -- 云效组合:`$yunxiao-requirement-lifecycle`(建需求时先本 Skill) +- 云效组合(唯一):`$YunxiaoPMapp` +- 入库清洗:`$AutoRDO` diff --git a/.claude/skills/oneos-autoprd/references/release-changelog.md b/.claude/skills/oneos-autoprd/references/release-changelog.md index 998e9bf..4c6a9ba 100644 --- a/.claude/skills/oneos-autoprd/references/release-changelog.md +++ b/.claude/skills/oneos-autoprd/references/release-changelog.md @@ -1,12 +1,12 @@ -# 功能变更记录(定稿触发) +# 功能变更记录(定稿触发)+ PRD 版本号 -用户回复**需求定稿关键字**后,必须生成「自上次定稿以来」的功能变更日志,追加到 PRD **最下方**,并更新定稿基线。 +用户回复**定稿关键字**后,必须生成「自上次定稿以来」的功能变更日志,追加到 PRD **最下方**,按规则递增 **`prdVersion`(V主.副.子)**,并更新定稿基线。 ## 触发关键字(命中任一) -`需求定稿` · `定稿` · `确认定稿` · `本次定稿` +`需求定稿` · `定稿` · `确认定稿` · `本次定稿` · **`本轮定稿`** -(可带模块名,如「保险采购需求定稿」。) +(可带模块名,如「保险采购本轮定稿」。可附带 `版本类型=主|副|子`。) ## 写什么 / 不写什么 @@ -16,44 +16,57 @@ | 变更了什么业务逻辑 / 流程 / 验收口径 | 「优化了哪些表」「改了哪些字段/接口」 | | 用户可感知的交互结果变化 | 纯样式、动效、无语义文案微调 | -条目口吻示例: +## 版本号 `V{主}.{副}.{子}` -- 「识别失败」:失败数可点击查看失败文件与原因;仅失败时也可打开明细 -- 「比价单」:审批通过后仍须在保单管理另行录入正式保单(强调不自动同步) +基线字段 `prdVersion`(无则视为 `V0.0.0`)。有功能/逻辑增量时递增;**无增量则版本号不变**。 + +### 判定优先级 + +1. **用户覆盖(优先)** + 口令或对话显式指定「主 / 副 / 子」→ **以用户为准**,不再自动改判。 + 定稿块首行写:`判定=用户指定·主`(或副/子)。 + +2. **自动判定(用户未指定时)** + - **主+1**(副→0,子→0):跨模块边界变化、主流程/状态机重做、角色权限模型大改、验收口径整体推翻等「大改动」≥1 条主导。 + - **副+1**(子→0):能力增删改、逻辑/文案产品语义变化、故事点级功能优化(**默认多数定稿**)。 + - **子+1**:仅小幅逻辑澄清;若用户明确「本轮定稿」且变更非空且未指定类型 → **至少副+1**(不用子)。 + - 定稿块首行写:`判定=主:…` / `判定=副:…` / `判定=子:…`(须写依据,禁止无依据猜)。 + +3. **无功能增量** + 版本号不变;正文写「本轮无功能/逻辑增量(仅样式或未达产品语义变更)」或「首版定稿」(无基线且首版可落到 `V1.0.0` 若视为首次发布——首版有实质内容时用主从 `V0.0.0`→`V1.0.0`)。 ## 落点 -1. **PRD 文末章节**(强制) +1. **PRD 文末章节** `src/prototypes//.spec/requirements-prd.md` → `## 10. 功能变更记录` - 新定稿块插在该章**最上方**(倒序:最新在上);历史块保留。 + 新定稿块插在该章**最上方**。 -2. **定稿基线**(强制) +2. **定稿基线** `src/prototypes//.spec/autoprd-baseline.json` ```json { "prototypeId": "insurance-procurement", + "prdVersion": "V1.2.0", "lastConfirmedAt": "2026-07-21T12:00:00+08:00", - "lastConfirmedLabel": "定稿 · 2026-07-21", + "lastConfirmedLabel": "定稿 · V1.2.0 · 2026-07-21", "summaryBullets": ["…", "…"] } ``` -3. **标注目录**:同步更新「产品需求说明(PRD)」全文节点;若有「PRD 分章」,增加或更新「功能变更记录」分章。 +3. **标注目录**:同步「产品需求说明(PRD)」全文节点。 -4. **云效交接**(若本轮同时走云效):把**本次定稿块**正文作为需求描述「更新内容」;旧「更新内容」挪到「更新内容·历史」。 +4. **云效**(若本轮走 YunxiaoPMapp):按 [yunxiao-description.md](yunxiao-description.md) 更新需求产品说明;**禁止**加载 `yunxiao-requirement-lifecycle`。 ## 定稿工作流 -1. 确认 ``(从对话 / 打开文件 / 用户指名推断)。 -2. 读现有 PRD 与 `autoprd-baseline.json`(无基线则本轮视为**首版定稿**)。 -3. 收集自 `lastConfirmedAt` 以来的产品语义变更: - 对话确认口径 → 批注/操作说明 → PRD 新旧差异 →(可选)相关提交说明。 - 过滤掉样式/表结构类改动。 -4. 若无功能增量:仍写定稿块,正文为「本轮无功能/逻辑增量(仅样式或未达产品语义变更)」或「首版定稿」。 -5. 追加 `### 定稿 · YYYY-MM-DD` + 条目列表到第 10 章顶部。 -6. 更新 `autoprd-baseline.json`;执行 annotation-sync。 -7. 向用户回报:基线时间、条目数、PRD 路径;若需发云效,提示可用口令。 +1. 确认 ``。 +2. 读 PRD 与 `autoprd-baseline.json`(无基线 → 首版;`prdVersion` 缺省 `V0.0.0`)。 +3. 收集自 `lastConfirmedAt` 以来的产品语义变更;过滤样式/表结构。 +4. 判定版本类型(用户指定优先)→ 计算新 `prdVersion`。 +5. 追加 `### 定稿 · V{x.y.z} · YYYY-MM-DD`(首行判定理由 + 条目)。 +6. 更新基线 JSON;annotation-sync。 +7. 回报:新版本号、判定理由、条目数、PRD 路径。 ## PRD 章节格式 @@ -62,17 +75,21 @@ > 产品经理原型发版记录。仅记功能与业务逻辑变更;不含样式/UI/表结构。 -### 定稿 · 2026-07-21 +### 定稿 · V1.2.0 · 2026-07-21 -- 「能力名」:做了什么 / 逻辑如何变 +判定=副:新增比价失败明细可点开 + +- 「识别失败」:失败数可点击查看失败文件与原因 - … -### 定稿 · 2026-07-10 +### 定稿 · V1.1.0 · 2026-07-10 + +判定=用户指定·副 - … ``` ## 与日常改原型的关系 -- **改原型过程中**:按 AutoPRD 主流程增量更新第 1–9 章;**不必**每改一次就写第 10 章。 -- **用户说定稿时**:才汇总写入第 10 章并刷新基线。 +- **改原型过程中**:增量更新第 1–9 章;**不必**每改一次写第 10 章。 +- **用户说定稿时**:才汇总第 10 章并刷新版本与基线。 diff --git a/.claude/skills/oneos-autoprd/references/template.md b/.claude/skills/oneos-autoprd/references/template.md index 449b8bf..e0c41fe 100644 --- a/.claude/skills/oneos-autoprd/references/template.md +++ b/.claude/skills/oneos-autoprd/references/template.md @@ -2,6 +2,22 @@ 复制下列结构填写。方括号为占位。**第 4 章用户故事必须用业务条线说明口径。** +## 概要能力 → 章节映射 + +| 用户概要 | 本模板位置 | +|---|---| +| 总览 | §1 一句话与目标 | +| 目标 / 边界 | §1–2 | +| 角色 | §3 | +| 用户故事(起点→运作→闭环)/ 故事点 | §4 | +| 正逆向流程 | §5 | +| 状态 / 风险 / 关键逻辑 | §6(可分小节写状态机与风险) | +| 流程图 | §7 | +| 交付 | §9 | +| 定稿变更与版本 | §10(见 release-changelog) | + +状态与风险也可在 §6 下设 `### 状态` / `### 风险` 短节,避免另起大结构推翻存量 PRD。 + ```markdown # <模块名> · 产品需求说明(全模块) @@ -110,7 +126,9 @@ flowchart TB > 产品经理原型发版记录。仅记功能与业务逻辑变更;不含样式/UI/表结构。 > 由「需求定稿」关键字触发追加;日常改原型不强制每改必写。详见 [release-changelog.md](release-changelog.md)。 -### 定稿 · YYYY-MM-DD +### 定稿 · V{x.y.z} · YYYY-MM-DD + +判定=<主|副|子|用户指定·…>:<一行依据> - 「<能力名>」:<做了什么 / 逻辑如何变> - … diff --git a/.claude/skills/oneos-autoprd/references/yunxiao-delivery-sync.md b/.claude/skills/oneos-autoprd/references/yunxiao-delivery-sync.md new file mode 100644 index 0000000..4cc6659 --- /dev/null +++ b/.claude/skills/oneos-autoprd/references/yunxiao-delivery-sync.md @@ -0,0 +1,47 @@ +# 【交付】任务描述回填(设计完成 · 按编号) + +与 **`$YunxiaoPMapp`** 组合。创建【交付】时描述保持占位 `等待设计任务完成后自动填入`;**本文件只在设计完成(或显式同步)时**把 AutoPRD Markdown 写入【交付】。 + +**禁止**按任务标题查找交付;**禁止**加载 `yunxiao-requirement-lifecycle`。 + +## 触发时机 + +| 时机 | 动作 | +|---|---| +| YunxiaoPMapp **设计完成**且 AutoPRD 落盘成功 | 必须回填【交付】描述 | +| 口令 `同步交付说明:交付任务=ONEOS-xx`(或需求编号可反查交付) | 按编号回填 | +| 仅创建【交付】 / 分析中 / 设计中尚未设计完成 | **不**写 PRD 正文 | + +## 前置 + +1. 已有【交付】任务编号:口令显式 > 需求 `## 工作项编号(系统)` > ASSOCIATED 反查校验。 +2. 冲突或缺少编号 → 停下询问;禁止按 `【交付】`+标题猜。 +3. 本地已有 `requirements-prd.md`(或本轮刚生成)。 + +## 写入内容 + +- 用与需求「产品说明(AutoPRD)」一致的正文**替换**占位文案。 +- 推荐:原型链接 + `requirements-prd.md` 全文(或 YunxiaoPMapp 约定的产品说明正文)。 +- 可附一句:`原始诉求见需求描述「原始诉求(AutoRDO)」`。 +- 不要删除或改写需求侧 AutoRDO 段(本步骤只改**任务**描述)。 + +## 执行顺序(设计完成组合) + +```text +1. AutoPRD 落盘 MD + 标注同步 +2. 更新需求「产品说明(AutoPRD)」 +3. 按交付任务编号 PATCH 任务描述(替换占位) +4. 附件(ZIP/截图)由 YunxiaoPMapp make-export 流程负责;本文件不替代附件 +5. 任一步失败 → 不得声称交付说明已更新 / 设计完成材料齐全 +``` + +## 回报 + +- 交付任务编号、是否已替换占位、MD 路径 +- 失败时列出缺项(无编号 / 无 MD / 写入失败) + +## 负向 + +- 创建交付当下灌入未完成的 PRD +- 按标题 list 复用「像交付的任务」 +- 引导旧 lifecycle 建同名任务 diff --git a/.claude/skills/oneos-autoprd/references/yunxiao-description.md b/.claude/skills/oneos-autoprd/references/yunxiao-description.md new file mode 100644 index 0000000..9ceb6f3 --- /dev/null +++ b/.claude/skills/oneos-autoprd/references/yunxiao-description.md @@ -0,0 +1,60 @@ +# 云效需求描述:产品说明(对接 YunxiaoPMapp) + +发云效 / 完善需求描述时,与 **`$YunxiaoPMapp`** 双段模板对齐。 +**禁止加载** `yunxiao-requirement-lifecycle`。 + +## 需求描述固定结构(不可互相覆盖) + +```markdown +## 原始诉求(AutoRDO) +(由 AutoRDO 清洗;设计完成也不删除;本 Skill 不覆盖本段) + +## 产品说明(AutoPRD) +(本 Skill 写入:原型链接 + requirements-prd 正文或等价产品说明) + +## 工作项编号(系统) +(由 YunxiaoPMapp 维护;本 Skill 不改本段) +``` + +## 产品说明段推荐拼装 + +```markdown +## 产品说明(AutoPRD) + +### 原型链接 +<{baseUrl}/{prototype-id}/index.html;禁止加 prototypes/ 前缀;禁止去掉 index.html> +无则写「待发布」 + +### 需求说明 +/.spec/requirements-prd.md 全文原样粘贴;禁止摘要顶替> + +### 更新内容 +<第 10 章最新定稿块;无则「首版定稿」或「本轮无功能/逻辑增量」> + +### 更新内容·历史 +<旧更新内容倒序;勿删> +``` + +若 YunxiaoPMapp 本轮只要求「产品说明」短写:至少包含原型链接 + MD 全文或与交付回填一致的正文;**仍禁止**覆盖 AutoRDO / 工作项编号段。 + +## 真相源文件 + +1. `src/prototypes//.spec/requirements-prd.md`(主) +2. 否则 `src/resources/prd/-autoprd.md` + +先 Read 文件再写入;不要凭记忆重写。 + +## 执行步骤 + +1. 确认 prototype-id 与需求编号(若写云效)。 +2. 读 MD 全文。 +3. PATCH/更新需求描述时**只改**「产品说明(AutoPRD)」相关内容;保留原始诉求与工作项编号。 +4. 若同时设计完成:按 [yunxiao-delivery-sync.md](yunxiao-delivery-sync.md) 回填【交付】。 +5. 回报:需求编号、MD 路径、是否全文写入、是否已回填交付编号。 + +## 禁止 + +- 只用第 9 章摘要顶替全文(除非用户只要链接) +- 二次删减章节、去掉 mermaid/表格却声称全文已写 +- 创建【交付】时灌入 PRD(须等设计完成;见交付回填) +- 引用或配合 `yunxiao-requirement-lifecycle` diff --git a/.claude/skills/oneos-autoprd/references/yunxiao-stage-tasks.md b/.claude/skills/oneos-autoprd/references/yunxiao-stage-tasks.md deleted file mode 100644 index 09cfe27..0000000 --- a/.claude/skills/oneos-autoprd/references/yunxiao-stage-tasks.md +++ /dev/null @@ -1,44 +0,0 @@ -# 云效阶段任务自动创建(AutoPRD 负责) - -本规则写在 **oneos-autoprd**,不依赖改写 `yunxiao-requirement-lifecycle`。 -凡本 Skill 参与的云效操作中,需求推进到下列状态时,**同一轮必须**建同名任务并正式关联需求。 - -## 触发状态 - -| 需求推进至 | 任务标签(右侧基础字段) | 负责人 | 任务标题 | -|------------|--------------------------|--------|----------| -| **分析中** | `分析` | **需求创建人**姓名 | 与需求标题**完全同名** | -| **设计中** | `设计` | **需求创建人**姓名 | 与需求标题**完全同名** | -| **待开发** | `交付`(若项目无「交付」标签则用 `开发`) | **何斐** | 与需求标题**完全同名** | - -## 强制字段与关系 - -1. **正式关联需求**:父子或关联项;只挂标题不算关联。创建后必须在界面上能看到需求 ↔ 任务关系。 -2. **标签**:写入任务「标签」基础字段(见上表);标题前缀不能代替标签。 -3. **创建时间沿用需求**:任务的创建时间(及标签侧可见的创建时间口径)尽量与**需求创建时间**一致。 - - 平台允许改创建时间 / 用 API 指定时:写成需求的创建时间。 - - 平台不允许:在任务描述首行注明 `创建时间口径=需求创建时间(YYYY-MM-DD HH:mm)`,并在回报里说明「平台限制未改系统创建时间」。 -4. **幂等**:同一需求、同一阶段标签下,已存在**未取消**且已正式关联的同名任务 → **不新建**,只校验负责人/标签/关联是否正确,缺则补齐。 -5. **顺序**:先确认需求已进入目标状态(或本轮动作将写入该状态),再创建/补齐任务;回报时给出任务编号与链接。 - -## 执行步骤(apply 时) - -```text -1. 读取需求:标题、创建人、创建时间、当前/目标状态、已关联任务列表 -2. 按上表确定本轮阶段标签与负责人 -3. 查重(需求ID + 阶段标签 + 未取消) -4. 无则创建:标题=需求标题;标签=阶段标签;负责人=上表;创建时间口径=需求创建时间;正式关联需求 -5. 有则校验并修补标签/负责人/关联 -6. 回报:需求状态、任务编号、负责人、是否新建/复用 -``` - -## 不在本规则内 - -- 不自动建「测试 / 发版」任务(除非用户另行要求)。 -- 不因样式类 PRD 变更触发建任务;仅**需求状态**进入分析中/设计中/待开发时触发。 -- 云效生命周期细则、流水线、缺陷闭环仍可由 `$yunxiao-requirement-lifecycle` 配合执行;**本建任务逻辑以 AutoPRD 本文为准**。 - -## 与定稿 / 需求说明的关系 - -- 写「需求说明 / 更新内容」仍按 AutoPRD 主流程与定稿流程。 -- 推进到待开发且需要描述时:先 AutoPRD 文档,再改状态,再按本文件建任务(负责人何斐)。 diff --git a/.cursor/rules/oneos-autoprd-sync.mdc b/.cursor/rules/oneos-autoprd-sync.mdc index a679800..2d64841 100644 --- a/.cursor/rules/oneos-autoprd-sync.mdc +++ b/.cursor/rules/oneos-autoprd-sync.mdc @@ -1,5 +1,5 @@ --- -description: 改原型跟进 AutoPRD;定稿写功能变更;云效建需求先跑 AutoPRD;状态推进建同名任务 +description: 改原型跟进 AutoPRD;定稿写功能变更与版本号;云效仅与 YunxiaoPMapp 组合;禁止旧 lifecycle 同名任务 alwaysApply: true --- @@ -7,7 +7,7 @@ alwaysApply: true ## 改原型 -修改 `src/prototypes/**` 且涉及**行为、文案、流程、判定或验收**时,同一轮必须跟进 **oneos-autoprd**: +修改 `src/prototypes/**` 且涉及**行为、文案、流程、判定或验收**时,同一轮必须跟进 **oneos-autoprd**(AutoPRD): 1. 更新 `.spec/requirements-prd.md`(用户故事:起点 → 怎么运作 → 闭环)。 2. 更新 `annotation-source.json` **顶层** `directory.nodes` 中「产品需求说明(PRD)」。 @@ -17,26 +17,16 @@ alwaysApply: true ## 需求定稿 -用户回复含 `需求定稿` / `定稿` / `确认定稿` / `本次定稿` 时: +用户回复含 `需求定稿` / `定稿` / `确认定稿` / `本次定稿` / `本轮定稿` 时: -1. 按 skill `references/release-changelog.md` 汇总**自上次定稿**的功能/逻辑变更。 -2. 追加到 PRD `## 10. 功能变更记录`(最新在上);更新 `.spec/autoprd-baseline.json`。 +1. 按 skill `references/release-changelog.md` 汇总**自上次定稿**的功能/逻辑变更,并递增 `prdVersion`(用户显式指定主/副/子时以用户为准)。 +2. 追加到 PRD `## 10. 功能变更记录`(最新在上);更新 `.spec/autoprd-baseline.json`(含 `prdVersion`)。 3. **不要**写布局/UI/表结构类条目。 -## 云效建需求 +## 云效(唯一组合 · YunxiaoPMapp) -使用 `yunxiao-requirement-lifecycle` **创建或完善**需求描述时,**先**跑 `oneos-autoprd`: +产品侧写云效**只**与 `$YunxiaoPMapp` 组合;**禁止加载** `yunxiao-requirement-lifecycle`。 -- 需求说明 ← AutoPRD 正文 -- 更新内容 ← 第 10 章自上次定稿以来的条目(无则首版/无增量) -- 旧更新内容 →「更新内容·历史」 - -## 云效状态 → 同名任务(AutoPRD 规则,不改云效 Skill) - -需求推进至 **分析中 / 设计中 / 待开发** 时,按 `references/yunxiao-stage-tasks.md`: - -- 建与需求**同名**任务并正式关联 -- 标签:分析 / 设计 / 交付(或开发) -- 创建时间口径沿用需求 -- 分析中、设计中负责人=创建人;待开发负责人=**何斐** -- 同阶段已有未取消关联任务则复用,不重复建 +- 入库碎片:先 `$AutoRDO`,再 YunxiaoPMapp 记录需求(原始诉求段)。 +- 设计完成 / 完善产品说明:先 `oneos-autoprd` 落盘 MD,再写需求 `## 产品说明(AutoPRD)`;按交付**任务编号**回填【交付】描述(创建交付时保持占位,见 AutoPRD `yunxiao-delivery-sync.md`)。 +- 【交付】/【分析】/【设计】树与交棒由 **YunxiaoPMapp** 负责;AutoPRD **不**建同名无前缀阶段任务。 diff --git a/.cursor/rules/oneos-v2-design-system.mdc b/.cursor/rules/oneos-v2-design-system.mdc new file mode 100644 index 0000000..0e78dd4 --- /dev/null +++ b/.cursor/rules/oneos-v2-design-system.mdc @@ -0,0 +1,85 @@ +--- +description: OneOS V2 全局定版设计规范(Stripe Violet 紫光高规 + 3视角架构 + PC/H5 移动端 100% 响应式 + 全量 UI 控件库 + 若依动态主题色 + 车牌号无点标准) +globs: src/prototypes/oneos-v2/**/*, src/prototypes/lease-contract-management/**/*, src/resources/design-system/**/*, src/prototypes/**/* +alwaysApply: true +--- + +# OneOS V2 全局定版设计规范(Cursor Agent 强制执行规则) + +> **【硬性准则】**:在 `OneOS V2` 目录下**生成新页面、修改或迁移任何原型页面**时,AI Agent **必须首先 Read** 并严格遵守 `src/resources/design-system/DESIGN.md` 或 `src/prototypes/oneos-v2/DESIGN.md`! + +--- + +## 0. 新页面生成与修改工作流 (Mandatory Workflow) + +1. **强前置步骤**:在创建或修改 `OneOS V2` 下的任何页面前,必须读取 `src/resources/design-system/DESIGN.md`,确认最新的 Token、UI 控件用法、3 视图模板和响应式规范。 +2. **禁止使用原生 HTML 控件**:严禁直接使用原生 `` 或未经过 V2 封装的第三方组件。必须统一导入并使用 `UIComponents.tsx` 导出的封装控件: + - `V2Button`(主/次/描边/幽灵/危险/返回;详见 `DESIGN.md` §3.0) + - `V2Select` + - `V2DatePicker` + - `V2DateRangePicker` + - `V2SingleInputDateRangePicker`(单输入框双日历,默认推荐) + - `V2TimePicker` + - `V2SingleInputTimeRangePicker`(单输入框双时间) + - `V2RadioGroup` + - `V2CheckboxGroup` + - `V2Switch` + - `V2Steps` + - `V2Timeline` + - `V2ApprovalProgress`(默认 `direction="vertical"` 垂直贯穿时间轴) + - `V2Pagination`(统一分页控制器) + - `V2Empty`(空状态与异常页组件) + - `V2SegmentedControl`(顶栏视图/模式分段切换控件) + - `V2StatusTabs`(分类/状态过滤页签条) + - `V2ImageUpload`(图片拖拽/新增上传 · Web Dropzone + H5 拍照/相册;详见 `DESIGN.md` §3.17) + - `V2MobileHeader` + - `V2MobileBottomNav` + - `V2MobileActionBar` +3. **母版参考**: + - **台账三视角**:`src/prototypes/lease-contract-management/LeaseContractHub.tsx`(13 项高阶筛选、3 视图、嵌套车辆子表)。 + - **结构化工单/处置表单页**:`src/prototypes/lease-contract-redesign/FaultDispositionForm.tsx`(左主表单三段卡 + 右 340px 指派/SLA;详见 `DESIGN.md` §4.8)。 +4. **筛选查询收起(强制)**:台账筛选栏 /「更多筛选」展开后,点击 **查询** 必须应用条件并**自动收起**筛选栏(H5 为关闭 Bottom Sheet);**重置**清空后同样收起。详见 `DESIGN.md` §2.4.3。 +5. **列表操作列(强制)**:常用「编辑 / 处理(处置)」**外侧**展示(最多 2 个);低频「查看记录 / 操作记录 / 历史」及危险操作收入 **⋮ 更多**。统一使用 `OperationActions`(`DESIGN.md` §3.16 / `vm-shared/DESIGN.md`)。 + +--- + +## 1. 核心视觉与配色规范 (Stripe Fintech UI) + +- **主色 (Primary Accent)**:Stripe Violet `#533AFD`(悬停 `#6346FF`,Focus 环 `#4226E8`,浅亮底 `#E0E7FF`,暗亮底 `rgba(83, 58, 253, 0.18)`) +- **画布背景**:浅色 `#F6F9FC` / 深色 `#0A0B0D` +- **卡片容器**:浅色 `#FFFFFF` / 深色 `#121418` +- **边框线**:浅色 `#E3E8EE` / 深色 `#23272F` +- **主正文**:浅色 `#0A2540` / 深色 `#F7FAFC` +- **次要正文**:浅色 `#425466` / 深色 `#A0AEC0` +- **语义色**:成功 `#10B981` · 预警 `#D97706` · 危险/错误 `#EF4444` · 提示 `#3B82F6` +- **若依 (RuoYi) 框架动态主题色**:所有组件背景、边框、选中态必须消费 CSS 变量 `var(--oneos-primary, var(--ln-primary, #533AFD))`,兼容若依的主题色动态切换。 +- **双色模式**:通过 `[data-ds-mode="dark"]` 或 `[data-oneos-theme="dark"]` 响应,严禁出现外壳深内容浅的失配情况。 +- **车牌号规范**:车牌号严禁出现间隔点 `·`(必须形如 `浙A88888F`、`京A66666`)。 + +--- + +## 2. PC / H5 移动端 App 嵌入 100% 响应式 (Mobile H5 Standard) + +- **双形态适配**:所有 V2 原型必须支持 PC 屏宽 (≥1024px) 与 H5 移动端 (≤767px) 形态。 +- **触控规格**:移动端 (≤767px) 场景下,所有可点击元素与输入框触控高度必须 **`≥ 44px`**(`min-height: 44px`),正文字号 **`≥ 14px`**。 +- **Mobile Bottom Sheet**:移动端下 `V2Select`、`V2DatePicker` 等弹窗自动转为固定吸底 **Bottom Sheet 面板**,配有顶部抓手条与 44px 确定按钮。 +- **移动端底部操作条**:提交与主操作按钮在移动端自动固定吸底(`V2MobileActionBar`)。 + +--- + +## 3. 统一的三视角页面架构 (Three Core Views) + +任何台账、列表、审批与履约管理页面必须提供或集成 3 视角模板: +1. **列表模式 (List View)**:包含 Bento Grid KPI 大盘 + Pill Tabs + 13 项高阶筛选(查询/重置后自动收起)+ 展开式嵌套子表格。 +2. **看板模式 (Kanban View)**:包含 4 阶段 Pipeline 管道列(卡片快速操作与流程推进)。 +3. **主从表单模式 (Split / Master-Detail View)**:左侧 340px 搜索任务/单据列表 + 右侧深度工作台(包含结构化表单、关联业务 Tabs 及履约图谱)。 + +--- + +## 4. 规范参考文件路径 + +- 全局主规范文档:`src/resources/design-system/DESIGN.md` +- 规范展示页:`src/prototypes/oneos-v2/DesignSystemShowcase.tsx` +- 组件库文件:`src/resources/design-system/components/UIComponents.tsx` +- 全局 Token CSS:`src/resources/design-system/oneos-ds-tokens.css` +- 若依预设导出:`src/resources/design-system/ruoyi-theme-preset.json` & `ruoyi-oneos-v2-theme.css` diff --git a/.cursor/rules/yunxiao-record-requirement-fast-path.mdc b/.cursor/rules/yunxiao-record-requirement-fast-path.mdc index d250731..7c42c76 100644 --- a/.cursor/rules/yunxiao-record-requirement-fast-path.mdc +++ b/.cursor/rules/yunxiao-record-requirement-fast-path.mdc @@ -1,17 +1,17 @@ --- -description: 云效「记录需求」须先点选优先级/推进至;统一运营管理平台走快路径,禁止默认参数与盲目探测 +description: 产品侧云效写操作走 YunxiaoPMapp;强制 Plan 门禁;禁止加载已删除的 yunxiao-requirement-lifecycle globs: alwaysApply: true --- -# 云效记录需求 · 参数门禁与快路径 +# 云效记录需求 · 产品侧唯一入口 -当用户使用 `$yunxiao-requirement-lifecycle` / 「记录需求」,且涉及优先级、推进至时: +产品侧凡「记录需求 / 推进至 / 交棒 / 创建迭代」等**会改云效**的操作: -1. **必须先拿到明确选择**:优先级(紧急/高/中/低)、推进至(分析中/设计中/设计完成/开发中)、**标签(从云效标签 catalog 点选,可多选)**。优先 `AskQuestion`;不可用则用 Plan 确认并由用户写明 A+B+C。 -2. **禁止**在未选择时默认「中 + 分析中」并自动建单。「批准/Implement 计划」≠ 已完成点选。 -3. 项目为**统一运营管理平台**(含 PC 端别名)时:先读 - `.cursor/skills/yunxiao-requirement-lifecycle/references/oneos-pc-fast-path.md` 与 - `.cursor/skills/yunxiao-requirement-lifecycle/assets/oneos-pc-runtime-ids.json`(标签清单见同目录 `oneos-pc-tag-catalog.md`),按已验证 API/ID 执行;禁止重复探测 create URL。 -4. **标签禁止**按 `lines.ts` / 业务条线说明自动推断;只能用用户点选的 catalog 名。API 打标失败时 UI 兜底;仍失败则停下请用户回复标签名,不得声称已成功。 -5. 优先 API(创建时写入描述;标签用 `PATCH … propertyKey=tag`);浏览器仅作标签失败或状态连跳兜底;少截图、同失败不循环重试。 +0. **只使用 `$YunxiaoPMapp`**(及入库前 `$AutoRDO`、设计完成 `$oneos-autoprd`)。 + **禁止加载**已删除的 `yunxiao-requirement-lifecycle`;勿按旧同名阶段任务规则建单。 +1. **强制先切 Cursor Plan 模式**(`SwitchMode` → `plan`):对齐需求/任务**编号**、推进目标、交付树策略等;用户确认后再一口气 apply。 +2. 标题规范:`【新增】…` / `【优化】…`(详见 YunxiaoPMapp / 口令面)。 +3. 查重与复用**只认任务编号**;创建【交付】描述占位,设计完成再灌 AutoPRD。 +4. 项目常量见 `.cursor/skills/YunxiaoPMapp/assets/runtime-ids.json`(勿再引用旧 lifecycle 路径)。 +5. 细则以 `$YunxiaoPMapp` Skill 全文为准。 diff --git a/.cursor/skills/AutoRDO/SKILL.md b/.cursor/skills/AutoRDO/SKILL.md new file mode 100644 index 0000000..aa2dae4 --- /dev/null +++ b/.cursor/skills/AutoRDO/SKILL.md @@ -0,0 +1,83 @@ +--- +name: AutoRDO +description: >- + AutoRDO (Requirement Description Optimization): cleans fragmented chat logs, + voice transcripts, oral notes, and feedback ledgers into written title and description; + auto-detects 类型/优先级/标签/提交部门/提交人 from content; maps OneOS modules via oneos-domain.md; + splits multi-line into multiple results; if any 待确认 exists, MUST switch to Plan mode + for choice-based confirmation. Does not change Yunxiao status or create tasks. + Pair with YunxiaoPMapp for cloud write; never load yunxiao-requirement-lifecycle. +--- + +# AutoRDO + +**Requirement Description Optimization**(需求描述优化)。 +将碎片化、通俗化文字(聊天/录音/反馈台账)在**保留原意**前提下拆解为**标题、描述**,并**自动识别**类型、优先级、标签、提交部门、提交人,供入库写入云效需求。 +多行/多条独立诉求 → **自动拆成多份**转译结果。 + +**强制**:清洗结果中**只要存在任意「待确认」**,Agent **必须立刻** `SwitchMode` → **plan**,逐条用选择题消解;**无需**用户再说「确认待确认」。无待确认则可直接交定稿。 + +## 边界(强制) + +| 做 | 不做 | +|---|---| +| 提炼标题与书面描述;自动识别类型/优先级/标签/提交部门/提交人 | 改云效状态 / 建任务 / **直接**打云效标签 | +| 多行/多条 → 多份转译结果 | 写成完整 AutoPRD / 臆造业务结论 | +| 有待确认 → 强制 Plan 逐条确认 | 停在初稿等口令;把猜测写入定稿 | +| 去除描述**结尾句号** | 加载 `yunxiao-requirement-lifecycle` | + +云效建单与打标由 **`$YunxiaoPMapp`** 负责;本 Skill 只出清洗稿与**推荐元数据**。 + +## 何时使用 + +- 口令含 `AutoRDO` / `清洗聊天` / `录音整理` / `原始诉求` +- 粘贴反馈台账(含部门/优先级/模块列)或口述碎片 +- YunxiaoPMapp「记录需求」前必须先跑本 Skill(材料为碎片/台账时) + +## 输入 + +- 聊天、会议速记、口述、录音转写 +- **多行/台账**:含反馈人、所属部门、业务模块、优先级、PC或移动端等列时优先采信列值 +- 确认阶段补充文字 + +## 处理规则 + +**强制前置**:ONE-OS 材料先 Read [references/oneos-domain.md](references/oneos-domain.md),元数据规则见 [references/meta-fields.md](references/meta-fields.md),再按 [references/rules.md](references/rules.md) 执行。 + +1. **多条拆解** → 每条独立成稿 +2. **OneOS 对齐** → 模块/部门标准名 +3. **元数据识别** → 类型、优先级、标签、提交部门、提交人(显式列 > 正文标记 > 语义推断;不确定 → 待确认) +4. **标题 + 描述转译** → 去口语、去结尾句号 +5. **有待确认 → 强制 Plan** → 见 [references/confirm-pending.md](references/confirm-pending.md) + +## 输出 + +```markdown +## 原始诉求(AutoRDO) + +**标题**:<清晰标题> +**类型**:【新增】|【优化】 +**优先级**:P1-高|P2-中|P3-低 +**标签**:<标准模块>[, PC端|小程序] +**提交部门**:<标准部门名或空> +**提交人**:<姓名或空> + +**描述**: +<转译正文;无结尾句号> +反馈:<有则附日期/禅道/状态/端> + +待确认: +- … +``` + +多条时先写「共 N 条」,再 `### 1` … `### N`。 +交 YunxiaoPMapp 示例:`记录需求:标题=…;类型=…;优先级=…;标签=…;提交部门=…;提交人=…;描述=…` + +## 口令 + +```text +AutoRDO:<粘贴聊天或台账行> +AutoRDO:录音转写如下 … +按下列补充消解待确认: +<补充说明> +``` diff --git a/.cursor/skills/AutoRDO/agents/openai.yaml b/.cursor/skills/AutoRDO/agents/openai.yaml new file mode 100644 index 0000000..a3ccd63 --- /dev/null +++ b/.cursor/skills/AutoRDO/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "AutoRDO" + short_description: "清洗诉求并识别类型优先级标签部门提交人" + default_prompt: "按 AutoRDO 清洗:先读 oneos-domain.md 与 meta-fields.md;多行拆多条;出标题+描述+类型/优先级/标签/提交部门/提交人。有待确认则同轮强制切 Plan 用选择题消解;不改云效、不直接打标、不贴词典全文。" diff --git a/.cursor/skills/AutoRDO/references/confirm-pending.md b/.cursor/skills/AutoRDO/references/confirm-pending.md new file mode 100644 index 0000000..24dec13 --- /dev/null +++ b/.cursor/skills/AutoRDO/references/confirm-pending.md @@ -0,0 +1,126 @@ +# AutoRDO · 待确认逐条 Plan 确认 + +## 目标 + +清洗稿中的「待确认」经产品经理**逐条人工确认**后,回填进标题/描述,得到可交 YunxiaoPMapp 的定稿。 +确认过程**优先选择题**;选项用 [oneos-domain.md](oneos-domain.md) 辅助生成;也支持用补充说明/需求描述文字直接消解待确认点。 + +## 何时进入(强制) + +```text +清洗初稿产出后: + 若任一需求含「待确认」→ 同轮必须 SwitchMode → plan + 若全部无待确认 → 不必进 Plan,初稿即定稿 +``` + +- **触发条件只有一条**:存在待确认(单条或多条中任意一点) +- **禁止**要求用户再说「确认待确认」「开始确认」才进 Plan +- 用户在 Plan 中可用选择题作答,也可粘贴补充说明消解 + +**不进 Plan 的唯一情况**:清洗结果中没有任何「待确认」条目。 + +## Plan 门禁(强制) + +1. 清洗初稿写出后(可先展示初稿摘要),**立刻** `SwitchMode` → **plan**,说明:存在待确认,须逐条确认后再回填定稿;本阶段仍**不写云效** +2. Plan 列出:待确认的**需求条号**、每条下的待确认点清单、推荐确认方式(选择题 / 文字补充) +3. **用户确认 / 批准 Plan / 「执行」之前**:禁止把未确认内容写进定稿描述 +4. 批准后:按条推进确认 → 回填 → 输出定稿 + +## 逐条确认顺序 + +```text +需求 1 的待确认点 1 → 点 2 → … → 需求 1 定稿片段 +需求 2 的待确认点 1 → … +… +全部完成后输出「已确认定稿」全集 +``` + +- **一次只推进一个待确认点**(或同一点下的一组互斥选项),避免一屏堆十几个问题 +- 多条需求时,Plan 清单可注明建议从第 1 条起;用户可指定跳过或改序 +- 某条无「待确认」→ 跳过,保留初稿即可 + +## 确认方式(优先选择) + +每个待确认点按下列优先级出题: + +| 优先级 | 方式 | 适用 | +|---|---|---| +| 1 | **选择题**(2–5 个互斥选项 + 可选「其他/我补充」) | 模块归属、部门名称、类型/优先级、是否/范围、枚举字段、拆条边界等 | +| 2 | **多选** | 明确可多选的范围(如适用端:PC+H5) | +| 3 | **文字补充** | 无法枚举、需自由描述字段清单/规则细节时 | + +### 选项如何用 OneOS 词典生成 + +先 Read `oneos-domain.md`,再出选项: + +- **模块名不清**:候选 = 词典中相关条线的标准模块名(如还车应结款 / 违章管理 / 还车管理),勿自造模块 +- **部门/角色口语**:候选 = 第 1 节标准名(业管→业务管理组,安全小组→安全部 等)+「保持单据原文分区名」 +- **跨条线联动**:选项写清主责模块 vs 联动对象;标题只挂主责,联动写描述 +- **故事点细节**:仅当材料或用户补充已暗示时,才把词典闭环中的合理分支列为选项;**禁止**把未提及的故事点细节默认选中 + +每题结构建议: + +```text +【需求 n · 待确认 k】<待确认原文> +依据词典,请选择: +A. … +B. … +C. … +D. 其他(请补充一句) +也可用一段需求描述直接回答本点 +``` + +## 用需求描述补充 + +用户可用以下任一方式消解待确认,**不必**每题都点选: + +- 粘贴一段补充说明 / 功能规则 / 字段清单 +- 回复「按下列描述确认:…」 +- 对某一待确认点直接写答案句 + +Agent 须: + +1. 将补充文字**映射**到对应待确认点(可一次消解多点) +2. 写入该条**描述**(书面化、去结尾句号),从「待确认」列表**移除已消解项** +3. 补充仍含糊的部分 → 保留或新开「待确认」,继续选择题 +4. **不**把补充里未说的内容脑补进去 + +## 回填定稿规则 + +确认完成后,对该条输出「已确认」版本: + +```markdown +### n(已确认) + +## 原始诉求(AutoRDO) + +**标题**:… + +**描述**: +<初稿 + 已确认信息合并;无结尾句号> + +待确认: +- (无则写「无」或省略本段) +``` + +- 标题若因模块/动作确认而需微调,可改,并在确认回合说明改了什么 +- 未确认点**不得**假装已确认;可标「本条暂缓,待确认仍保留」 +- 全部条完成后,给总览:已确认条数 / 仍含待确认条数 + +## 口令 + +```text +AutoRDO:<材料> → 有待确认则自动进 Plan +按下列补充消解待确认: +<粘贴补充说明> +第 6 条待确认选 A;第 7 条补充:字段非必填,计入应补汇总 +``` + +(「确认待确认」等口令**不再作为**进 Plan 的前置条件;有待确认即强制进。) + +## 不做 + +- 确认阶段仍不改云效、不建任务 +- 不把 `oneos-domain.md` 全文贴进定稿 +- 不跳过 Plan 直接把「猜的答案」写进描述 +- 不等用户额外口令才进 Plan(有待确认必须进) diff --git a/.cursor/skills/AutoRDO/references/meta-fields.md b/.cursor/skills/AutoRDO/references/meta-fields.md new file mode 100644 index 0000000..e0b8a3d --- /dev/null +++ b/.cursor/skills/AutoRDO/references/meta-fields.md @@ -0,0 +1,96 @@ +# AutoRDO · 元数据自动识别 + +从粘贴内容(台账表格行、聊天、口述)自动识别下列字段,写入每条原始诉求输出;**不**直接改云效打标(打标由 YunxiaoPMapp 执行)。 + +## 输出字段 + +| 字段 | 说明 | 无依据时 | +|---|---|---| +| **类型** | `【新增】` / `【优化】`(与云效标题前缀一致) | 默认 `【优化】`,并列入待确认 | +| **优先级** | `P1-高` / `P2-中` / `P3-低` | 默认 `P2-中`,高不确定则待确认 | +| **标签** | 主模块标签(对齐 `oneos-domain.md` 标准模块名);可附端标签 `PC端` / `小程序` | 仅模块不确定则待确认 | +| **提交部门** | 映射到词典标准部门名 | 材料无部门且无法推断 → 待确认或空 | +| **提交人** | 反馈人/用户姓名 | 材料无人名 → 空(不臆造) | + +## 识别优先级 + +```text +1. 显式列/字段(反馈台账:优先级、所属部门、反馈方、业务模块、PC或移动端…) +2. 正文中的明确标记(P1、【优化】、@某人、部门署名) +3. 语义推断(用词 + oneos-domain 模块/部门) +4. 仍不确定 → 待确认(选择题),禁止假装已确认 +``` + +## 类型(【新增】/【优化】) + +| 信号 | 类型 | +|---|---| +| 新增、增加、支持、允许、增加列/字段/入口、批量导入(首次能力) | 【新增】 | +| 优化、调整、修改、放开、更清晰、重构、移动至、改由、精确到、兼容 | 【优化】 | +| 台账/口令已写 `【新增】`/`【优化】` | 以显式为准 | +| 同时含新增与优化语义 | 以**主诉求**定类型;次要点写在描述 | + +标题可带类型前缀供入库:`【优化】交车管理:…`;若用户只要裸标题,类型仍单独输出字段。 + +## 优先级 + +| 信号 | 优先级 | +|---|---| +| 列值 `P1-高` / `P1` / 紧急、阻塞、无法办理、P1 | `P1-高` | +| 列值 `P2-中` / `P2` | `P2-中` | +| 列值 `P3-低` / `P3` / 文案、置灰、体验微调 | `P3-低` | +| 无信号 | 默认 `P2-中` | + +## 标签 + +1. **主标签** = 归属标准模块名(`oneos-domain.md`),如 `还车应结款`、`违章管理`、`交车管理` +2. 台账「业务模块」列优先;口语映射到标准名(验车管理→验车入库,调拨管理→车辆调拨) +3. **端标签**(可选,可多选):材料含 `PC端`/`小程序`/`PC及小程序` 时写入 +4. 「其他」「全局优化」「小程序审批中心」等非模块名:主标签用最贴近模块或 `系统全局`;勿发明云效不存在的随意标签名 +5. 输出格式:逗号分隔,如 `还车应结款, PC端` + +## 提交部门 + +| 材料常见写法 | 标准输出 | +|---|---| +| 业务管理部、业管、业务管理 | 业务管理组 | +| 运维部、运维 | 运维部 | +| 数智中心、数智、信息化 | 数智部 | +| 安全部、安全 | 安全部 | +| 财务部、财务 | 财务部 | +| 法务部、法务 | 法务部 | +| 采购部/采购组 | 按词典区分输出 | + +台账「所属部门」列优先;聊天中「我们运维…」可推断,低置信度则待确认。 + +## 提交人 + +- 台账「反馈方/用户」「反馈人」列 → 原样作为提交人 +- 聊天 @姓名、署名「张三:」→ 取该名 +- 多人联合反馈 → 取主反馈人(首列/首个署名);其余可写描述附注 +- **禁止**用当前对话用户名冒充提交人(除非材料写明) + +## 与描述附注的关系 + +- **结构化字段**(类型/优先级/标签/提交部门/提交人)单独成行,供 YunxiaoPMapp 建单口令复用 +- 禅道编号、处理状态、反馈日期等 → 可放描述末行「反馈:…」附注,不升格为强制字段 + +## 输出模板(每条) + +```markdown +## 原始诉求(AutoRDO) + +**标题**:<清晰标题;可选带【新增】/【优化】前缀> +**类型**:【新增】|【优化】 +**优先级**:P1-高|P2-中|P3-低 +**标签**:<模块>[, PC端|小程序] +**提交部门**:<标准部门名或空> +**提交人**:<姓名或空> + +**描述**: +<转译正文;无结尾句号> +反馈:<日期 · 原文部门 · 状态 · 禅道号 · 端 …>(有则写) + +待确认: +- … +``` diff --git a/.cursor/skills/AutoRDO/references/oneos-domain.md b/.cursor/skills/AutoRDO/references/oneos-domain.md new file mode 100644 index 0000000..5a47ee4 --- /dev/null +++ b/.cursor/skills/AutoRDO/references/oneos-domain.md @@ -0,0 +1,399 @@ +# ONE-OS 业务领域词典(供 AutoRDO 对齐) + +> 来源:业务条线说明原型 `lease-business-line-overview` +> 用途:清洗标题/描述时,优先用下列**标准模块名、部门名、闭环表述**对齐,避免口语臆造模块或错挂条线。 +> 系统定位:1 套系统 · 5 大业务条线闭环 · 3 大数据底座 · 演进方向为业财一体 +> **强制**:本文件只用于理解与用词对齐;**禁止**把本词典全文贴进云效描述或 AutoRDO 输出稿。 + +--- + +## 0. 系统总览 + +| 维度 | 口径 | +|---|---| +| 平台名 | ONE-OS(亦写作 OneOS) | +| 五大业务条线 | 租赁 · 能源 · 运维 · 安全 · 物流 | +| 三大数据底座 | 车辆资产运营情况 · 各业务条线盈亏情况 · 里程调度情况 | +| 现阶段 | 业务驱动 · 财务手工维护(收款/付款/核销多人工) | +| 演进方向 | 业财闭环一体化(打通财务 YS,「收款记录」「付款记录」与业务台账双向回写) | + +**标题提炼提示**:口述涉及合同/台账/交还车/氢费/证照/调度时,先归入对应条线再落模块名;跨条线联动(如还车应结款联动违章事故)在描述中写明联动对象,标题以主责模块为准。 + +--- + +## 1. 业务部门 / 角色词典(标准用词) + +清洗时统一下列名称;同义口语请映射到标准名。 + +| 标准名称 | 常见口语/别称 | 主要出现条线 | +|---|---|---| +| 业务管理组 | 业管、业务管理 | 租赁、运维、安全、物流 | +| 业务管理组-能源部 | 能源组、业管能源、业务管理部-能源组 | 能源、租赁还车 | +| 业务服务组 | 业务服务 | 物流 | +| 业务员 | 业务 | 租赁 | +| 调度岗 | 调度 | 物流 | +| 法务部 | 法务 | 租赁、运维采购合同 | +| 财务部 | 财务 | 租赁评级、能源账户、运维付款/维保、安全违章事故、物流台账 | +| 安全部 | 安全 | 租赁评级、证照、安全条线 | +| 运维部 | 运维、区域操作员 | 运维全链路、交还车、租赁提车后交车 | +| 采购部 | 采购 | 能源加氢站、运维采购合同/验车 | +| 采购组 | 采购组 | 供应商管理(能源/运维) | +| 数智部 | 数智、信息化 | 合同模板电子化、能源扩展 | +| 董事长 | 老板、总 | 非标合同、资产出售过户报废确认 | +| 司机 | 提车司机、内部司机 | 交车培训、故障、安全培训 | +| 加氢站 / 签约加氢站 | 站点、站方 | 能源 | + +**组织边界提示** + +- 「采购组」侧重供应商主数据;「采购部」侧重站点管理与采购/三方租赁合同。 +- 「业务管理组」与「业务管理组-能源部」勿混用;氢费、加氢对账默认能源部。 +- 物流调度明确:**非 TMS**,不做配载与承运商调度。 + +--- + +## 2. 功能模块总表(按条线) + +### 2.1 租赁业务条线(6 模块 · 全链路闭环) + +**条线闭环**:客户评级决定是否可签 → 标准模板支撑签约 → 租赁合同进入履约 → 提车应收对齐后交车 → 台账承接租期账单 → 还车应结款会签归档。 + +| 步 | 模块名 | 一句话定位 | 责任部门 | +|---|---|---|---| +| 1 | 客户管理 | 准入评级 · 决定能否签约、走哪条流程 | 业务管理组、法务部、财务部、安全部 | +| 2 | 标准合同管理 | 模板配置 · 支撑电子合同一键发起 | 法务部、数智部 | +| 3 | 租赁合同 | 签约发起 · 标准 / 非标 / 禁止三条路径 | 业务员、法务部 | +| 4 | 提车应收款 | 应收生成 · 实收对齐 · 触发交车 | 业务员、法务部、业务管理组、运维部 | +| 5 | 租赁业务台账 | 交车后账单 · 周期收费与实收延续 | 运维部、业务员、业务管理组 | +| 6 | 还车应结款 | 还车结算 · 多部门会签 · 归档闭环 | 业务管理组、安全部、运维部、业务管理组-能源部 | + +**关键结果标签(客户管理)**:可标准签约 · 可签·走非标 · 禁止签约·高风险 + +--- + +### 2.2 能源业务条线(6 模块 · 业财闭环) + +**条线闭环**:供应商主数据支撑站点绑定 → 签约站账号与订单主动上报 → 非签约站氢费补录对账 → 客户对账单关联收款 → 加氢记录标记已付款 → 成熟后向采购端与司机端延伸。 + +| 步 | 模块名 | 一句话定位 | 责任部门 | +|---|---|---|---| +| 1 | 供应商管理 | 加氢/充电站供应商 · 站点绑定与业财直连 | 采购组、财务部 | +| 2 | 加氢站管理 | 签约站 / 非签约站 · 账号、余额与价格 | 采购部、加氢站 | +| 3 | 加氢订单 | 主动上报 · PLC 自动采集 · 预约接单 | 签约加氢站、业务管理组-能源部 | +| 4 | 车辆氢费明细 | 非签约站补录 · 对账归档 · 余额关联 | 业务管理组-能源部 | +| 5 | 氢费账户 | 客户预付款 · 对账单 · 业财收款闭环 | 业务管理组-能源部、财务部 | +| 6 | 扩展规划 | 上游采购 · 下游司机服务 | 采购部、业务管理组-能源部、数智部 | + +--- + +### 2.3 运维管理条线(15 模块) + +**主线(main)**:验车入库 → 上牌 → 证照 → 备车 → 交车 → 还车 → 出售/过户/报废出库 +**并行能力(support)**:供应商、采购合同、替换车、故障、年审、维保、异动、调拨(无严格先后) + +**条线闭环**:供应商与采购合同入库 → 上牌证照与备车整备 → 交车/替换/还车闭环 → 故障与年审维保 → 异动调拨 → 出售/过户/报废审批出库。 + +| 步 | 模块名 | 轨道 | 一句话定位 | 责任部门 | +|---|---|---|---|---| +| 1 | 供应商管理 | support | 整车厂 / 三方租赁 · 付款底座 | 采购组、财务部 | +| 2 | 车辆采购 / 三方租赁合同 | support | 车型数量 · 付款关联 · 验车任务 | 采购部、法务部、财务部 | +| 3 | 验车入库 | main | 采购 / 三方来源 · 验完自动入库 | 运维部、采购部 | +| 4 | 车辆上牌 | main | 车架号入库 · 完成牌证登记 | 运维部 | +| 5 | 证照管理 | main | 一车一档 · 到期待办推送 | 运维部、安全部 | +| 6 | 备车管理 | main | 交车前整备 · 随时可交付 | 运维部 | +| 7 | 交车管理 | main | 司机培训留痕 · 区域派单 · OCR 比对 · 电子签 | 运维部、业务管理组、司机 | +| 8 | 替换车管理 | support | 临时 / 永久替换 · 费用自动核算 | 运维部、业务管理组 | +| 9 | 还车管理 | main | 区域派单 · 费用自动计算 · 电子签 | 运维部、业务管理组 | +| 10 | 故障管理 | support | AI 助手 + 人工 · 证据链归档 | 运维部、司机 | +| 11 | 年审记录 | support | 提前 3 个月 · OCR 刷新证照 | 运维部 | +| 12 | 维修与保养记录 | support | 费用汇总 · 业财一体 · 盈亏核算 | 运维部、财务部 | +| 13 | 车辆异动 | support | 出库维修 / 年审 / 保养 · 成本归集 | 运维部 | +| 14 | 车辆调拨 | support | 跨区调拨 · 运维权转移 | 运维部、业务管理组 | +| 15 | 车辆出售、过户、报废 | main | 资产变更审批 · 过程留痕 · 自动出库 | 运维部、董事长、财务部 | + +--- + +### 2.4 安全管理条线(6 模块 · 安全闭环) + +**条线闭环**:司机信息与证照 → 定期培训与资料库学习 → 违规记录关联评级 → 违章/事故登记 → 联动还车应结款与代处理费用。 + +| 步 | 模块名 | 一句话定位 | 责任部门 | +|---|---|---|---| +| 1 | 司机管理 | 物流内部司机 · 信息证照一体 | 安全部、业务管理组 | +| 2 | 内部司机培训 | 定期安全培训 · 证据链留痕 | 安全部、司机 | +| 3 | 安全培训资料库 | 资料上传 · APP / 小程序学习 | 安全部、司机 | +| 4 | 违规记录 | 司机违规 · 关联评级 | 安全部、业务管理组 | +| 5 | 违章管理 | 分值罚款 · 还车联动 · 代处理 | 安全部、财务部、业务管理组 | +| 6 | 事故管理 | 权责划分 · 保险赔付 · 还车联动 | 安全部、财务部、业务管理组 | + +--- + +### 2.5 物流业务条线(3 模块 · 经营闭环) + +**条线闭环**:物流合同生效 → 轻量调度派车(交车后可办结)→ 办结自动生成物流业务明细 → 汇总盈亏 → 汇集项目盈亏表 → 演进关联收款记录完成业财闭环。 + +| 步 | 模块名 | 一句话定位 | 责任部门 | +|---|---|---|---| +| 1 | 物流合同 | 关键信息录入 · 业务确认生效 · 可派车 | 业务管理组、业务服务组 | +| 2 | 调度任务 | 轻量派车 · 交还车校验 · 办结出车 | 业务服务组、调度岗、运维部 | +| 3 | 物流台账 | 任务自动生成 · 补录导入 · 盈亏核算 | 业务服务组、财务部 | + +--- + +### 2.6 三大数据底座(管理决策层,非业务条线) + +| 底座 | 看什么 | 主要数据来源条线 | +|---|---|---| +| 车辆资产运营情况 | 规模 · 结构 · 健康度 · 品牌型号出勤率下钻省市 | 运维、车辆管理、证照/保险、交车/备车 | +| 各业务条线盈亏情况 | 收入 · 成本 · 利润(条线/项目/单车) | 租赁台账、物流台账、氢费账户、收款、维保/事故/违章 | +| 里程调度情况 | 里程补贴 · 客户履约 · 智能替换与运维干预 | 车机/GPS/人工里程、客户里程约定、替换车、运维干预 | + +--- + +## 3. 流程闭环速查(端到端一句话) + +| 条线 | 闭环一句话 | +|---|---| +| 租赁 | 客户 → 模板 → 签约 → 提车应收 → 交车 → 台账账单 → 还车应结款归档 | +| 能源 | 供应商 → 加氢站 → 订单采集 → 氢费明细 → 客户账户收款 → 记录已付款 | +| 运维 | 采购/验车入库 → 牌证备车 → 交还车 → 维保故障 → 异动调拨 → 处置出库 | +| 安全 | 司机档案 → 培训资料 → 违规评级 → 违章事故 → 还车应结款费用 | +| 物流 | 合同生效 → 调度派车办结 → 台账明细/盈亏 →(演进)收款业财闭环 | +| 系统级 | 五条线业务闭环 → 三大数据底座决策 → 打通财务 YS 业财一体 | + +**高频跨模块联动(写描述时勿漏)** + +- 租赁签约路径依赖**客户管理**评级(标准 / 非标 / 禁止) +- **提车应收**对齐后触发运维**交车**;交车成功生成**租赁业务台账** +- **还车管理**完成后生成**还车应结款**;安全**违章/事故**费用写入应结项 +- 运维**采购/三方租赁合同**自动生成**验车入库**任务 +- 物流**调度办结**须车辆已**交车**;办结自动写**物流台账** +- 能源签约站**加氢订单**与非签约站**氢费明细**统一进入**氢费账户**对账收款 + +--- + +## 4. 故事点(按模块:起点 → 怎么运作 → 闭环) + +> 结构对齐页面「起点 / 怎么运作 / 闭环」。标题用「模块名 + 核心动作」;描述保留谁发起、关键规则、闭环结果,不升格成完整 PRD。 +> 下列为各模块业务事实摘要,用于**识别归属与用词**;材料未提及的细节**不得**脑补进描述。 + +### 4.1 租赁 + +#### 客户管理 +- **起点**:业务管理组维护客户基础信息,作为后续合同与账单的统一客户主数据 +- **怎么运作**:法务每 3 个月评估法律风险;财务实时维护财务风险等级;安全实时维护安全风险等级;系统综合形成签约策略 +- **闭环**:未达禁止标准可签;偏离常规范式走「非标准流程」;达禁止标准禁止新签,现有合同标「高风险」并通知业务跟进或终止 + +#### 标准合同管理 +- **起点**:法务部维护各车型试用/正式等标准模板,明确条款边界与风控要求 +- **怎么运作**:数智部按模板配置电子合同;条款可设「触发车型」;「风控红线」被改进非标审批;「锁定区域」不可随意改 +- **闭环**:业务员直选模板走标准流程;触碰红线或锁定区外修改转法务非标审批 + +#### 租赁合同 +- **起点**:业务员选羚牛签约主体与乙方客户;系统按评级判标准/非标/禁止 +- **怎么运作**:合同要素实时反写电子合同预览;生成电子合同/条款附件/授权委托书;越界转非标;支持续签、转正式、转三方、增值服务、授权委托书、主动终止等 +- **闭环**:审批通过进入履约并串联提车应收、交车、台账;非标须法务与董事长确认后放行 + +#### 提车应收款 +- **起点**:提交租赁合同审批时,按合同条款自动生成提车应收金额 +- **怎么运作**:法务在「收款记录」导入到账(后期对接 YS/银企直联);业务/业管关联账单并分配金额;实收对齐后标记完成 +- **闭环**:应收完成后按约定生成交车任务,属地运维执行交车 + +#### 租赁业务台账 +- **起点**:运维交车成功后自动生成该车租赁账单,应付款日初始「待设置」 +- **怎么运作**:设置应付款日后生成首期应收;提车实收写入首期;溢出并入第二期减免 +- **闭环**:持续记录每期应收/实收/状态,支撑租期运营与还车结算 + +#### 还车应结款 +- **起点**:运维完成还车后自动生成还车应结款任务 +- **怎么运作**:业管、安全、运维、能源各自完成部门审批;汇总应退/应补;应补关联收款记录、应退关联付款记录 +- **闭环**:业管发起总审批;完成后自动归档,租赁单条业务全链路闭环 + +--- + +### 4.2 能源 + +#### 供应商管理 +- **起点**:加氢站/充电站作为供应商纳入统一体系,采购组维护基础信息与结算要素 +- **怎么运作**:创建站点时绑定供应商;氢费/电费对账支持直连付款;收付款明细关联财务记录;支撑付款/收款申请审批 +- **闭环**:供应商主数据与站点、对账、财务贯通,打好能源业财基础 + +#### 加氢站管理 +- **起点**:采购部统一管理签约站/非签约站;签约站须关联供应商与系统账号 +- **怎么运作**:签约站账号登录加氢订单(PC/移动/H5);预付款余额;营业状态;成本价关联氢费记录;能源部形成对账单并发起付款(后期对接 YS) +- **闭环**:站点/账号/余额/价格/营业状态集中管理,签约站可自助上报 + +#### 加氢订单 +- **起点**:签约站登录后多渠道上报订单;PLC 站点可自动获取 +- **怎么运作**:多端快速识别记录;PLC 自动获取;联动「小羚羚」预约接单,司机确认;进入加氢记录供对账 +- **闭环**:主动上报或自动采集,预约在线闭环,减少手工补录 + +#### 车辆氢费明细 +- **起点**:非签约站氢费由业务管理组-能源部手动维护 +- **怎么运作**:录入并关联站点/车辆/客户;对账后标「已对账」归档;联动预付款余额与客户能源账户;OCR 与电子围栏辅助核对 +- **闭环**:签约/非签约氢费统一归集,余额变动可追溯 + +#### 氢费账户 +- **起点**:管理客户氢费预付款余额与客户对账单 +- **怎么运作**:维护余额变动;生成对账单;关联「收款记录」;收款后加氢记录标「已付款」;支持充值/开票直达财务(替代钉钉,对接 YS) +- **闭环**:明细→对账→收款全链路可核对,付款状态与财务实收一致 + +#### 扩展规划 +- **起点**:条线成熟后向上游采购与下游司机服务延伸 +- **怎么运作**:上游氢气采购;下游外部氢能车司机服务;与现有供应商/站点/订单/账户衔接 +- **闭环**:覆盖「采购—站点—用氢—结算—司机服务」更大范围生态 + +--- + +### 4.3 运维 + +#### 供应商管理 +- **起点**:整车厂、三方租赁企业纳入供应商体系 +- **怎么运作**:维护档案;为采购/三方租赁合同提供主数据;支撑付款底座 +- **闭环**:与采购合同、付款记录贯通 + +#### 车辆采购 / 三方租赁合同 +- **起点**:向整车厂/三方租赁厂商发起采购或三方租赁合同 +- **怎么运作**:创建审批;关联付款记录;按条款自动生成验车任务 +- **闭环**:合同、付款、验车联动,入库合规入口 + +#### 验车入库 +- **起点**:对采购或三方来源氢能车执行验车 +- **怎么运作**:接收验车任务;记录过程与问题;验完自动入库 +- **闭环**:验车结果与合同、库存绑定,来源可审计 + +#### 车辆上牌 +- **起点**:对仅有车架号的新车执行上牌 +- **怎么运作**:筛选待上牌车;录入/同步牌照;更新库存状态 +- **闭环**:牌证完整,为证照与交车提供前置条件 + +#### 证照管理 +- **起点**:行驶证、道路运输证、登记证、特种设备证/标识、加氢卡、安全阀、压力表等一车一档 +- **怎么运作**:维护档案与有效期;临近到期生成年审/等评/换证待办;推送运维/安全 +- **闭环**:证照全生命周期管理,降低漏办与违规风险 + +#### 备车管理 +- **起点**:日常提前整备,确保交车任务可随时交付 +- **怎么运作**:按计划/库存策略发起检查;记录整备项与责任人 +- **闭环**:交车前置完成,提升交付效率与质量一致性 + +#### 交车管理 +- **起点**:承接租赁/物流交车任务,按区域派属地运维;司机先完成在线培训并留痕 +- **怎么运作**:司机上传身份证/驾驶证/从业资格证、签名与现场拍照;交付证照及保险有效车辆;记录里程/氢量/电量/位置;OCR 胎纹;还车时比对算磨损等费用;E 签宝电子签归档 +- **闭环**:培训与交车数据完整可查,为还车费用与争议提供依据 + +#### 替换车管理 +- **起点**:因客户或车辆原因发起临时/永久替换 +- **怎么运作**:选类型与新旧车;完成替换交还;客户原因自动算费用差 +- **闭环**:过程可追踪、费用可核算,履约不中断 + +#### 还车管理 +- **起点**:处理业务/业管还车任务,按区域派属地运维 +- **怎么运作**:记录还车时间/里程/氢电量/位置;OCR 胎纹对比算磨损费;按里程与氢电差算费用;E 签宝确认归档 +- **闭环**:费用依据交还对比自动出具 + +#### 故障管理 +- **起点**:微信 AI 故障助手或人工记录故障关键信息 +- **怎么运作**:记时间地点车型紧急程度;留存图文视频聊天证据;运维处理至关闭;形成待办统计;沉淀品牌故障类型统计支撑采购论证 +- **闭环**:全过程留痕,证据链完整 + +#### 年审记录 +- **起点**:按行驶证审验有效期提前 3 个月生成年审任务 +- **怎么运作**:按车按月归集;完成后 OCR 新行驶证刷新证照;按月量化完成率 +- **闭环**:任务不遗漏,证照与办理结果同步 + +#### 维修与保养记录 +- **起点**:运维手动维护负责车辆维保记录(首期) +- **怎么运作**:配件费/人工费汇总;按承担方(客户/我司)生成客户账单或计入运维成本;计入车辆盈亏 +- **闭环**:维保成本可追溯可分摊,支撑单车盈亏与对客户计费 + +#### 车辆异动 +- **起点**:因当地维修/年审/保养出库异动,须提前审批 +- **怎么运作**:记异动前后氢电量里程;异动期间加氢记录归集为异动成本 +- **闭环**:可审批可计量,避免成本漏记 + +#### 车辆调拨 +- **起点**:车辆从 A 地调至 B 地运营,须提前审批 +- **怎么运作**:记发起方/费用/运输;接收方确认;运维操作权由 A 转 B +- **闭环**:跨区调拨透明,责任属地随车切换 + +#### 车辆出售、过户、报废 +- **起点**:达出售/过户/报废条件发起资产变更,录入交易方价格过户残值等 +- **怎么运作**:提交申请;董事长确认;运维记录全过程归档;完成后退出运营并出库 +- **闭环**:处置审批留痕,生命周期闭环至出库归档 + +--- + +### 4.4 安全 + +#### 司机管理 +- **起点**:管理物流内部司机,支撑快速选取与档案维护 +- **怎么运作**:维护基础信息与在职状态;管理驾驶证/从业资格证;与交车、台账联动 +- **闭环**:主数据统一、证照可查,支撑调度与合规 + +#### 内部司机培训 +- **起点**:定期开展安全培训并形成可追溯记录 +- **怎么运作**:制定计划组织参训;记录时间内容参训人;形成证据链供交管查询 +- **闭环**:培训电子归档,随时可调阅 + +#### 安全培训资料库 +- **起点**:安全部上传培训与安全运营资料供司机学习 +- **怎么运作**:维护课件制度指引;分类发布版本检索;司机经 APP/小程序学习 +- **闭环**:资料集中更新,移动端可学可查 + +#### 违规记录 +- **起点**:记录内部司机运营违规 +- **怎么运作**:登记事件类型时间结果;关联司机档案;联动客户安全评级 +- **闭环**:违规可追踪汇总,支撑选用与评级 + +#### 违章管理 +- **起点**:记录交通违章并与还车应结款联动 +- **怎么运作**:登记时间地点分值罚款及是否缴纳;未处理纳入还车结算;逾期未缴自动生成代处理费用 +- **闭环**:违章可查,代处理费用与还车/财务口径一致 + +#### 事故管理 +- **起点**:记录事故信息,支撑定责理赔与还车结算 +- **怎么运作**:登记权责划分;记保险介入/赔付/上浮;联动还车应结款写入安全费用 +- **闭环**:登记→赔付→上浮→应结款贯通,费用不遗漏 + +--- + +### 4.5 物流 + +#### 物流合同 +- **起点**:创建物流运力服务合同,录客户/车型/车辆数/交车时间地点,上传附件 +- **怎么运作**:录入项目与合同要素;业务确认生效后可创建轻量调度并提示运维交车 +- **闭环**:合同成为调度派车与交车的上游凭证 + +#### 调度任务 +- **起点**:在生效合同下创建出车任务,派车派司机;非 TMS +- **怎么运作**:选合同、计划出车日、线路、是否多趟与计价口径;未交车须先交车;办结确认实际出车后自动生成物流业务明细一行 +- **闭环**:出车事实追溯到合同与车牌,驱动台账落账 + +#### 物流台账 +- **起点**:承接调度办结自动生成的出车明细,导入/行编辑补录异常与历史 +- **怎么运作**:办结写入营收侧并算金额/总成本/盈亏;氢费/ETC 等成本可手工补录;汇集项目盈亏表;演进关联「收款记录」 +- **闭环**:收入与司机/车辆成本同台账可核对,盈亏可对接财务实收 + +--- + +## 5. AutoRDO 应用约定 + +1. **标题**:优先 `模块标准名:核心动作/结果`(10–30 字),模块名以本词典为准(例:`验车入库:采购与三方租赁合同自动生成验车记录及入库`) +2. **条线归属**:一句需求只挂一个主模块;跨条线联动写在描述里,不硬塞进标题 +3. **部门用词**:口语「业管/能源组/运维」等映射到第 1 节标准名 +4. **闭环口径**:若材料只讲做到一半,闭环写实际说到的结果,缺的标「待确认」 +5. **不做**:不把本词典全文贴进云效描述或输出稿;不脑补材料未出现的故事点细节 +6. **业财关键词**:收款记录、付款记录、对账单、应收/实收、YS、银企直联、E 签宝、小羚羚、PLC——保留原文业务对象名,勿随意改写 + +--- + +## 6. 条线演进(辅助理解,非强制写入描述) + +| 条线 | 现阶段 | 演进方向 | +|---|---|---| +| 租赁 | 合同电子化 · 业务闭环化 | 移动端商城直连发起合同,后续自动打通 | +| 能源 | 手工维护氢费明细 | 签约站主动上报,能源部转为核对确认 | +| 运维 | 车辆使用环节闭环 | 入库→运作→出库全链路 | +| 安全 | 司机安全全链条 | 与客户安全评级挂钩、事前预判 | +| 物流 | 合同 + 调度办结落账 | 成本自动回填、司机在线、关联收款;不做完整 TMS | +| 系统 | 业务驱动 · 财务手工 | 打通 YS · 业财一体 | diff --git a/.cursor/skills/AutoRDO/references/rules.md b/.cursor/skills/AutoRDO/references/rules.md new file mode 100644 index 0000000..3d6cce1 --- /dev/null +++ b/.cursor/skills/AutoRDO/references/rules.md @@ -0,0 +1,67 @@ +# AutoRDO 清洗与拆解细则 + +## 目标 + +碎片 → 可入库的书面「标题」与「描述」。保留原意;不写成产品 PRD。 +多行/多条独立输入 → **多份**转译结果(一份需求一份稿)。 + +## 必做与精炼规则 + +| 规则 | 说明 | +|---|---| +| **多条拆解** | 输入为多条独立诉求时,**先拆后洗**:
• **拆分信号**(满足任一即可):换行且每行一句独立诉求;`1. 2. 3.` / `-` / `*` 列表;表格多行;空行分段;「另外」「还有」「第二条」等显式分条
• **每条**各自输出一份标题+描述+待确认,**禁止**合并成一条大需求
• **不拆**:同一条需求内部的续写、补充说明、功能规则子点(属于描述细节,挂在该条下)
• **边界不清**:无法判断是一条还是多条时,在文首标「待确认:拆条边界」,并按最可能的分条给出结果,请用户校对 | +| **OneOS 领域对齐** | 涉及 ONE-OS / 羚牛业务时,**先读** [oneos-domain.md](oneos-domain.md):
• 部门口语映射到标准名(业管→业务管理组,能源组→业务管理组-能源部 等)
• 标题模块名优先用词典标准名(如「验车入库」而非口语「验车管理」——若材料确指该模块)
• 跨条线联动写在描述,标题只挂主责模块
• **禁止**把词典全文或未在材料中出现的故事点细节写入输出 | +| **标题提炼** | 根据材料内容理解,自动概括简炼、清晰的标准需求标题:
• **内容理解**:准确识别归属的业务模块与核心动作/诉求(如:`验车入库:采购与三方租赁合同自动生成验车记录及入库`)。
• **自然流畅**:表达自然书面化,无需死板强求拼接固定句式;彻底剔除“我们要做一个”、“主要是用于”、“完成之后才能”等口语废话。
• **字数适中**:控制在 10-30 字内,能让读者一眼看懂需求核心。 | +| **元数据识别** | 详见 [meta-fields.md](meta-fields.md)。从台账列或正文自动识别并输出:
• **类型**:`【新增】`/`【优化】`
• **优先级**:`P1-高`/`P2-中`/`P3-低`
• **标签**:标准模块名(+ 可选 PC端/小程序)
• **提交部门**:映射词典标准名(业务管理部→业务管理组,数智中心→数智部 等)
• **提交人**:反馈方/用户列或署名
• 显式列优先;推断不确定则待确认;**不**直接写云效打标 | +| **描述转译** | 原文的转译结果,将口述/碎片材料转译为规范书面表达,保留原意(不改变谁要求什么、约束与例外;不替换成「更优方案」);业财对象名(收款记录、付款记录、YS、E 签宝、小羚羚、PLC 等)按词典保留 | +| 书面化 | 口语改成完整句子或清晰短条目;主谓齐全 | +| 去口头禅 | 删除:嗯、啊、那个、就是说、然后呢、对对对、你懂吧 等 | +| 自我修正 | 以最后一次明确口径为准;若前后矛盾且未决,列入「待确认」 | +| 去重复 | 同一意思多轮确认只保留一处;多条拆解时,各条之间不要互相抄写无关内容 | +| 轻度格式 | 可用短标题/条目;禁止展开成总览/角色/流程图/故事点等 PRD 结构 | +| 去结尾句号 | 描述中每个句子或条目末尾去掉 `。`;保留 `?` `!` 若确为疑问/感叹 | +| 待确认 | 材料含糊处写 `待确认:…`,不臆造;**只要存在待确认,同轮强制**进入 [confirm-pending.md](confirm-pending.md) 的 Plan 逐条确认(无需用户再说「确认待确认」) | +| **待确认 Plan 确认** | 详见 [confirm-pending.md](confirm-pending.md):
• 清洗结果含待确认 → **立刻** `SwitchMode` → plan
• 按需求条号**逐点**确认;**优先选择题**(2–5 项 +「其他/我补充」),选项用 `oneos-domain.md` 辅助
• 也支持用户粘贴**需求描述/补充说明**一次消解多点
• 确认后回填描述并移除已消解项;未确认不得写入定稿 | + +## 多条拆解示例 + +**输入(多行):** +```text +还车应结款生成后自动展示违章事故信息,不必等安全员提交 +验车完成后才能车辆入库 +氢费账户对账单要能关联收款记录 +``` + +**输出**:3 份独立的「原始诉求(AutoRDO)」,每份各自标题+描述;不要揉成一条。 + +## 标题提炼对比示例 + +- ❌ **坏标题(直抄口语/带废话)**:`我们现在要做一个验车管理功能主要是采购合同和三方租赁合同自动形成验车记录验车完成之后才能车辆入库` +- ✅ **好标题(自然清晰,覆盖标准模块)**:`验车入库:采购与三方租赁合同自动生成验车记录及入库`(模块名对齐 oneos-domain) + +## 不做 + +- 不生成 AutoPRD 十章或「产品说明」 +- 不写验收清单、故事点、mermaid +- 不改云效、不建【交付】/分析/设计 +- 不加载 `yunxiao-requirement-lifecycle` +- 不把多条独立诉求合并成一条「大杂烩」需求 + +## 录音 + +1. 已有转写 → 按上文清洗转写稿并拆解标题与描述;转写中若含多条独立诉求,同样先拆后洗 +2. 仅音频 → 回报「请提供转写文本后再 AutoRDO」,停止声称已清洗完成 + +## 质量自检 + +- [ ] 多行/多条输入时,已按条拆成多份结果(条数与输入独立诉求数一致或已说明待确认边界) +- [ ] 已对照 `oneos-domain.md`(ONE-OS 相关材料) +- [ ] **标题清晰通顺**(标准模块名 + 核心动作,无死板句式绑定,无口语废话) +- [ ] 部门/业财关键词已映射或保留为词典标准口径 +- [ ] **描述为原文的转译结果**,干系人能认出这是自己说的意思;未脑补词典故事点 +- [ ] 无口头禅堆砌、无未决矛盾被写成定论 +- [ ] 描述末尾无 `。` +- [ ] 未冒充完整 PRD;未把词典全文写入输出 +- [ ] 已输出类型/优先级/标签/提交部门/提交人(有依据或已标待确认;未臆造提交人) +- [ ] 若存在待确认:已同轮强制进 Plan(未等「确认待确认」口令);优先选择题、选项有词典依据;文字补充已映射到对应点;定稿无未确认臆造 +- [ ] 若无待确认:未多余进 Plan diff --git a/.cursor/skills/YunxiaoPM/SKILL.md b/.cursor/skills/YunxiaoPM/SKILL.md new file mode 100644 index 0000000..b6a827e --- /dev/null +++ b/.cursor/skills/YunxiaoPM/SKILL.md @@ -0,0 +1,113 @@ +--- +name: YunxiaoPM +description: >- + 产品经理云效(Projex)自动化:记录需求(压缩点选 1a2b3a4d:类型/项目/优先级/标签)、 + 实时点选云效项目、推进 待处理→已确认→分析中→设计中→设计完成→待开发, + 交付树【交付】ASSOCIATED /【分析】【设计】TASK_SUB,无单快轨与编号直推交棒何斐, + 创建迭代并挂【交付】(不挂需求)。用户说 YunxiaoPM、YunxiaoPMapp、需求任务、记录需求、受理确认、开始分析、 + 开始设计、设计完成、交棒开发、快轨待开发、编号直推、创建迭代 时使用。 + 不建【开发】/【测试】。凡写云效先 Plan 确认再一口气 apply;禁止对齐 + yunxiao-requirement-lifecycle。 +--- + +# 需求任务(YunxiaoPM) + +产品部云效自动化。斜杠调起 **`/YunxiaoPM`**;对外中文名 **需求任务**(原名 YunxiaoPMapp)。本 Skill **自洽成篇**;**禁止** fork / include / 「对齐」`yunxiao-requirement-lifecycle`。 + +产品经理会话**不要**同时挂载旧 lifecycle Skill,避免双建任务。 + +## Plan 模式门禁(强制 · 凡写云效) + +凡会改云效的操作(建单、改状态、建/改任务、打标、传附件、改负责人、建迭代等),Agent **第一步**必须: + +1. `SwitchMode` → **plan**(说明:先对齐参数与执行清单,确认后再一口气 apply)。 +2. **新建**依赖项目空间时:先按 [references/project-selection.md](references/project-selection.md) **实时拉项目列表并点选(门禁 PJ)**;禁止静默使用 `runtime-ids.json` 默认 `spaceIdentifier`。口令/选项**无法对应**时:**自动重拉项目列表一次**再匹配;仍失败则停请用户重选(禁止第 3 次空转拉取)。 +3. Plan 写清:**已选项目名 + spaceId**、目标需求/任务**编号**、将改状态、将建/复用的交付·分析·设计编号策略、迭代版本类型(若适用)、§0.1① 占位风险勾选(若适用)、**不会做的事**(不建【开发】/【测试】、不按标题查重)。**记录需求**须按 [compact-select.md](references/compact-select.md) 给出 1–4 题字母表,接受压缩答复如 `1a2b3a4d`。 +4. **用户确认 / 批准 Plan / 「执行」之前**:禁止 apply;项目未点选同禁。压缩串须先解析回显,再等「执行」。 +5. 确认后切回 Agent,**同一轮按清单一口气执行到底**,再一次性校验回报;中途缺参才停下。 + +**例外(可读可不进 Plan):** 仅查状态 / 为什么没流转 / 给我方案。 + +**禁止:** 以「参数已齐」「速度路径」「用户很熟」跳过 Plan;「批准计划」若清单未点齐关键参数(含 **PJ 项目**),仍视为未完成门禁。 + +## 真相源模型 + +```text +需求状态 = 阶段看板唯一真相 +【交付】 = 每需求最多 1 条容器(ASSOCIATED→需求) +【分析】/【设计】 = TASK_SUB→交付(交付「子项」必可见;与 ASSOCIATED 同 create 互斥) +【开发】/【测试】 = 不进本 Skill +查重/复用唯一渠道 = 任务编号(ONEOS-xx);禁止按标题 +``` + +编号权威:需求描述 `## 工作项编号(系统)`(见 [references/workitem-ids.md](references/workitem-ids.md))。 +操作顺序:口令显式编号 > 读该区块 > ASSOCIATED/SUB 校验;冲突则停。 + +## 外置调用(禁止本 Skill 内嵌对方全文) + +| 时机 | 调用 | +|---|---| +| 入库清洗聊天/录音 | **`$AutoRDO`**(独立 Skill;路径 `AutoRDO/SKILL.md`;不内嵌清洗细则) | +| 设计完成 PRD + 对象存储链接 + 回填【交付】 | **`$oneos-autoprd`(AutoPRD)**;创建【交付】仍占位,设计完成才灌 MD | +| 人员 / 状态 / 字段 ID;项目 catalog 仅缓存 | [assets/runtime-ids.json](assets/runtime-ids.json) | +| **PJ 云效项目点选**(新建必选;实时列表) | [references/project-selection.md](references/project-selection.md) · [scripts/list_projects.py](scripts/list_projects.py) | +| **压缩点选 `1a2b3a4d`**(类型/项目/优先级/标签) | [references/compact-select.md](references/compact-select.md) · [scripts/list_tags.py](scripts/list_tags.py) | +| 阶段日历工时 | [references/work-hours.md](references/work-hours.md) + [assets/cn-workday-calendar.json](assets/cn-workday-calendar.json) + [scripts/workday_hours.py](scripts/workday_hours.py) | + +## 路由(按需完整阅读) + +| 场景 | 模块 | +|---|---| +| 交付树、关联约定、禁止项 | [references/model.md](references/model.md) | +| 描述双段 · AutoRDO / 占位 / AutoPRD | [references/description-split.md](references/description-split.md) | +| 步骤 0–5 标准路径 | [references/stage-flow.md](references/stage-flow.md) | +| 无单快轨到待开发 | [references/fast-track.md](references/fast-track.md) | +| 编号直推交棒 | [references/number-push.md](references/number-push.md) | +| 交棒门禁 · 回退最小集 | [references/handoff-and-rollback.md](references/handoff-and-rollback.md) | +| 计划开始/完成 · 阶段日历工时 | [references/work-hours.md](references/work-hours.md) | +| Make 导出 ZIP + 复制截图 | [references/make-export-attach.md](references/make-export-attach.md) | +| 创建迭代 · V主.副.子 · 只挂交付 | [references/sprint.md](references/sprint.md) | +| 口令面 | [references/commands.md](references/commands.md) | +| 记录需求元字段(优先级/标签/提交部门/提交人) | [references/record-meta-fields.md](references/record-meta-fields.md) | +| 云效项目点选 PJ | [references/project-selection.md](references/project-selection.md) | +| 验收清单 · 回报模板 | [references/acceptance.md](references/acceptance.md) | +| 交接契约(开发 Skill 入口) | [references/handoff-contract.md](references/handoff-contract.md) | +| 已验证实写 API · 极速建单 | [references/live-api.md](references/live-api.md) · [scripts/live_create_fast.py](scripts/live_create_fast.py) | +| 2026-07-23 复盘与耗时对比 | [references/live-perf-2026-07-23.md](references/live-perf-2026-07-23.md) | + +## 口令速查 + +```text +记录需求:…;项目=(Plan 点选,勿默认);优先级=紧急|高|中|低;标签=…;提交部门=…;提交人=…;推进至=暂不推进|已确认|分析中|设计中|设计完成|待开发|待开发(快轨) +受理确认:ONEOS-xx +开始分析:ONEOS-xx +开始设计:ONEOS-xx;交付任务=…;分析任务=… +设计完成:ONEOS-xx;设计任务=…;原型=… +交棒开发:ONEOS-xx;交付任务=… +快轨待开发:ONEOS-xx +编号直推:分析任务=ONEOS-b / 设计任务=ONEOS-c / 交付任务=ONEOS-a +创建迭代:版本类型=主|副|子;交付任务=ONEOS-a,ONEOS-b,…;名称前缀=… +``` + +**PJ 项目**:新建前必须从云效实时列表点选(见 [project-selection.md](references/project-selection.md));口令带项目名仅作预填建议。 +**压缩点选**:记录需求 Plan 展示 `1.类型 2.项目 3.优先级 4.标签` 字母表;你可回 `1a2b3a4d`(见 [compact-select.md](references/compact-select.md))。标签未命中会自动重拉一次标签候选并重生选项。 +**记录需求**元字段(优先级/标签/提交部门/提交人):与压缩点选并用;缺项用字母表补齐。见 [references/record-meta-fields.md](references/record-meta-fields.md)。 + +后续口令**优先带任务编号**;未带则读「工作项编号(系统)」;仍无则询问;**禁止按标题补全**。 + +## 本 Skill 终点 + +交棒完成(需求=待开发;【交付】负责人=何斐)后结束。可一句:「请技术经理使用开发 Skill」。 +例外:交棒后「创建迭代并关联交付」仍属 YunxiaoPM。 + +**明确不做:** 创建【开发】/【测试】、挂仓库、开分支、提测、写用例。 + +## §0.1 五条补齐(摘要) + +1. **交棒占位**:标准路径下交付仍为 `等待设计任务完成后自动填入` 时**允许**交棒,但 Plan 必须勾选风险,回报首行标红。**快轨**有手工/原型时禁止占位(见 [fast-track.md](references/fast-track.md))。 +2. **预计工时**:标准路径 = **阶段日历工时**(工作日×8);**快轨待开发**需求默认预计/实际各 **2**。 +3. **编号真相源**在需求「工作项编号(系统)」;新建后立即 PATCH 该区块。 +4. **无单快轨**:【设计】描述同步需求;计划起止=当日;TASK_SUB→交付后补 ASSOCIATED→需求;【交付】描述手工同步或 AutoPRD;交付/设计与需求同标签;设计当日完成态。 +5. **描述双段**不互相覆盖;迭代**只挂【交付】**(需求不挂迭代);回退重做设计则**新开设计编号**,交付计划开始不改。 + +细则见各 references。 diff --git a/.cursor/skills/YunxiaoPM/acceptance.md b/.cursor/skills/YunxiaoPM/acceptance.md new file mode 100644 index 0000000..26cc0b7 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/acceptance.md @@ -0,0 +1,49 @@ +# 验收清单与回报 + +## apply 后必须自检 + +| # | 项 | 通过标准 | +|---|---|---| +| 1 | 需求编号 | 回报含 ONEOS-xx | +| 2 | 任务编号 | 交付/分析/设计凡新建或操作均回报编号;禁止只报标题 | +| 3 | 编号区块 | 需求「工作项编号(系统)」与实际一致 | +| 4 | ASSOCIATED | **仅交付**详情关联项可见需求(`createWorkitemRelationInfo=ASSOCIATED`;禁止 PARENT 冒充) | +| 5 | SUB | **必过**:分析/设计 create 用 `TASK_SUB→交付`(含 `parent`+`parentIdentifier`);交付「子项」tab 须可见。与 ASSOCIATED 同 create 互斥,阶段任务关联项可空(见 live-api.md) | +| 6 | 计划开始 | 未误覆盖已有值 | +| 7 | 阶段日历工时 | 有计划完成时已写入;脚注存在;回报用语正确 | +| 8 | 交棒 | 需求=待开发;交付负责人=何斐(交棒场景) | +| 9 | 占位风险 | 交付仍占位时首行标红 | +| 10 | 负向 | 本轮**无**【开发】/【测试】任务;未按标题查重 | + +设计完成额外:AutoPRD 段已写;交付非占位(除非失败停下);ZIP+截图已挂或已列出缺项。 + +迭代额外:只挂【交付】(需求不挂);版本号符合递增规则。 + +## 回报模板(精简) + +```text +【YunxiaoPMapp】 +风险:(若有占位交棒则首行标红) +需求:ONEOS-xx | 状态=… +交付:ONEOS-a | 负责人=… +分析:ONEOS-b | …(无则写无) +设计:ONEOS-c | …(无则写无) +阶段日历工时:分析 Hh / 设计 Hh(若本轮写入) +附件:…(若本轮) +迭代:…(若本轮) +下一步:请技术经理使用开发 Skill(交棒后) +``` + +## 适合全自动 vs 人工门禁 + +| 适合全自动 | 建议半自动/人工门禁 | +|---|---| +| 建单、打标签、挂迭代、写描述、建交付树、改状态、交棒负责人、幂等查重 | 受理(已确认)、是否快轨、设计完成是否真可开发、跨需求优先级与迭代容量、回退与取消 | + +## 实写性能(2026-07-23 复盘) + +| 项 | 要求 | +|---|---| +| 改状态 | 只用 `status/transit`(见 [live-api.md](live-api.md)) | +| 改【交付】负责人 | 只用 `PATCH …/{id}` + `propertyKey=assignedTo` | +| 极速复测 | `scripts/live_create_fast.py`;默认不开浏览器 | diff --git a/.cursor/skills/YunxiaoPM/agents/openai.yaml b/.cursor/skills/YunxiaoPM/agents/openai.yaml new file mode 100644 index 0000000..f0fdad9 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "需求任务" + short_description: "云效:压缩点选记录需求→分析/设计→交棒待开发→挂迭代" + default_prompt: "按需求任务(/YunxiaoPM)处理:先 Plan 出压缩点选字母表并确认项目,用户回 1a2b3a4d 与「执行」后再 apply。" diff --git a/.cursor/skills/YunxiaoPM/assets/cn-workday-calendar.json b/.cursor/skills/YunxiaoPM/assets/cn-workday-calendar.json new file mode 100644 index 0000000..65039c3 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/assets/cn-workday-calendar.json @@ -0,0 +1,95 @@ +{ + "schema_version": 1, + "timezone": "Asia/Shanghai", + "note": "法定放假日 holidays;国务院公布的周末调休补班日 workdays_on_weekend。缺年则禁止静默按仅去周末计算。", + "source": { + "2025": "国办发明电〔2024〕12号 https://www.gov.cn/zhengce/zhengceku/202411/content_6986383.htm", + "2026": "国办发明电〔2025〕7号 https://www.gov.cn/zhengce/content/202511/content_7047090.htm" + }, + "years": { + "2025": { + "holidays": [ + "2025-01-01", + "2025-01-28", + "2025-01-29", + "2025-01-30", + "2025-01-31", + "2025-02-01", + "2025-02-02", + "2025-02-03", + "2025-02-04", + "2025-04-04", + "2025-04-05", + "2025-04-06", + "2025-05-01", + "2025-05-02", + "2025-05-03", + "2025-05-04", + "2025-05-05", + "2025-05-31", + "2025-06-01", + "2025-06-02", + "2025-10-01", + "2025-10-02", + "2025-10-03", + "2025-10-04", + "2025-10-05", + "2025-10-06", + "2025-10-07", + "2025-10-08" + ], + "workdays_on_weekend": [ + "2025-01-26", + "2025-02-08", + "2025-04-27", + "2025-09-28", + "2025-10-11" + ] + }, + "2026": { + "holidays": [ + "2026-01-01", + "2026-01-02", + "2026-01-03", + "2026-02-15", + "2026-02-16", + "2026-02-17", + "2026-02-18", + "2026-02-19", + "2026-02-20", + "2026-02-21", + "2026-02-22", + "2026-02-23", + "2026-04-04", + "2026-04-05", + "2026-04-06", + "2026-05-01", + "2026-05-02", + "2026-05-03", + "2026-05-04", + "2026-05-05", + "2026-06-19", + "2026-06-20", + "2026-06-21", + "2026-09-25", + "2026-09-26", + "2026-09-27", + "2026-10-01", + "2026-10-02", + "2026-10-03", + "2026-10-04", + "2026-10-05", + "2026-10-06", + "2026-10-07" + ], + "workdays_on_weekend": [ + "2026-01-04", + "2026-02-14", + "2026-02-28", + "2026-05-09", + "2026-09-20", + "2026-10-10" + ] + } + } +} diff --git a/.cursor/skills/YunxiaoPM/assets/runtime-ids.json b/.cursor/skills/YunxiaoPM/assets/runtime-ids.json new file mode 100644 index 0000000..d4579ac --- /dev/null +++ b/.cursor/skills/YunxiaoPM/assets/runtime-ids.json @@ -0,0 +1,221 @@ +{ + "schema_version": 1, + "skill": "YunxiaoPM", + "verified_at": "2026-07-25", + "note": "YunxiaoPM constants. Project space MUST be user-selected (PJ gate); project.spaceIdentifier is last-known cache only — never auto-apply. See references/project-selection.md.", + "project": { + "selection_mode": "user_pick_required", + "name": null, + "name_aliases": [ + "01_ONEOS", + "ONEOS", + "统一运营管理平台", + "统一运营管理平台PC端", + "统一运营管理平台 PC 端" + ], + "spaceIdentifier": null, + "customCode": null, + "spaceType": "Project", + "organizationIdentifier": "697c54a19df7fdfa65466405", + "default_sprint_name_prefix": "统一运营管理平台PC端", + "list_api": "GET /projex/api/workspace/project/search/list", + "last_selected": { + "name": "01_ONEOS", + "spaceIdentifier": "1280be963a5a2cc126a4118dca", + "customCode": "ONEOS", + "note": "历史常用项;仅作预填建议,禁止未点选即使用" + }, + "refreshed_at": "2026-07-25" + }, + "projects_catalog": [ + { + "name": "05_羚牛碳资产平台", + "identifier": "ff104a3bce09463da136a97999", + "customCode": "CARBON" + }, + { + "name": "06_对外客户项目", + "identifier": "baca8b4d676c5af67e475caec0", + "customCode": "CUST" + }, + { + "name": "07_LNBOX", + "identifier": "35e1e915a052379bbdfb993aac", + "customCode": "LNBOX" + }, + { + "name": "04_AI应用", + "identifier": "d9002ea72c6c3a1a97b02be750", + "customCode": "AIAPP" + }, + { + "name": "03_数据中台", + "identifier": "a801cef5c9a68fa051c07432c7", + "customCode": "DATA" + }, + { + "name": "02_小羚羚APP", + "identifier": "db771a2cca07bed43b369af077", + "customCode": "XLLAPP" + }, + { + "name": "01_ONEOS", + "identifier": "1280be963a5a2cc126a4118dca", + "customCode": "ONEOS" + }, + { + "name": "敏捷研发示例项目", + "identifier": "65eca0c2e16a23939081e19e14", + "customCode": "DEMO" + } + ], + "people": { + "wangmian": { + "displayName": "王冕", + "identifier": "6811df000601d2fea60144a9", + "role": "产品创建人/默认负责人" + }, + "hefei": { + "displayName": "何斐", + "identifier": "695f0400562f09713f9c3a93", + "role": "待开发交棒【交付】负责人" + } + }, + "tags": { + "故障管理": "ceb526a7343995577645317e9a", + "还车应结款": "4204960ce658c94b17abb7c5f6", + "工作台": "b62d21beac55c0b43389915eab", + "交车管理": "b6947b5aca82a8c759612ba039", + "合同管理": "23873be81931cb0dc1bf87a456", + "安全培训": "76988b5f73ef515de5930d59b9", + "证照管理": "11f4c2ec65901a8f3199c81706", + "还车管理": "1e0fce2d6929ff4ac13b310e97" + }, + "status": { + "req": { + "待处理": "100005", + "已确认": "32", + "分析中": "154395", + "设计中": "156603", + "设计完成": "307012", + "待开发": "1582fc929d429111b925309493" + }, + "task": { + "待处理": "100005", + "已完成": "100014" + }, + "transit_api": "POST /projex/api/workitem/workitem/{id}/status/transit", + "fast_handoff_hops": [ + "设计完成", + "待开发" + ], + "note": "见 references/live-api.md;禁止再用 updateStatus" + }, + "workitem_types": { + "product_req": { + "name": "产品类需求", + "category": "Req", + "identifier": "9uy29901re573f561d69jn40" + }, + "task": { + "name": "任务", + "category": "Task", + "identifier": "ba102e46bc6a8483d9b7f25c" + } + }, + "priority": { + "紧急": "646004e97f54bb77fec7b455df", + "高": "95b89e0a524d9693e1f335ffe5", + "中": "fa155d1214f9f8db222d39db3b", + "低": "92924feff9c1085891e7511872" + }, + "fields": { + "plan_start": { + "fieldIdentifier": "79", + "name": "计划开始时间", + "value_shape": "YYYY-MM-DD HH:mm:ss China wall time preferred; epoch ms string also accepted", + "example": "2026-07-27 12:00:00", + "update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON" + }, + "plan_end": { + "fieldIdentifier": "80", + "name": "计划完成时间", + "value_shape": "YYYY-MM-DD HH:mm:ss or epoch ms string", + "update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON" + }, + "estimated_hours": { + "fieldIdentifier": "101586", + "name": "预计工时", + "note": "不可直接改字段;须工时预估登记", + "create_api": "POST /projex/api/workitem/workitem/time/estimate body spentTime,type,recordUserIdentifier,workitemIdentifier", + "delete_api": "DELETE /projex/api/workitem/workitem/time/estimate/{workitemId}/{estimateId}", + "list_api": "GET /projex/api/workitem/workitem/time/estimate/list?workitemIdentifier=" + }, + "actual_hours": { + "name": "实际工时", + "note": "快轨待开发默认 2", + "create_api": "POST /projex/api/workitem/workitem/time body actualTime,type,recordUserIdentifier,workitemIdentifier,gmtStart,gmtEnd (epoch ms string)", + "list_api": "GET /projex/api/workitem/workitem/time/list?workitemIdentifier=", + "fast_track_default": 2, + "verified_at": "2026-07-27", + "verified_on": "ONEOS-293" + }, + "fast_track": { + "req_estimated_hours": 2, + "req_actual_hours": 2, + "design_plan_start_end_same_day": true, + "design_description": "copy_req_document", + "delivery_description": "manual_sync_or_autoprd", + "delivery_design_same_tags_as_req": true, + "design_associated_to_req_after_task_sub": true, + "document_update_api": "PATCH /projex/api/workitem/workitem/{id}/document {content,formatType:RICHTEXT}" + }, + "submit_department": { + "fieldIdentifier": "3132597a9718d1c282b7ba5a0c", + "name": "提交部门", + "format": "string input", + "update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON", + "verified_at": "2026-07-27", + "verified_on": "ONEOS-293" + }, + "submitter": { + "fieldIdentifier": "9e01269e96f91fbb97d36bf5b3", + "name": "提交人", + "format": "string input", + "update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON", + "verified_at": "2026-07-27", + "verified_on": "ONEOS-293" + }, + "tag": { + "fieldIdentifier": "tag", + "name": "标签", + "propertyKey": "tag" + } + }, + "assignee_rules": { + "待开发_交付任务": "hefei", + "分析中_交付与分析": "creator", + "设计中_设计": "creator" + }, + "relation": { + "delivery_to_req": "ASSOCIATED", + "stage_to_delivery": "TASK_SUB", + "create_field": "createWorkitemRelationInfo", + "forbid": [ + "PARENT_as_关联项", + "ASSOCIATED_only_for_stage_tasks_without_TASK_SUB" + ], + "note": "交付必须 ASSOCIATED→需求。分析/设计默认 TASK_SUB→交付(子项 tab)。二者同 create 互斥。见 references/live-api.md" + }, + "delivery_placeholder": "等待设计任务完成后自动填入", + "task_title_prefixes": { + "delivery": "【交付】", + "analysis": "【分析】", + "design": "【设计】" + }, + "publish_url": { + "shape": "{baseUrl}/{prototype-id}/index.html", + "forbid_prototypes_prefix": true, + "require_index_html": true + } +} diff --git a/.cursor/skills/YunxiaoPM/docs-YunxiaoPMapp-实现原理-开发Skill对接.md b/.cursor/skills/YunxiaoPM/docs-YunxiaoPMapp-实现原理-开发Skill对接.md new file mode 100644 index 0000000..24a728d --- /dev/null +++ b/.cursor/skills/YunxiaoPM/docs-YunxiaoPMapp-实现原理-开发Skill对接.md @@ -0,0 +1,317 @@ +# YunxiaoPMapp 实现原理说明 + +> 供**开发部门**设计 / 制作「开发侧 Skill」(暂称 **YunxiaoDevapp**)时对齐契约。 +> 本文描述产品侧 Skill **YunxiaoPMapp** 的模型、边界、数据契约与交棒接口;**不是**对 `yunxiao-requirement-lifecycle` 的兼容说明。 +> 技能包:https://github.com/15810879921-coder/oneos-pm-skills · `skills/YunxiaoPMapp/` +> 文档版本:2026-07-24 + +--- + +## 1. 为什么拆成两个 Skill + +| | YunxiaoPMapp(产品) | 开发 Skill(待建) | +|---|---|---| +| 职责 | 需求从「待处理」到「待开发」交棒 | 从「待开发」到开发完成 / 提测前 | +| 任务类型 | 【交付】【分析】【设计】 | 【开发】(可多条)、可选挂仓库/分支 | +| 负责人交接 | 交棒时【交付】→ **何斐** | 拆【开发】子任务并指派研发 | +| 明确不做 | 建【开发】/【测试】、开分支、提测 | 不建【分析】/【设计】、不改 AutoRDO 段 | + +**硬规则:** 两个 Skill **禁止互相 include / fork 全文**;只认本文第 6 章「交棒契约」。产品会话与开发会话不要同时挂载旧 `yunxiao-requirement-lifecycle`,避免双建任务树。 + +--- + +## 2. 核心真相源(必须先统一) + +```text +需求状态 = 阶段看板唯一真相(分析中 / 设计中 / 待开发 …) +【交付】任务 = 每需求最多 1 条「交付容器」 +【分析】【设计】 = 【交付】下的阶段子项(产品侧建) +【开发】【测试】 = 开发 / 测试 Skill 建(产品侧永不创建) +查重 / 复用 = 只认云效任务编号 ONEOS-xx,禁止按标题 +``` + +### 2.1 关系模型(云效) + +```mermaid +flowchart TB + Req["产品类需求 ONEOS-R"] + DEL["【交付】ONEOS-a"] + AN["【分析】ONEOS-b"] + DE["【设计】ONEOS-c"] + DEV["【开发】… 开发 Skill"] + TEST["【测试】… 测试 Skill"] + + DEL -->|"ASSOCIATED 关联项"| REQ + AN -->|"TASK_SUB 子项"| DEL + DE -->|"TASK_SUB 子项"| DEL + DEV -.->|"建议:SUB→交付 或 ASSOCIATED→需求+交付"| DEL + TEST -.->|"测试 Skill"| REQ +``` + +| 关系 | 谁写 | 验收 | +|---|---|---| +| `ASSOCIATED` | **仅【交付】→ 需求** | 交付详情「关联项」可见需求 | +| `TASK_SUB` | 【分析】/【设计】→【交付】 | 交付详情「子项」可见阶段任务 | +| (开发侧建议) | 【开发】→【交付】或需求 | 由开发 Skill 自定,但须可编号查重 | + +**踩坑(已复现):** 对分析/设计只写 `parentIdentifier` + `ASSOCIATED→需求`,交付「子项」会为空。正确:create 时 `createWorkitemRelationInfo=TASK_SUB` + `parent`/`parentIdentifier`=交付。同一 create **只能**带一条关系,ASSOCIATED 与 TASK_SUB 互斥。 + +### 2.2 标题只是展示 + +- 形式:`【交付】`/`【分析】`/`【设计】` + 需求标题 +- **不参与查重**;改标题不影响幂等 +- 开发侧【开发】标题建议 `【开发】` + 范围简述,同样**只认编号** + +--- + +## 3. 编号权威源 + +需求描述固定区块(机器可解析): + +```markdown +## 工作项编号(系统) +- 交付:ONEOS-a +- 分析:ONEOS-b(无则写无) +- 设计:ONEOS-c(无则写无) +``` + +| 规则 | 说明 | +|---|---| +| 新建后立即 PATCH | 只改本区块,不动 AutoRDO / AutoPRD | +| 操作顺序 | 口令显式编号 > 读本区块 > ASSOCIATED/SUB 反查 | +| 冲突 | 停下人工;**禁止按标题猜** | +| 多交付编号 | 停止并列号请人合并 | + +**开发 Skill 入口建议:** 口令必须带 `需求=ONEOS-R` + `交付任务=ONEOS-a`;缺则读本区块;仍无则询问。 + +--- + +## 4. 需求描述双段(产品写 · 开发只读) + +```markdown +## 原始诉求(AutoRDO) +(聊天/录音清洗稿;设计完成也不删) + +## 产品说明(AutoPRD) +(设计完成才灌满;含对象存储预览链接) + +## 工作项编号(系统) +- 交付 / 分析 / 设计 +``` + +| 段 | 谁写 | 开发侧用法 | +|---|---|---| +| AutoRDO | `$AutoRDO` → 产品记需求时 | 占位交棒时**唯一可信**业务原文 | +| AutoPRD | `$oneos-autoprd` → 设计完成时 | 正式需求说明;缺则有权要求产品补设计完成 | +| 编号区块 | YunxiaoPMapp | 定位交付树 | + +**【交付】描述:** 设计完成前固定占位文案 `等待设计任务完成后自动填入`;设计完成后替换为 AutoPRD 正文。 +**允许占位交棒**,但产品回报必须标红风险;开发 Skill 应检测占位并提示「材料不齐,可凭 AutoRDO 开工或退回补设计」。 + +对象存储预览 URL 形态:`{baseUrl}/{prototype-id}/index.html`(禁止加 `prototypes/` 前缀、禁止去掉 `index.html`)。 + +--- + +## 5. 产品侧阶段状态机(0→5) + +```text +0 创建需求(待处理) ─AutoRDO→ 仅需求,不建任务 +1 受理确认(已确认) 仍不建任务 +2 分析中 建【交付】+【分析】 +3 设计中 建【设计】;收口【分析】计划完成 +4 设计完成 收口【设计】;AutoPRD+附件;灌【交付】描述 +5 待开发交棒 ★ 需求→待开发;【交付】负责人→何斐 + ★ = YunxiaoPMapp 终点 / 开发 Skill 起点 +``` + +### 5.1 各步要点(开发需知道的副作用) + +| 步骤 | 需求状态 | 任务侧关键动作 | +|---|---|---| +| 2 分析中 | 分析中 | 新建【交付】ASSOCIATED→需求;描述=占位;计划开始仅空写当日且**此后不可改**;新建【分析】TASK_SUB→交付 | +| 3 设计中 | 设计中 | 新建【设计】;【分析】计划完成=当日 + 阶段日历工时 | +| 4 设计完成 | 设计完成 | 【设计】完成态;需求+交付挂 ZIP/截图;交付描述换正式说明 | +| 5 交棒 | **待开发** | **只改交付负责人→何斐**;不新建分析/设计;不写交付计划完成 | + +### 5.2 两条旁路(仍交到同一终点) + +| 路径 | 前提 | 行为 | +|---|---|---| +| **无单快轨** | 尚无分析路径 / 明确跳过分析 | 待开发 + 新建交付+设计(设计当日收口);**不建分析**;默认可不跑 AutoPRD | +| **编号直推** | 已有分析/设计**编号** | 收口空计划完成 → 待开发 + 交付交棒何斐;不新建冗余单 | + +--- + +## 6. 交棒契约(开发 Skill 必须实现) + +### 6.1 入口条件(产品已完成) + +```text +需求状态 = 待开发 +【交付】任务编号 = ONEOS-a(唯一) +【交付】负责人 = 何斐 +【交付】ASSOCIATED → 该需求 +可选:分析/设计编号在「工作项编号(系统)」中 +``` + +### 6.2 开发 Skill 建议入口口令 + +```text +接手开发:需求=ONEOS-R;交付任务=ONEOS-a +拆分开发:交付任务=ONEOS-a;范围=前端|后端|…;负责人=… +开始开发:开发任务=ONEOS-d +开发完成:开发任务=ONEOS-d +``` + +### 6.3 开发 Skill 建议职责边界 + +| 应做 | 不应做 | +|---|---| +| 在【交付】下建一条或多条【开发】(编号幂等) | 再建【分析】/【设计】或第二套【交付】 | +| 正式关联:建议 SUB→交付,或同时 ASSOCIATED→需求 | 只按标题「【开发】xxx」查重 | +| 挂仓库 / 分支 / MR(按你们 Codeup 规范) | 覆盖需求 AutoRDO 段 | +| 改【开发】状态与负责人 | 擅自把需求从待开发退回(除非产品授权回退口令) | +| 检测交付描述是否仍为占位并提示 | 假装 AutoPRD 已齐全 | +| 全部【开发】完成后通知测试 Skill / 建【测试】 | 在产品 Skill 会话里混跑 | + +### 6.4 共享常量(可引用,勿整包加载) + +路径均在 `skills/YunxiaoPMapp/assets/`: + +| 文件 | 内容 | +|---|---| +| `runtime-ids.json` | 项目 spaceId、何斐 ID、工作项类型、状态 transit、字段 79/80 | +| `cn-workday-calendar.json` | 法定节假日 / 调休(阶段日历工时) | + +开发侧可复制一份到自己的 `assets/`,或只读引用短路径;**不要**把 YunxiaoPMapp 的 references 全文 include 进开发 Skill。 + +--- + +## 7. 计划时间与「阶段日历工时」 + +产品侧口径(开发侧若写计划时间建议对齐语义): + +| 对象 | 计划开始 | 计划完成 | 预计工时 | +|---|---|---|---| +| 【交付】 | 首次分析中空则写当日,**永不覆盖** | 产品侧不写(留给上线) | 一般不推全周期 | +| 【分析】【设计】 | 创建时空则写当日,**永不覆盖** | 阶段收口日 | `工作日×8` | + +```text +预计工时 = 阶段日历工时(Lead Time)≠ 人力投入人天 +算法:起止日含首尾,扣周末与法定假,计入调休补班;×8 小时 +禁止:(结束−开始+1)×8 的自然日算法 +``` + +脚注固定:`【系统】预计工时=阶段日历工时(工作日×8),非人力投入预估` +脚本:`scripts/workday_hours.py` + +**建议:** 开发 Skill 若给【开发】写预计工时,在文档中明确是「人力投入预估」还是「阶段日历工时」,避免与产品侧混称。 + +--- + +## 8. Agent 运行时原理(制作 Skill 时照抄模式) + +### 8.1 Plan 门禁 + +凡写云效:`SwitchMode → plan` → 用户确认 → 一口气 apply → 一次校验回报。 +禁止用「参数已齐 / 速度路径」跳过 Plan。 + +### 8.2 模块化路由 + +`SKILL.md` 只做路由与门禁;细则在 `references/*.md`;常量在 `assets/`;脚本在 `scripts/`。 +外置能力(AutoRDO、AutoPRD)**调用对方 Skill**,不内嵌对方全文。 + +### 8.3 幂等 + +```text +幂等键 = 项目 + 需求编号 + 任务类型角色(交付/分析/设计/开发…)+ 已登记任务编号 +命中编号 → 复用并补缺字段;禁止新建第二条同角色主容器(交付唯一) +``` + +### 8.4 验收与回报 + +每次 apply 后自检(产品侧清单见 `references/acceptance.md`)。开发侧建议至少回报: + +```text +【YunxiaoDevapp】 +需求:ONEOS-R | 状态=待开发|开发中|… +交付:ONEOS-a | 负责人=… +开发:ONEOS-d1, ONEOS-d2 | … +仓库/分支:… +下一步:… +``` + +### 8.5 已验证写路径(可复用思路) + +| 动作 | 约定(见 live-api.md) | +|---|---| +| 改状态 | `POST …/status/transit`(勿用错误 updateStatus) | +| 改负责人 | `PATCH …/{id}` + `propertyKey=assignedTo` | +| 建单关系 | create 带 `createWorkitemRelationInfo` | +| 极速复测 | `scripts/live_create_fast.py`(可参考,开发侧另写自己的脚本) | + +--- + +## 9. 回退最小集(开发需配合) + +| 场景 | 产品侧规则 | 开发侧注意 | +|---|---|---| +| 待开发 → 退回设计中 | 需求回退;交付负责人可改回产品;**交付计划开始不改**;重做设计则**新开设计编号** | 已建【开发】是否取消 / 暂停由开发 Skill 定义;勿静默删编号 | +| 设计完成后需求大变 | Plan 问是否回退;更新 AutoPRD;AutoRDO 保留+变更纪要 | 勿覆盖 AutoRDO | +| 取消需求 | 任务标取消;不删编号区块 | 同步取消未完成【开发】 | + +--- + +## 10. 建议的「YunxiaoDevapp」目录骨架 + +```text +YunxiaoDevapp/ +├── SKILL.md # 门禁 + 路由 + 边界(引用本文契约,不 include PMapp) +├── assets/ +│ └── runtime-ids.json # 可从 PMapp 复制/裁剪 +├── references/ +│ ├── model.md # 【开发】与交付/需求的关系约定 +│ ├── handoff-intake.md # 认交付编号 + 占位检测 +│ ├── split-dev-tasks.md # 拆前端/后端多【开发】 +│ ├── codeup.md # 分支 / MR / 关联 +│ ├── commands.md # 口令面 +│ └── acceptance.md +└── scripts/ # 可选极速 API +``` + +`SKILL.md` description 建议写明:触发词「接手开发 / 拆分开发 / 开始开发」;**Does NOT create 【分析】/【设计】**;入口条件需求=待开发。 + +--- + +## 11. 对照检查表(开发 Skill 评审用) + +- [ ] 是否只认「需求编号 + 交付任务编号」,禁止标题查重 +- [ ] 是否拒绝创建第二套【交付】或【分析】【设计】 +- [ ] 占位交棒时是否提示风险且不假装 PRD 齐全 +- [ ] 【开发】是否可编号幂等、可挂到交付树 +- [ ] 是否与 YunxiaoPMapp **会话隔离**(不同 Skill、不同口令) +- [ ] 计划开始字段是否遵守「已有值不覆盖」(若沿用同一字段) +- [ ] 提测 / 【测试】是否交给测试 Skill,本包边界清晰 + +--- + +## 12. 相关文档索引 + +| 主题 | 路径(仓库内) | +|---|---| +| Skill 入口 | `skills/YunxiaoPMapp/SKILL.md` | +| 交付树模型 | `skills/YunxiaoPMapp/references/model.md` | +| 交棒契约 | `skills/YunxiaoPMapp/references/handoff-contract.md` | +| 标准路径 0–5 | `skills/YunxiaoPMapp/references/stage-flow.md` | +| 交棒门禁 / 回退 | `skills/YunxiaoPMapp/references/handoff-and-rollback.md` | +| 描述双段 | `skills/YunxiaoPMapp/references/description-split.md` | +| 编号区块 | `skills/YunxiaoPMapp/references/workitem-ids.md` | +| 快轨 / 编号直推 | `references/fast-track.md` · `number-push.md` | +| 工时算法 | `references/work-hours.md` | +| 实写 API | `references/live-api.md` | + +安装产品 Skill: + +```bash +npx skills add 15810879921-coder/oneos-pm-skills --skill YunxiaoPMapp -a cursor -g -y +``` diff --git a/.cursor/skills/YunxiaoPM/docs-产品部流转流程图.md b/.cursor/skills/YunxiaoPM/docs-产品部流转流程图.md new file mode 100644 index 0000000..9afb919 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/docs-产品部流转流程图.md @@ -0,0 +1,179 @@ +# 产品部流转:YunxiaoPMapp(到交棒为止) + +> 用途:先把**产品部**在云效上的工作流转说清楚;开发 / 测试 / 发版不在本 Skill 内,仅在文末标出交接点。 +> 依据:`references/stage-flow.md` · `model.md` · `fast-track.md` · `handoff-contract.md` +> 日期:2026-07-24 + +--- + +## 1. 产品部管到哪里 + +```text +产品部(YunxiaoPMapp)终点 = 需求状态「待开发」+【交付】负责人「何斐」 +之后 = 请技术经理使用开发 Skill(不建【开发】/【测试】) +``` + +| 谁 | 做什么 | 不做什么 | +|---|---|---| +| 产品 / YunxiaoPMapp | 建需求、打标签、建【交付】【分析】【设计】、推进状态、设计完成灌 AutoPRD、交棒 | 建【开发】【测试】、开分支、提测、发版 | +| 技术经理(接手) | 从「待开发」起拆开发 | 不改写 AutoRDO;不新建第二套【交付】 | + +--- + +## 2. 总览流程图(标准路径) + +```mermaid +flowchart TB + subgraph inputs [材料入口] + Chat[聊天/录音/口述] + AutoRDO["$AutoRDO 清洗"] + Proto[原型页 Make] + end + + subgraph pm [产品部 · YunxiaoPMapp] + S0["0 创建需求\n状态=待处理\n只写 AutoRDO\n不建任务"] + S1["1 受理确认\n状态=已确认\n仍不建任务"] + S2["2 分析中\n建【交付】+【分析】"] + S3["3 设计中\n建【设计】\n收口【分析】"] + S4["4 设计完成\n收口【设计】\nAutoPRD+附件\n灌【交付】描述"] + S5["5 待开发交棒\n交付负责人→何斐\n★ 产品终点"] + end + + subgraph after [产品之后 · 不在本 Skill] + Dev["开发 Skill\n拆【开发】…"] + Test["测试 Skill"] + Rel["发版"] + end + + Chat --> AutoRDO --> S0 + Proto -.-> S4 + S0 --> S1 --> S2 --> S3 --> S4 --> S5 + S5 -->|"交棒契约:需求编号+交付编号"| Dev + Dev --> Test --> Rel +``` + +--- + +## 3. 各步说明(产品侧) + +| 步骤 | 需求状态 | 云效任务动作 | 描述写什么 | 口令示例 | +|---|---|---|---|---| +| 0 创建 | 待处理 | 无 | `## 原始诉求(AutoRDO)`;编号区块待建 | `记录需求:…;推进至=暂不推进` | +| 1 受理 | 已确认 | 仍无 | 不动 | `受理确认:ONEOS-xx` | +| 2 分析中 | 分析中 | 新建【交付】ASSOCIATED→需求;新建【分析】TASK_SUB→交付 | 交付=占位文案 | `开始分析:ONEOS-xx` | +| 3 设计中 | 设计中 | 新建【设计】TASK_SUB→交付;分析计划完成=当日+阶段日历工时 | — | `开始设计:ONEOS-xx;交付=…;分析=…` | +| 4 设计完成 | 设计完成 | 设计完成态;需求+交付挂 ZIP/截图;交付描述换 AutoPRD | `$oneos-autoprd` 灌 `## 产品说明` | `设计完成:ONEOS-xx;设计=…;原型=…` | +| 5 交棒 | **待开发** | **只**把【交付】负责人改为何斐 | 若仍占位须标红风险 | `交棒开发:ONEOS-xx;交付=…` | + +**编号权威:** 需求描述 `## 工作项编号(系统)`(交付 / 分析 / 设计)。后续口令优先带 `ONEOS-xx`,禁止按标题查重。 + +--- + +## 4. 交付树(产品建出来的结构) + +```mermaid +flowchart TB + REQ["产品类需求 ONEOS-R\n状态=阶段真相"] + DEL["【交付】ONEOS-a\nASSOCIATED→需求"] + AN["【分析】ONEOS-b"] + DE["【设计】ONEOS-c"] + + DEL -->|"关联项 ASSOCIATED"| REQ + AN -->|"子项 TASK_SUB"| DEL + DE -->|"子项 TASK_SUB"| DEL +``` + +验收口诀: +- 打开【交付】→「关联项」能看到需求 +- 打开【交付】→「子项」能看到分析/设计(必须 `TASK_SUB`,不能只写 parentIdentifier) + +--- + +## 5. 两条旁路(仍交到同一终点) + +```mermaid +flowchart LR + subgraph standard [标准] + A1[分析中] --> A2[设计中] --> A3[设计完成] --> A4[待开发] + end + subgraph fast [无单快轨] + B1[待处理/已确认等] --> B2["待开发\n建交付+设计当日收口\n不建分析"] + end + subgraph push [编号直推] + C1[已有分析/设计编号] --> C2[收口空计划完成] --> C3[待开发+交棒何斐] + end +``` + +| 路径 | 何时用 | 注意 | +|---|---|---| +| 标准 0→5 | 正常产品节奏 | 设计完成才灌 AutoPRD | +| 快轨 | 明确跳过分析、急交棒 | 默认可不跑 AutoPRD;交付常占位→回报标红 | +| 编号直推 | 树上已有阶段任务编号 | 不新建冗余单;只认编号 | + +--- + +## 6. 产品部人机边界(Plan 门禁) + +```text +凡写云效:Plan 对齐 → 人确认 → 一口气 apply → 一次校验回报 +``` + +| 适合全自动 | 建议人点一下 | +|---|---| +| 建单、打标、建树、改状态、交棒负责人、幂等复用 | 优先级、标签、是否快轨、设计是否真可开发、占位是否接受交棒 | + +--- + +## 7. 交棒给下游时交出什么 + +产品回报至少包含: + +```text +【YunxiaoPMapp】 +风险:(占位交棒则首行标红) +需求:ONEOS-R | 状态=待开发 +交付:ONEOS-a | 负责人=何斐 +分析:ONEOS-b 或 无 +设计:ONEOS-c 或 快轨占位说明 +下一步:请技术经理使用开发 Skill +``` + +下游入口条件见 `references/handoff-contract.md`:认 **需求编号 + 交付任务编号**。 + +--- + +## 8. 和全链路的关系(本文件边界) + +```mermaid +flowchart LR + PM[产品部本文] --> Handoff[待开发交棒] + Handoff --> Dev[开发] + Dev --> QA[测试] + QA --> Rel[发版] +``` + +全链路(含谢佳伟 / 时生亮)见仓库原型: +`src/prototypes/yunxiao-pipeline-handbook/` +产品 Skill 对接开发说明: +`docs-YunxiaoPMapp-实现原理-开发Skill对接.md` + +--- + +## 9. 相关 references + +| 主题 | 路径 | +|---|---| +| 标准 0–5 | `references/stage-flow.md` | +| 交付树 / 子项 | `references/model.md` | +| 快轨 | `references/fast-track.md` | +| 编号直推 | `references/number-push.md` | +| 交棒门禁 | `references/handoff-and-rollback.md` | +| 交棒契约 | `references/handoff-contract.md` | +| 口令 | `references/commands.md` | + +--- + +## 延伸阅读 + +- 四角色泳道(产品/开发/测试/发版):`docs-四角色泳道流程图.md` +- 开发侧草案:`docs-开发侧流转草案.md` diff --git a/.cursor/skills/YunxiaoPM/docs-全流程分步说明.md b/.cursor/skills/YunxiaoPM/docs-全流程分步说明.md new file mode 100644 index 0000000..10a37c9 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/docs-全流程分步说明.md @@ -0,0 +1,318 @@ +# 全流程分步说明:产品 → 开发 → 测试 → 发版 + +> 把整条链路拆成**按顺序执行的步骤**,每步说清:谁做、做什么、云效上变成什么、下一步谁接。 +> 读完应能照着口令/动作走完一轮(标准路径)。 +> 日期:2026-07-24 + +--- + +## 先记住三件事 + +1. **需求状态 = 阶段真相** + 看需求状态就知道现在卡在哪一段(分析中 / 待开发 / 开发中 / 待测试…)。 +2. **每条需求只有 1 个【交付】容器** + 分析、设计、开发、测试都挂在这个交付下面(子项),不要再建第二个【交付】。 +3. **编号说话,不靠标题查** + 口令尽量带 `ONEOS-xx`;改标题不影响关联。 + +```text +需求(状态主轴) + └─【交付】(容器,关联到需求) + ├─【分析】【设计】 ← 产品建 + ├─【开发】… ← 开发建 + └─【测试】 ← 开发提测时建 +【发版】另建,挂迭代 + 范围内需求 +``` + +--- + +## 总步骤一览(共 18 步) + +| 阶段 | 步骤 | 一句话 | +|---|---|---| +| **产品** | 1~6 | 从记诉求到交棒给何斐 | +| **开发** | 7~12 | 从拆任务到提测 | +| **测试** | 13~15 | 从接测到测试完成 | +| **发版** | 16~17 | 从建发版到发布结果 | +| **收口** | 18 | 产品验收关闭 | + +--- + +## 阶段 A|产品(步骤 1~6) + +### 步骤 1|记录需求 + +| | | +|---|---| +| **谁** | 产品 / AI(YunxiaoPMapp) | +| **做什么** | 把聊天、录音、口述先洗成 AutoRDO,再建一条**产品类需求** | +| **需求状态** | **待处理** | +| **云效任务** | 不建任何任务 | +| **描述里写** | `## 原始诉求(AutoRDO)`;编号区先空着 | +| **口令例** | `记录需求:…;推进至=暂不推进` | +| **为什么** | 先落盘,避免口头需求丢;还没确认值不值得做 | + +--- + +### 步骤 2|受理确认 + +| | | +|---|---| +| **谁** | 产品 | +| **做什么** | 确认要做:定优先级、打标签(从云效标签里点选) | +| **需求状态** | **已确认** | +| **云效任务** | 仍不建任务 | +| **口令例** | `受理确认:ONEOS-xx` | +| **为什么** | 和「还没想清楚」的待处理分开;确认后才进分析/设计 | + +--- + +### 步骤 3|开始分析 + +| | | +|---|---| +| **谁** | 产品 / AI | +| **做什么** | ① 建 **【交付】**(整条需求的唯一容器,关联到需求)② 建 **【分析】**(挂在交付**子项**下) | +| **需求状态** | **分析中** | +| **云效任务** | 【交付】+【分析】 | +| **口令例** | `开始分析:ONEOS-xx` | +| **验收** | 打开【交付】→「关联项」有需求;「子项」有【分析】 | +| **为什么** | 分析要有落点;后续设计/开发/测试都挂同一棵树 | + +--- + +### 步骤 4|开始设计 + +| | | +|---|---| +| **谁** | 产品 / AI | +| **做什么** | 建 **【设计】**(子项挂交付);收口【分析】计划时间 | +| **需求状态** | **设计中** | +| **云效任务** | 新增【设计】 | +| **口令例** | `开始设计:ONEOS-xx;交付=…;分析=…` | +| **为什么** | 进入原型/方案设计;分析阶段收口 | + +--- + +### 步骤 5|设计完成 + +| | | +|---|---| +| **谁** | 产品 / AI | +| **做什么** | 收口【设计】;跑 AutoPRD,把**产品说明**灌进【交付】描述;挂原型 ZIP/截图等附件 | +| **需求状态** | **设计完成** | +| **口令例** | `设计完成:ONEOS-xx;设计=…;原型=…` | +| **为什么** | 开发接手时看的是【交付】里的产品说明,不是聊天记录 | + +--- + +### 步骤 6|交棒开发(产品终点) + +| | | +|---|---| +| **谁** | 产品 / AI | +| **做什么** | 需求改为待开发;**【交付】负责人改为何斐**(不新建开发/测试任务) | +| **需求状态** | **待开发** ★ | +| **交出什么** | 需求编号 +【交付】编号 | +| **口令例** | `交棒开发:ONEOS-xx;交付=…` | +| **为什么** | 产品部到此结束;技术经理从这里拆开发 | + +> 旁路:急单可用**快轨**(跳过分析、直接待开发),仍交到同一终点「待开发 + 交付→何斐」。 + +--- + +## 阶段 B|开发(步骤 7~12) + +### 步骤 7|分配开发任务 + +| | | +|---|---| +| **谁** | 开发主管何斐 / 开发 Skill | +| **做什么** | 输入**交付任务编号** → 自动建 **【开发】**;标题=`【开发】`+需求标题;**描述人工补**;指定开发人;自动写计划时间 | +| **关联** | 子项挂【交付】;关联项挂需求 | +| **需求状态** | 仍为 **待开发**(确认即可,一般不用再改) | +| **口令例** | `分配任务:交付=ONEOS-a;开发人=张三` | +| **为什么** | 一条需求可拆多个开发任务;都挂在同一交付下 | + +--- + +### 步骤 8|开始开发 + +| | | +|---|---| +| **谁** | 开发人员 | +| **做什么** | 口令带上**开发任务编号** | +| **开发任务** | → **处理中** | +| **需求状态** | → **开发中** | +| **口令例** | `开始开发:ONEOS-d` | +| **为什么** | 看板一眼能看出「正在写代码」 | + +--- + +### 步骤 9|写代码 / 提 MR + +| | | +|---|---| +| **谁** | 开发人员 | +| **做什么** | 按【交付】里的产品说明实现;分支/MR 建议挂上开发任务编号与需求编号 | +| **需求状态** | 仍为 **开发中** | +| **为什么** | 代码可追溯到哪条开发任务、哪条需求 | + +--- + +### 步骤 10|AI 自测门禁(开发侧草案) + +| | | +|---|---| +| **谁** | AI(开发完成后触发) | +| **做什么** | 找与**需求同标题**的测试计划 → 按用例自测 → 不过则自动提缺陷(标题含开发任务编号)并尝试修复,直到本计划用例全过 | +| **需求状态** | 仍为 **开发中**(未全过前) | +| **为什么** | 在正式提测前先挡住明显问题(属草案,规则可再收紧) | + +--- + +### 步骤 11|开发任务完成 + +| | | +|---|---| +| **谁** | 系统 / AI(用例全过之后) | +| **做什么** | 该条【开发】标为**已完成** | +| **需求状态** | → **开发完成**(若有多条开发,建议等**全部**完成再改,避免过早) | +| **为什么** | 标记「代码+门禁」这一段结束 | + +--- + +### 步骤 12|完成开发 · 正式提测 + +| | | +|---|---| +| **谁** | 开发人员(自测也完成) | +| **做什么** | 口令「完成开发」→ 自动建 **【测试】**(标题=`【测试】`+任务名);子项挂【交付】;建议再关联需求;指派测试(如谢佳伟) | +| **需求状态** | → **待测试** ★ | +| **口令例** | `完成开发:ONEOS-d` | +| **为什么** | 开发交棒给测试;测试侧开始接单 | + +--- + +## 阶段 C|测试(步骤 13~15) + +### 步骤 13|接测试任务 + +| | | +|---|---| +| **谁** | 测试(谢佳伟等) | +| **做什么** | 打开【测试】任务;确认关联的需求与交付;准备/核对测试计划与用例包(建议按**需求编号**分包,比「同标题」更稳) | +| **需求状态** | **待测试** → 开始执行后改为 **测试中** | +| **为什么** | 正式测试以计划用例为准,不靠口头「测过了」 | + +--- + +### 步骤 14|执行用例 · 缺陷回流 + +| | | +|---|---| +| **谁** | 测试执行;失败时回流开发修 | +| **做什么** | 按用例执行;失败建缺陷 → 开发修复 → 再测 | +| **需求状态** | **测试中** | +| **为什么** | 质量关口;未绿不能进发版 | + +--- + +### 步骤 15|测试完成 + +| | | +|---|---| +| **谁** | 测试 / AI 辅助判定 | +| **做什么** | **本需求**相关用例全绿 →【测试】收口;通知发版对接人 | +| **需求状态** | → **测试完成** ★ | +| **注意** | 只卡本需求,不卡同迭代里别的需求 | +| **为什么** | 发版范围以「测试完成」的需求为准 | + +--- + +## 阶段 D|发版(步骤 16~17) + +### 步骤 16|建发版任务 + +| | | +|---|---| +| **谁** | 发版对接人(时生亮)· 手工或 AI | +| **做什么** | 建 **【发版】**;关联**迭代** + 本期内要上的需求(建议再挂交付/测试);汇总更新说明 | +| **需求状态** | 进入发布流程时 → **发布中** | +| **为什么** | 一次发版可能含多条需求;要有明确范围清单 | + +--- + +### 步骤 17|发布结果回写 + +| | | +|---|---| +| **谁** | 发版对接人 / 流水线 | +| **做什么** | 执行发布 | +| **结果** | 成功 → **发布完成**;失败 → **发布失败**(可重试再进发布中) | +| **为什么** | 成败要回写云效,避免「以为发了其实没发」 | + +--- + +## 阶段 E|收口(步骤 18) + +### 步骤 18|业务验收 · 关闭 + +| | | +|---|---| +| **谁** | 产品 | +| **做什么** | 业务验收通过后,关闭需求,并收口【交付】 | +| **需求状态** | → **已关闭** | +| **为什么** | 全链路结束;树上任务与需求一致收口 | + +--- + +## 状态主链(串起来看) + +```text +待处理 → 已确认 → 分析中 → 设计中 → 设计完成 + → 待开发 ← 产品交棒给何斐 + → 开发中 → 开发完成 + → 待测试 → 测试中 → 测试完成 + → 发布中 → 发布完成(或发布失败可重试) + → 已关闭 ← 产品验收关单 +``` + +--- + +## 四个交棒口(最重要) + +| 交棒 | 交出人 | 接棒人 | 交出物 | 状态落点 | +|---|---|---|---|---| +| ① 产品→开发 | 产品 | 何斐 | 需求号 + 交付号 | 待开发 | +| ② 开发→测试 | 开发人 | 测试 | 【测试】任务已建好 | 待测试 | +| ③ 测试→发版 | 测试 | 发版 | 本需求用例全绿 | 测试完成 | +| ④ 发版→产品 | 发版 | 产品 | 发布完成 / 更新说明 | 发布完成 → 已关闭 | + +--- + +## 谁在什么时候动(对照表) + +| 步骤 | 产品 | 开发主管 | 开发人 | 测试 | 发版 | +|---|---|---|---|---|---| +| 1~2 记需求/确认 | ● | | | | | +| 3~5 分析设计 | ● | | | | | +| 6 交棒 | ● | 接 | | | | +| 7 分配任务 | | ● | | | | +| 8~9 开始写码 | | | ● | | | +| 10~11 AI门禁/完成任务 | | | ●/AI | | | +| 12 提测 | | | ● | 接 | | +| 13~15 测试 | | | 修缺陷 | ● | | +| 16~17 发版 | | | | | ● | +| 18 关闭 | ● | | | | | + +--- + +## 相关文档 + +| 文档 | 用途 | +|---|---| +| `docs-四角色泳道流程图.md` | 泳道图总览 | +| `docs-产品部流转流程图.md` | 产品 1~6 细规 | +| `docs-开发侧流转草案.md` | 开发 7~12 细规与待拍板 | +| `yunxiao-pipeline-handbook` | 云效规则与配置 | diff --git a/.cursor/skills/YunxiaoPM/docs-四角色泳道流程图.md b/.cursor/skills/YunxiaoPM/docs-四角色泳道流程图.md new file mode 100644 index 0000000..1dcbaac --- /dev/null +++ b/.cursor/skills/YunxiaoPM/docs-四角色泳道流程图.md @@ -0,0 +1,181 @@ +# 四角色泳道图:产品 · 开发 · 测试 · 发版 + +> 用途:一张图看清从建需求到发版关闭,四条泳道各自做什么、在哪交棒。 +> 依据:产品部流转 · 开发侧草案 · 生产线手册(测试/发版) +> 日期:2026-07-24 +> 说明:开发泳道含「AI 用例门禁」草案;测试泳道为正式提测后的人/计划执行。 + +--- + +## 1. 泳道总览(推荐主图) + +```mermaid +flowchart TB + subgraph PM["泳道① 产品"] + direction TB + P0["建需求 · 待处理
只写 AutoRDO"] + P1["受理确认 · 已确认"] + P2["分析中
建【交付】+【分析】"] + P3["设计中
建【设计】"] + P4["设计完成
AutoPRD 灌交付"] + P5["交棒 · 待开发
交付负责人→何斐"] + P6["业务验收后
关闭需求+交付"] + P0 --> P1 --> P2 --> P3 --> P4 --> P5 + end + + subgraph DEV["泳道② 开发"] + direction TB + D1["何斐:分配任务
建【开发】SUB→交付"] + D2["开发人:开始开发
任务处理中 · 需求开发中"] + D3["写代码 / 提 MR"] + D4["AI 按同标题测试计划自测
不过→提缺陷并修"] + D5["开发任务已完成
需求→开发完成"] + D6["完成开发(含自测)
建【测试】· 需求待测试"] + D1 --> D2 --> D3 --> D4 --> D5 --> D6 + end + + subgraph QA["泳道③ 测试"] + direction TB + T1["接【测试】任务
需求=待测试"] + T2["建/挂测试计划与用例
(建议按需求编号分包)"] + T3["测试中 · 执行用例"] + T4["缺陷回流开发修复"] + T5["本需求用例全绿
需求→测试完成"] + T1 --> T2 --> T3 + T3 -.->|失败| T4 + T4 -.->|再测| T3 + T3 -->|通过| T5 + end + + subgraph REL["泳道④ 发版"] + direction TB + R1["建【发版】任务
挂迭代+范围内需求"] + R2["汇总更新说明"] + R3["发布中"] + R4{"发布结果"} + R5["发布完成"] + R6["发布失败 · 可重试"] + R1 --> R2 --> R3 --> R4 + R4 -->|成功| R5 + R4 -->|失败| R6 + R6 -.-> R3 + end + + P5 -->|"交棒契约
需求编号+交付编号"| D1 + D6 -->|"提测
【测试】已挂交付"| T1 + T5 -->|"交棒发版"| R1 + R5 -->|"业务可验收"| P6 +``` + +--- + +## 2. 横向时间轴版(谁在哪一阶段动) + +从左到右是需求状态主链;上下是四个角色。空格表示该角色此阶段无主动动作。 + +```mermaid +flowchart LR + subgraph S_PM["产品阶段"] + direction TB + sp0["待处理→已确认"] + sp1["分析/设计/设计完成"] + sp2["待开发交棒"] + sp3["已关闭"] + end + + subgraph S_DEV["开发阶段"] + direction TB + sd0["拆【开发】"] + sd1["开发中"] + sd2["AI门禁→开发完成"] + sd3["建【测试】→待测试"] + end + + subgraph S_QA["测试阶段"] + direction TB + sq0["待测试"] + sq1["测试中"] + sq2["测试完成"] + end + + subgraph S_REL["发版阶段"] + direction TB + sr0["建【发版】"] + sr1["发布中"] + sr2["完成/失败"] + end + + sp0 --> sp1 --> sp2 + sp2 --> sd0 --> sd1 --> sd2 --> sd3 + sd3 --> sq0 --> sq1 --> sq2 + sq2 --> sr0 --> sr1 --> sr2 + sr2 --> sp3 +``` + +--- + +## 3. 交接点一览 + +| # | 从 → 到 | 交出什么 | 需求状态落点 | +|---|---|---|---| +| 1 | 产品 → 开发 | 需求编号 +【交付】编号;交付负责人=何斐 | **待开发** | +| 2 | 开发 → 测试 | 【测试】任务(子项挂交付);用例计划可先备好 | **待测试** | +| 3 | 测试 → 发版 | 本需求用例全绿;范围内需求清单 | **测试完成** | +| 4 | 发版 → 产品 | 发布完成证据 / 更新说明 | **发布完成** → 产品关单 **已关闭** | + +--- + +## 4. 各泳道职责边界(一句话) + +| 角色 | 管什么 | 不管什么 | +|---|---|---| +| **产品** | 建需求、【交付】【分析】【设计】、AutoPRD、交棒待开发、验收关闭 | 不建【开发】【测试】【发版】、不开分支 | +| **开发** | 拆【开发】、写码、AI 自测门禁、建【测试】提测 | 不改写产品 AutoRDO;不建第二套【交付】 | +| **测试** | 接【测试】、跑计划用例、缺陷回流、判测试完成 | 不擅自改需求状态到开发中;不建【发版】(可催) | +| **发版** | 建【发版】、挂迭代与范围、发布与回写成败 | 不替代测试判绿;不关闭需求(交给产品) | + +--- + +## 5. 云效工作项树(跨角色) + +```mermaid +flowchart TB + REQ["需求 · 状态=阶段真相"] + DEL["【交付】ASSOCIATED→需求"] + AN["【分析】"] + DE["【设计】"] + DV["【开发】"] + TE["【测试】"] + RE["【发版】ASSOCIATED→迭代/需求"] + + DEL --> REQ + AN -->|TASK_SUB| DEL + DE -->|TASK_SUB| DEL + DV -->|TASK_SUB| DEL + TE -->|TASK_SUB| DEL + DV -.->|ASSOCIATED| REQ + TE -.->|ASSOCIATED| REQ + RE -.->|关联范围| REQ +``` + +--- + +## 6. 典型负责人(手册约定,可按项目改) + +| 泳道 | 典型对接人 | +|---|---| +| 产品 | 王冕 · YunxiaoPMapp / AI | +| 开发 | 何斐(拆任务)→ 王雨昊 / 李振等(执行) | +| 测试 | 谢佳伟 | +| 发版 | 时生亮 | + +--- + +## 相关文档 + +| 文档 | 说明 | +|---|---| +| `docs-全流程分步说明.md` | **整条链路逐步说明(推荐先读)** | +| `docs-产品部流转流程图.md` | 产品 0→5 细图 | +| `docs-开发侧流转草案.md` | 开发 D1–D5 细图 | +| `yunxiao-pipeline-handbook` | 全链路配置与规则 | diff --git a/.cursor/skills/YunxiaoPM/docs-开发侧流转草案.md b/.cursor/skills/YunxiaoPM/docs-开发侧流转草案.md new file mode 100644 index 0000000..27e99f6 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/docs-开发侧流转草案.md @@ -0,0 +1,230 @@ +# 开发侧流转草案(产品交棒之后) + +> 来源:产品经理口述规则整理(2026-07-24) +> 上游终点:产品部 `docs-产品部流转流程图.md` → 需求=**待开发**,【交付】负责人=**何斐** +> 本文件目标:把「何斐拆任务 → 开发执行 → AI 用例门禁 → 提测」画清,供后续 **YunxiaoDevapp** Skill 定稿 +> 状态:**草案**(文末有待拍板项) + +--- + +## 1. 总览流程图 + +```mermaid +flowchart TB + subgraph handoff [产品已交棒] + Ready["需求=待开发\n【交付】负责人=何斐"] + end + + subgraph d1 [D1 分配开发] + Assign["何斐:分配任务 + 交付任务编号"] + CreateDev["自动建【开发】+需求标题\n描述人工补\n指定开发人\n写计划时间"] + Link["子项 TASK_SUB→交付\n关联项 ASSOCIATED→需求"] + StReady["需求保持/确认为 待开发"] + end + + subgraph d2 [D2 开始开发] + Start["开发人:开始开发 + 开发任务编号"] + DevDoing["开发任务→处理中"] + ReqDoing["需求→开发中"] + end + + subgraph d3 [D3 AI 用例门禁] + CodeDone["代码开发完成触发"] + FindPlan["按需求同标题找测试计划"] + AITest["AI 按用例自测"] + Fail["未通过→自动提缺陷\n标题含开发任务编号\nAI 写标题描述"] + Fix["自动修复→再测"] + PassGate["用例全部通过"] + end + + subgraph d4 [D4 开发任务完成] + DevDone["该条【开发】→已完成"] + ReqDevDone["需求→开发完成"] + end + + subgraph d5 [D5 完成开发·提测] + Finish["开发人:完成开发\n含自测完成"] + CreateTest["自动建【测试】+任务名\n子项→交付"] + PendingTest["需求→待测试"] + end + + Ready --> Assign --> CreateDev --> Link --> StReady + StReady --> Start --> DevDoing --> ReqDoing + ReqDoing --> CodeDone --> FindPlan --> AITest + AITest -->|未通过| Fail --> Fix --> AITest + AITest -->|全通过| PassGate --> DevDone --> ReqDevDone + ReqDevDone --> Finish --> CreateTest --> PendingTest +``` + +--- + +## 2. 分步说明(按你的 5 条) + +### D1|何斐分配任务 + +| 项 | 规则 | +|---|---| +| 触发 | Skill 口令:`分配任务` + **交付任务编号** | +| 新建 | 【开发】任务;标题 = `【开发】` + **需求标题** | +| 描述 | **人工添加**(Skill 不代写正文,或只留空模板) | +| 负责人 | 口令指定开发人员 | +| 计划时间 | **自动创建**计划开始/计划完成(算法待定:阶段日历工时 or 人力预估,见待拍板) | +| 关联 | **子项** `TASK_SUB` →【交付】;**关联项** `ASSOCIATED` → 需求 | +| 需求状态 | 改为 / 确认为 **待开发**(见待拍板:与产品交棒是否重复) | + +### D2|开始开发 + +| 项 | 规则 | +|---|---| +| 触发 | `开始开发` + **开发任务编号** | +| 开发任务 | → **处理中** | +| 需求 | → **开发中**(有 ≥1 条开发进入处理中即可,或本条触发即改,见待拍板) | + +### D3|代码完成后 · AI 按测试计划自测 + +| 项 | 规则 | +|---|---| +| 触发 | 「代码开发完成」(MR 合并 / 口令 / Webhook,触发源待定) | +| 找计划 | **自动寻找与需求同标题的测试计划** | +| 执行 | AI 按该计划中用例自行测试 | +| 失败 | 自动提缺陷:标题含 **开发任务编号** + AI 生成标题/描述;再自动修复,循环直到用例全过 | + +### D4|用例全通过 + +| 项 | 规则 | +|---|---| +| 该条【开发】 | → **已完成** | +| 需求 | → **开发完成** | + +### D5|开发人「完成开发」(自测也完成) + +| 项 | 规则 | +|---|---| +| 触发 | `完成开发` +(建议带)开发任务编号 | +| 新建 | 【测试】任务;标题 = `【测试】` + 任务名称(建议=需求标题) | +| 关联 | 子项 →【交付】(建议同时 ASSOCIATED→需求) | +| 需求 | → **待测试** | + +--- + +## 3. 与产品部的衔接 + +```mermaid +flowchart LR + PM["产品 YunxiaoPMapp\n待开发 + 交付→何斐"] --> DevSkill["开发 Skill 草案\n本文 D1–D5"] + DevSkill --> QA["测试执行人/Skill\n待测试之后"] +``` + +| 上游已具备 | 开发侧依赖 | +|---|---| +| 需求编号 +【交付】编号 | D1 入口只认编号,不按标题拆第二套交付 | +| 交付 ASSOCIATED→需求 | D1 开发任务再 ASSOCIATED→需求、SUB→交付 | +| 产品不建【开发】【测试】 | 仅开发 Skill 建这两类 | + +--- + +## 4. 待拍板(建议你先定这几条) + +### 4.1 状态顺序是否自洽 + +你现在的顺序是: + +```text +D3 AI 用例全过 → 开发任务已完成 + 需求「开发完成」 +D5 完成开发 → 建【测试】+ 需求「待测试」 +``` + +与现有手册常见顺序对比: + +```text +手册常见:开发人完成自测 → 待测试 →(测试执行)→ 测试完成 +你草案: AI 先跑测试计划用例过关 → 开发完成 → 人再点完成开发 → 待测试 +``` + +需要确认: +1. D3 的「测试计划」是 **开发自测门禁**,正式【测试】任务仍留给谢佳伟? +2. 还是 D3 已等同正式测试,那 D5 再建【测试】是否重复? + +### 4.2 「需求→待开发」写在 D1 + +产品交棒时需求**已经是待开发**。D1 再改一次通常多余。建议改为: +- D1 **校验**需求已是待开发;若不是则停下或只允许从设计完成快轨补交棒 +- 有开发任务进入处理中时,需求才变为 **开发中**(即你的 D2) + +### 4.3 「与需求同标题的测试计划」 + +标题易改、易撞名。更稳的是: +- 测试计划关联 **需求编号 / 迭代**,或 +- 用例包命名 `CP-{需求编号}-…` + +否则同名需求/改标题会导致找错计划。 + +### 4.4 AI 自动提缺陷并自动修复 + +风险高:无上限循环、误报、改回旧逻辑。建议门禁: +- 最多 N 轮;失败转人工 +- 缺陷必须关联 **开发任务编号 + 需求编号 + 用例 ID** +- 与「防回归」清单联动,禁止静默改无关模块 + +### 4.5 多条【开发】时需求状态 + +| 场景 | 建议 | +|---|---| +| 任一条开始开发 | 需求 → 开发中 | +| 需求 → 开发完成 | **全部**未取消【开发】均为已完成(与手册 Y21 一致) | +| 需求 → 待测试 | 全部【开发】完成后,由末位「完成开发」或系统自动建【测试】 | + +你原文 D4 写「该条」通过就把需求改成开发完成——多开发人并行时会过早。建议改成「全员开发任务完成」。 + +### 4.6 关联写法(与产品侧一致) + +同一 create 只能带一条 `createWorkitemRelationInfo`: +- 优先保证【开发】/【测试】**子项挂在交付**(交付详情「子项」可见) +- ASSOCIATED→需求若同 create 互斥,需约定:先 SUB,再补 ASSOCIATED,或 OpenAPI 双挂 + +--- + +## 5. 建议口令面(草案) + +```text +分配任务:交付任务=ONEOS-a;开发人=张三,李四;计划开始=…;计划完成=… +开始开发:开发任务=ONEOS-d +代码完成:开发任务=ONEOS-d → 触发 AI 用例门禁(或 Webhook) +完成开发:开发任务=ONEOS-d → 建【测试】;需求→待测试 +``` + +--- + +## 6. 推荐定稿后的状态主链(供你勾选) + +**方案 A(贴近你原文,AI 测作开发门禁)** + +```text +待开发 → 开发中 →(AI 用例门禁)→ 开发完成 → 待测试 → 测试中 → 测试完成 → … +``` + +**方案 B(贴近现有生产线手册)** + +```text +待开发 → 开发中 → 开发完成(人自测+MR)→ 待测试 → 测试中(人/AI 跑计划)→ 测试完成 → … +``` + +AI 跑测试计划放在 **待测试之后**,与【测试】任务负责人(如谢佳伟)对齐。 + +--- + +## 7. 下一步 + +1. 你拍板:**方案 A 或 B**,以及多【开发】时「开发完成 / 待测试」的聚合规则 +2. 我按定稿输出 `YunxiaoDevapp` 的 `SKILL.md` 骨架 + 与产品交棒契约对齐段落 +3. 再补一版「仅开发侧」验收清单(10 条以内) + +--- + +## 相关文档 + +| 文档 | 路径 | +|---|---| +| 产品部流转 | `docs-产品部流转流程图.md` | +| 产品↔开发契约 | `docs-YunxiaoPMapp-实现原理-开发Skill对接.md` | +| 全链路手册 | `src/prototypes/yunxiao-pipeline-handbook/` | diff --git a/.cursor/skills/YunxiaoPM/fast-track.md b/.cursor/skills/YunxiaoPM/fast-track.md new file mode 100644 index 0000000..4ae6a81 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/fast-track.md @@ -0,0 +1,47 @@ +# 无单快轨到待开发 + +适用:口令明确「快轨」或「推进至待开发」且当前需求尚无标准分析路径任务(或用户确认跳过分析)。 + +## 动作表 + +| 对象 | 动作 | +|---|---| +| 需求 | 状态 → **待开发**;**预计工时=2**、**实际工时=2**(覆盖同日 8h 日历工时默认);标签按口令;更新编号区块 | +| 【交付】 | 无则新建;ASSOCIATED→需求(勿用 PARENT);负责人→何斐;**计划开始=创建当日**(`79`);**不写**计划完成;**描述双路径**(见下);**标签与需求相同**;更新编号区块 | +| 【设计】 | **必须新建**;**TASK_SUB→交付**(交付「子项」须可见);**描述=复制需求当前描述正文**;计划开始/完成均=当日(create 后 `field/value` 写 `79`+`80`);任务→完成态;**标签与需求相同**;建后再尝试 **ASSOCIATED→原始需求**(Cookie 常失败,见 live-api;失败须标红,**子项优先于关联项**);更新编号区块 | +| 【分析】 | **禁止创建** | + +## 交付描述双路径(A1) + +| 口令/材料 | 【交付】描述 | +|---|---| +| **手工描述**(无指定原型) | 同步需求当前手工/AutoRDO 正文(与设计一致可复制 document) | +| **指定原型页面** | 调用 `$oneos-autoprd`,从原型生成 AutoPRD「产品说明」写入【交付】 | +| 既无手工可同步正文、又无原型 | 才允许占位 `等待设计任务完成后自动填入`,Plan 勾选交棒占位风险,回报首行标红 | + +有手工或原型时**禁止**交棒占位。 + +## AutoPRD + +- 快轨默认可不跑 AutoPRD;**例外**:口令指定原型 → 交付走 AutoPRD 路径(上表)。 +- 口令同时「设计完成 + 原型」时仍按步骤 4 灌需求产品说明段。 + +## 查重 + +只认任务编号。已有分析单不得因「快轨」被删除。 + +## 回报必含 + +- 需求 / 交付 / 设计编号 +- 「快轨:未建分析;设计已同步需求描述并当日收口」 +- 交付描述来源:手工同步 / AutoPRD / 占位(若占位则首行标红) +- 需求预计工时=2、实际工时=2 +- 设计关联项含需求编号;交付子项含设计编号 + +## 与标准 / 编号直推对比 + +```text +标准:分析中→交付+分析 → 设计中→设计+收口分析 → 设计完成→收口设计 → 待开发交棒 +快轨(无既有阶段单):→ 待开发:新建交付+设计+交棒何斐(无分析;设计当日收口;需求工时 2+2) +编号直推:已有分析/设计编号 → 收口空计划完成 → 待开发+交付交棒(不新建冗余单) +``` diff --git a/.cursor/skills/YunxiaoPM/live-api.md b/.cursor/skills/YunxiaoPM/live-api.md new file mode 100644 index 0000000..f2bab69 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/live-api.md @@ -0,0 +1,242 @@ +# 云效实写 API(YunxiaoPMapp 已验证) + +`verified_at`: 2026-07-25 · **项目须门禁 PJ 点选**(见 [project-selection.md](project-selection.md));历史验证样本项目为 `01_ONEOS` / 原「统一运营管理平台」(`last_selected.spaceIdentifier` 见 `assets/runtime-ids.json`,禁止未点选即使用)。 + +本文件只记**已跑通**的写法;禁止再盲试 `updateStatus` / 错误 `updateFieldValue` POST。 + +## 认证 + +- Cookie:Chrome 域 `.aliyun.com` / `devops.aliyun.com`(`browser_cookie3` 或 Playwright storage) +- Header:`x-xsrf-token` = cookie `XSRF-TOKEN`(URL 解码后) +- `Origin` / `Referer`:`https://devops.aliyun.com` + +## 建单 + +`POST|PUT /projex/api/workitem/workitem?_input_charset=utf-8` + +创建响应 `result.identifier` / `serialNumber` 即编号真相;**禁止按标题查重**。 + +## 改负责人(已通) + +```http +PATCH /projex/api/workitem/workitem/{id}?_input_charset=utf-8 +{"propertyKey":"assignedTo","propertyValue":"","operateType":"COVER"} +``` + +交棒:【交付】`propertyValue` = 何斐 ID。 + +## 打标签(已通) + +```http +PATCH /projex/api/workitem/workitem/{id}?_input_charset=utf-8 +{"workitemIdentifier":"{id}","propertyKey":"tag","propertyValue":"[,]","operateType":"COVER"} +``` + +## 改状态(已通 · 唯一推荐) + +```http +POST /projex/api/workitem/workitem/{id}/status/transit?_input_charset=utf-8 +{"fromStatus":"<当前status.identifier>","toStatus":"<目标status.identifier>"} +``` + +成功:`code=200` 且 `result=true`。失败时 `errorMsg` 含「不能流转」。 + +### 需求状态 ID(本项目) + +| 显示名 | identifier | +|---|---| +| 待处理 | `100005` | +| 已确认 | `32` | +| 分析中 | `154395` | +| 设计中 | `156603` | +| 设计完成 | `307012` | +| 待开发 | `1582fc929d429111b925309493` | + +### 任务状态 ID + +| 显示名 | identifier | +|---|---| +| 待处理 | `100005` | +| 已完成 | `100014` | + +### 极速交棒跳转(工作流允许) + +从「待处理」菜单可见直达「设计完成」;推荐最少跳: + +```text +待处理 → 设计完成 → 待开发 +``` + +标准路径若需看板留痕,可走完整链:已确认→分析中→设计中→设计完成→待开发(仍用本 API,勿开 UI)。 + +### 禁止(已证伪) + +| 写法 | 结果 | +|---|---| +| `PATCH …/updateStatus` + `statusIdentifier` | `400 不能为空` | +| `PATCH …/{id}` + `propertyKey=status` | `property not found` | +| Playwright 点左侧/列表上的状态色块(`x<1100`) | 假成功、状态不落库 | + +仅当 `status/transit` 不可用时,才用 UI:右侧详情状态钮(`getBoundingClientRect().x > 1100`)+ `.next-menu-item`。 + +## 计划开始/完成 · 提交部门/人 · 预计工时(2026-07-27 修订) + +**通用字段写入(已通):** + +```http +POST /projex/api/workitem/workitem/field/value/{workitemId}?_input_charset=utf-8 +Content-Type: application/x-www-form-urlencoded + +fieldValueList=[{"fieldIdentifier":"79","value":"2026-07-27 12:00:00"},{"fieldIdentifier":"3132597a9718d1c282b7ba5a0c","value":"业务管理部"},{"fieldIdentifier":"9e01269e96f91fbb97d36bf5b3","value":"何苗苗"}] +``` + +| 字段 | fieldIdentifier | value | +|---|---|---| +| 计划开始 | `79` | `YYYY-MM-DD HH:mm:ss`(推荐正午)或 epoch ms 字符串 | +| 计划完成 | `80` | 同上 | +| 提交部门 | `3132597a9718d1c282b7ba5a0c` | 纯文本 | +| 提交人 | `9e01269e96f91fbb97d36bf5b3` | 纯文本 | + +**预计工时 `101586`:禁止直接改字段**(报「不可直接修改」)。须登记: + +```http +POST /projex/api/workitem/workitem/time/estimate?_input_charset=utf-8 +{"workitemIdentifier":"","spentTime":8,"type":"develop","description":"阶段日历工时","recordUserIdentifier":"","forCreate":false,"containsRestDay":false} +``` + +删除多余预估:`DELETE /projex/api/workitem/workitem/time/estimate/{workitemId}/{estimateId}` +列表:`GET …/time/estimate/list?workitemIdentifier=` + +旧写法 `PATCH …/updateWorkitemFieldValue` 对上述自定义字段常 `400 不能为空`,勿再优先使用。 + +## 父子 / 子项 / 关联项(已通 · 2026-07-23 修订 · 子项优先) + +### 关联项(【交付】强制 · ASSOCIATED) + +任务详情「关联项」只认 `ASSOCIATED`。**仅【交付】**建单时 `createWorkitemRelationInfo` 必须指向**需求**: + +```json +{ + "createWorkitemRelationInfo": { + "relatedWorkitemIdentifier": "<需求id>", + "relatedToRelationIdentifier": "ASSOCIATED" + } +} +``` + +校验: + +```http +GET /projex/api/workitem/v2/workitem/{交付id}/relation/workitem/list/by-relation-category?category=ASSOCIATED&isForward=true +``` + +`result` 含该需求即通过。 + +### 禁止(关联项) + +| 写法 | 结果 | +|---|---| +| `relatedToRelationIdentifier=PARENT` 把交付挂需求 | 详情可能有 parent,**关联项仍为空** | +| 分析/设计只用 `ASSOCIATED→需求` + `parentIdentifier` | 关联项可能有,**交付子项仍为空**(ONEOS-246/247) | +| 建后再 `POST …/relation/record` 补关系 | Cookie 路径下常报「不能关联相同的工作项」 | +| `createWorkitemRelationList` | 不落 ASSOCIATED | + +### 子项(【分析】/【设计】强制 · TASK_SUB) + +「子项」页读 `PARENT_SUB` / `TASK_SUB`,分析/设计**必须**: + +```json +{ + "parent": "<交付id>", + "parentIdentifier": "<交付id>", + "createWorkitemRelationInfo": { + "relatedWorkitemIdentifier": "<交付id>", + "relatedToRelationIdentifier": "TASK_SUB" + } +} +``` + +同一 create 只能带一条 `createWorkitemRelationInfo`。 +`ASSOCIATED→需求` 与 `TASK_SUB→交付` **不能同时写**。 + +**产品优先级:交付「子项」tab > 阶段任务「关联项」。** +交付本身仍必须 `ASSOCIATED→需求`。标准路径下分析/设计的「关联项」允许为空。 + +**无单快轨例外(设计双挂):** create 用 `TASK_SUB→交付` 后,须再补 **ASSOCIATED→原始需求**,使设计详情「关联项」可见需求。 + +```http +POST /projex/api/workitem/workitem/{设计id}/relation/record?_input_charset=utf-8 +{"relationIdentifier":"ASSOCIATED","toWorkitemIdentifier":"<需求id>"} +``` + +**已证伪(Cookie · 2026-07-27):** 凡工作项**已创建**后再 `relation/record` 补挂(含 ASSOCIATED / TASK_SUB),常报「不能关联相同的工作项」;与是否已有父项无关。`createWorkitemRelationList` 亦不落 ASSOCIATED。 +**可行路径:** + +| 目标 | 做法 | +|---|---| +| 交付「子项」可见设计(优先) | create 带 `TASK_SUB→交付` | +| 设计「关联项」可见需求 | create 带 `ASSOCIATED→需求`(与上互斥,同 create 只能一条) | +| 双挂 | 需个人 `x-yunxiao-token` OpenAPI;Cookie 路径**不得**声称成功 | + +产品默认:**子项优先**;关联项失败须在回报中标红并列出缺项。 + +校验子项: + +```http +GET /projex/api/workitem/v2/workitem/{交付id}/relation/workitem/list/by-relation-category?category=PARENT_SUB&isForward=true +``` + +结果须含对应分析/设计 identifier。 + +校验设计关联项: + +```http +GET /projex/api/workitem/v2/workitem/{设计id}/relation/workitem/list/by-relation-category?category=ASSOCIATED&isForward=true +``` + +`result` 须含原始需求 identifier。 + +### 无单快轨字段默认(2026-07-27) + +| 对象 | 规则 | +|---|---| +| 【设计】描述 | 复制需求 document HTML | +| 【设计】`79`/`80` | 当日 `12:00:00` / `23:59:59`,create 后 `field/value`(勿 create 同时带 79+80) | +| 【交付】描述 | 手工同步需求正文,或原型→AutoPRD;禁止无故占位 | +| 【交付】`79` | 创建当日;不写 `80` | +| 【交付】/【设计】标签 | 与需求相同,`PATCH propertyKey=tag` | +| 需求预计工时 | `time/estimate` **spentTime=2**(先删多余预估) | +| 需求实际工时 | `POST …/workitem/time`,body 用 **`actualTime`**(非 spentTime)+ `gmtStart`/`gmtEnd` epoch ms 字符串;见 `runtime-ids.json` `fields.actual_hours` | +| 描述更新 | `PATCH …/workitem/{id}/document`,`{"content":"","formatType":"RICHTEXT"}` | + +## 迭代挂接(已通 · 2026-07-27 · 只挂交付) + +创建迭代:`POST /projex/api/workspace/sprint`(必填 `staffIds`;可写 `capacityHours`)。 + +挂【交付】到迭代: + +```http +PATCH /projex/api/workitem/workitem/{交付id}?_input_charset=utf-8 +{"workitemIdentifier":"{交付id}","propertyKey":"sprint","propertyValue":"{sprintId}","operateType":"COVER"} +``` + +清空误挂(如需求):`propertyValue:""` + `operateType:"COVER"`。 + +**校验**必须读 `/extra`(详情主接口常不含 sprint 字段,禁止据此判失败): + +```http +GET /projex/api/workitem/workitem/{id}/extra?_input_charset=utf-8 +→ result.sprint[].identifier / name +``` + +产品规则:**只挂【交付】**;需求 / 分析 / 设计默认不挂(除非口令显式)。 + +### 极速建单注意 + +1. Cookie 只刷一次;全程纯 HTTP,默认**不开浏览器**。 +2. 交付建完后,【分析】与【设计】**并行**创建(均 TASK_SUB→交付);标准/快轨两树可并行。 +3. 状态用 `transit` + **本地追踪 fromStatus**(禁止每次 GET);负责人在交棒场景下**创建时即何斐**。 +4. 建单 `fieldValueList` 可带计划开始 `79`;**不要**在 create 同时写 `79+80`(同日会 400)。 +5. 标签必须 PATCH(create 带 tag 不落库);可与建子任务重叠;快轨交付/设计须与需求同标签。 +6. `requests.Session` keep-alive;**禁止**对共享 opener 加全局锁。 +7. 脚本入口:`scripts/live_create_fast.py`(v5:快轨描述/计划/标签/工时 2+2/设计 ASSOCIATED 补挂)。 diff --git a/.cursor/skills/YunxiaoPM/live_create_fast.py b/.cursor/skills/YunxiaoPM/live_create_fast.py new file mode 100644 index 0000000..4e5be22 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/live_create_fast.py @@ -0,0 +1,660 @@ +#!/usr/bin/env python3 +"""YunxiaoPM 极速真实建单 v5:快轨描述/计划/标签/工时 2+2/设计 ASSOCIATED 补挂。""" +from __future__ import annotations + +import json +import time +import urllib.parse +from concurrent.futures import ThreadPoolExecutor, as_completed +from datetime import datetime, timedelta, timezone +from pathlib import Path +from typing import Any + +try: + import browser_cookie3 +except ImportError: # pragma: no cover + browser_cookie3 = None + +import requests + +ROOT = Path(__file__).resolve().parents[1] +RUNTIME = json.loads((ROOT / "assets" / "runtime-ids.json").read_text()) +STATUS = RUNTIME["status"]["req"] +TASK_STATUS = RUNTIME["status"]["task"] +FAST = RUNTIME.get("fields", {}).get("fast_track") or {} + +SPACE = ( + RUNTIME.get("project", {}).get("spaceIdentifier") + or (RUNTIME.get("project", {}).get("last_selected") or {}).get("spaceIdentifier") +) +if not SPACE: + raise RuntimeError( + "未选定项目 spaceIdentifier:须先走门禁 PJ 点选,或设置 runtime project.last_selected" + ) +REQ_TYPE = RUNTIME["workitem_types"]["product_req"]["identifier"] +TASK_TYPE = RUNTIME["workitem_types"]["task"]["identifier"] +HEFEI = RUNTIME["people"]["hefei"]["identifier"] +WANG = RUNTIME["people"].get("wangmian", {}).get("identifier", "6811df000601d2fea60144a9") +PRI = RUNTIME["priority"]["中"] +TAG_FAULT = RUNTIME.get("tags", {}).get("故障管理", "ceb526a7343995577645317e9a") +PLACEHOLDER = RUNTIME["delivery_placeholder"] +PROTO = "https://prototype.lnoneos.com/vehicle-fault-handling/index.html" +TZ = timezone(timedelta(hours=8)) +TODAY = datetime.now(TZ).strftime("%Y-%m-%d") +NOON = f"{TODAY} 12:00:00" +EOD = f"{TODAY} 23:59:59" +NOON_MS = str( + int(datetime.now(TZ).replace(hour=12, minute=0, second=0, microsecond=0).timestamp() * 1000) +) +START_MS = str( + int(datetime.now(TZ).replace(hour=9, minute=0, second=0, microsecond=0).timestamp() * 1000) +) +END_MS = str( + int(datetime.now(TZ).replace(hour=11, minute=0, second=0, microsecond=0).timestamp() * 1000) +) +FAST_EST = int(FAST.get("req_estimated_hours", 2)) +FAST_ACT = int(FAST.get("req_actual_hours", 2)) + +PENDING = STATUS["待处理"] +DESIGN_DONE = STATUS["设计完成"] +PENDING_DEV = STATUS["待开发"] +TASK_DONE = TASK_STATUS["已完成"] + +_COOKIE = "" +_XSRF = "" + + +def load_auth() -> None: + global _COOKIE, _XSRF + jar: dict[str, str] = {} + if browser_cookie3: + for domain in (".aliyun.com", "devops.aliyun.com", ".devops.aliyun.com"): + try: + for c in browser_cookie3.chrome(domain_name=domain): + jar[c.name] = c.value + except Exception: + pass + if not jar: + p = Path("/tmp/yunxiao_cookies.json") + if p.exists(): + raw = json.loads(p.read_text()) + jar = ( + raw + if isinstance(raw, dict) and "XSRF-TOKEN" in raw + else {c["name"]: c["value"] for c in raw.get("cookies", [])} + ) + _COOKIE = "; ".join(f"{k}={v}" for k, v in jar.items()) + x = jar.get("XSRF-TOKEN", "") + _XSRF = urllib.parse.unquote(x) if "%" in x else x + if not _XSRF: + raise RuntimeError("缺少 XSRF-TOKEN:请先在 Chrome 登录 devops.aliyun.com") + + +def session() -> requests.Session: + s = requests.Session() + s.headers.update( + { + "Content-Type": "application/json", + "Cookie": _COOKIE, + "x-xsrf-token": _XSRF, + "X-XSRF-TOKEN": _XSRF, + "Origin": "https://devops.aliyun.com", + "Referer": f"https://devops.aliyun.com/projex/project/{SPACE}/req", + "accept": "application/json", + "User-Agent": "YunxiaoPM-live_create_fast/5.0", + "Connection": "keep-alive", + } + ) + return s + + +def api(s: requests.Session, method: str, url: str, body: Any = None) -> dict: + r = s.request(method, url, json=body, timeout=60) + try: + return r.json() + except Exception: + r.raise_for_status() + raise + + +def create(s: requests.Session, payload: dict) -> dict: + url = "https://devops.aliyun.com/projex/api/workitem/workitem?_input_charset=utf-8" + j = api(s, "POST", url, payload) + r = j.get("result") or {} + if j.get("code") == 200 and isinstance(r, dict) and r.get("identifier"): + return r + j = api(s, "PUT", url, payload) + r = j.get("result") or {} + if j.get("code") == 200 and isinstance(r, dict) and r.get("identifier"): + return r + raise RuntimeError(f"create failed: {j}") + + +def get(s: requests.Session, wid: str) -> dict: + return api( + s, + "GET", + f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}?_input_charset=utf-8", + )["result"] + + +def apply_tag(s: requests.Session, wid: str, tag_id: str = TAG_FAULT) -> None: + j = api( + s, + "PATCH", + f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}?_input_charset=utf-8", + { + "workitemIdentifier": wid, + "propertyKey": "tag", + "propertyValue": tag_id, + "operateType": "COVER", + }, + ) + if j.get("code") != 200: + raise RuntimeError(f"tag failed {wid}: {j}") + + +def set_document(s: requests.Session, wid: str, html: str) -> None: + j = api( + s, + "PATCH", + f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}/document?_input_charset=utf-8", + {"content": html, "formatType": "RICHTEXT"}, + ) + if j.get("code") != 200 or j.get("errorMsg"): + raise RuntimeError(f"document failed {wid}: {j}") + + +def set_fields(s: requests.Session, wid: str, pairs: list[tuple[str, str]]) -> None: + data = urllib.parse.urlencode( + { + "fieldValueList": json.dumps( + [{"fieldIdentifier": k, "value": v} for k, v in pairs], ensure_ascii=False + ) + } + ) + r = s.post( + f"https://devops.aliyun.com/projex/api/workitem/workitem/field/value/{wid}?_input_charset=utf-8", + data=data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + timeout=60, + ) + j = r.json() + if j.get("code") != 200: + raise RuntimeError(f"field/value failed {wid}: {j}") + + +def set_estimate_hours(s: requests.Session, wid: str, hours: int, user: str = WANG) -> None: + est = api( + s, + "GET", + f"https://devops.aliyun.com/projex/api/workitem/workitem/time/estimate/list?workitemIdentifier={wid}", + ).get("result") or [] + for row in est: + eid = row.get("identifier") + if eid: + api( + s, + "DELETE", + f"https://devops.aliyun.com/projex/api/workitem/workitem/time/estimate/{wid}/{eid}", + ) + j = api( + s, + "POST", + "https://devops.aliyun.com/projex/api/workitem/workitem/time/estimate?_input_charset=utf-8", + { + "workitemIdentifier": wid, + "spentTime": hours, + "type": "develop", + "description": "快轨默认预计工时", + "recordUserIdentifier": user, + "forCreate": False, + "containsRestDay": False, + }, + ) + if j.get("code") != 200: + raise RuntimeError(f"estimate failed {wid}: {j}") + + +def set_actual_hours(s: requests.Session, wid: str, hours: int, user: str = WANG) -> None: + j = api( + s, + "POST", + "https://devops.aliyun.com/projex/api/workitem/workitem/time?_input_charset=utf-8", + { + "workitemIdentifier": wid, + "actualTime": hours, + "type": "develop", + "description": "快轨默认实际工时", + "recordUserIdentifier": user, + "gmtStart": START_MS, + "gmtEnd": END_MS, + }, + ) + if j.get("code") != 200 or not j.get("result"): + raise RuntimeError(f"actual time failed {wid}: {j}") + + +def try_associate_to_req(s: requests.Session, stage_id: str, req_id: str) -> bool: + """建后补 ASSOCIATED→需求。Cookie 下常失败,成功返回 True。""" + bodies = [ + {"relationIdentifier": "ASSOCIATED", "toWorkitemIdentifier": req_id}, + { + "relationIdentifier": "ASSOCIATED", + "fromWorkitemIdentifier": stage_id, + "toWorkitemIdentifier": req_id, + }, + ] + for body in bodies: + for url in ( + f"https://devops.aliyun.com/projex/api/workitem/workitem/{stage_id}/relation/record?_input_charset=utf-8", + f"https://devops.aliyun.com/projex/api/workitem/v2/workitem/{stage_id}/relation/record?_input_charset=utf-8", + ): + j = api(s, "POST", url, body) + if j.get("code") == 200 and j.get("result") not in (None, False): + if not (isinstance(j.get("result"), dict) and j["result"].get("status") in (404, 405)): + rows = list_associated(s, stage_id) + if req_id in {r.get("identifier") for r in rows}: + return True + return False + + +def transit(s: requests.Session, wid: str, from_status: str, to_status: str) -> None: + if from_status == to_status: + return + j = api( + s, + "POST", + f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}/status/transit?_input_charset=utf-8", + {"fromStatus": from_status, "toStatus": to_status}, + ) + if not (j.get("code") == 200 and j.get("result") is True): + raise RuntimeError(f"transit {wid} {from_status}->{to_status}: {j}") + + +def md_to_html(md: str) -> str: + parts = [] + for line in md.splitlines(): + if line.startswith("## "): + parts.append(f"

{line[3:]}

") + elif line.startswith("### "): + parts.append(f"

{line[4:]}

") + elif line.startswith("- "): + parts.append(f"

• {line[2:]}

") + elif line.strip(): + parts.append(f"

{line}

") + return "".join(parts) + + +def req_document_html(s: requests.Session, rid: str) -> str: + w = get(s, rid) + return ((w.get("document") or {}).get("content") or w.get("description") or "").strip() + + +def req_payload(subject: str, html: str) -> dict: + return { + "subject": subject, + "description": html, + "formatType": "RICHTEXT", + "document": {"content": html, "formatType": "RICHTEXT"}, + "spaceIdentifier": SPACE, + "space": SPACE, + "spaceType": "Project", + "workitemTypeIdentifier": REQ_TYPE, + "workitemType": REQ_TYPE, + "categoryIdentifier": "Req", + "category": "Req", + "assignedTo": WANG, + "fieldValueList": [ + {"fieldIdentifier": "priority", "value": PRI}, + {"fieldIdentifier": "assignedTo", "value": WANG}, + ], + "attachmentIdList": [], + "cloneFrom": None, + "createWorkitemRelationList": [], + } + + +def task_payload( + subject: str, + html: str, + assignee: str, + *, + plan_start: bool = True, + associated_req: str | None = None, + parent_delivery: str | None = None, +) -> dict: + fvl = [ + {"fieldIdentifier": "priority", "value": PRI}, + {"fieldIdentifier": "assignedTo", "value": assignee}, + ] + if plan_start: + fvl.append({"fieldIdentifier": "79", "value": NOON_MS}) + payload: dict[str, Any] = { + "subject": subject, + "description": html, + "formatType": "RICHTEXT", + "spaceIdentifier": SPACE, + "space": SPACE, + "spaceType": "Project", + "workitemTypeIdentifier": TASK_TYPE, + "workitemType": TASK_TYPE, + "categoryIdentifier": "Task", + "category": "Task", + "assignedTo": assignee, + "fieldValueList": fvl, + "attachmentIdList": [], + "cloneFrom": None, + } + if parent_delivery: + payload["parent"] = parent_delivery + payload["parentIdentifier"] = parent_delivery + payload["createWorkitemRelationInfo"] = { + "relatedWorkitemIdentifier": parent_delivery, + "relatedToRelationIdentifier": "TASK_SUB", + } + else: + if not associated_req: + raise ValueError("associated_req required for delivery: ASSOCIATED→需求") + payload["createWorkitemRelationInfo"] = { + "relatedWorkitemIdentifier": associated_req, + "relatedToRelationIdentifier": "ASSOCIATED", + } + return payload + + +def list_associated(s: requests.Session, wid: str) -> list[dict]: + j = api( + s, + "GET", + f"https://devops.aliyun.com/projex/api/workitem/v2/workitem/{wid}/relation/workitem/list/by-relation-category?category=ASSOCIATED&isForward=true", + ) + return j.get("result") or [] + + +def assert_associated_to_req(s: requests.Session, wid: str, req_id: str, label: str) -> None: + rows = list_associated(s, wid) + ids = {r.get("identifier") for r in rows} + if req_id not in ids: + raise RuntimeError( + f"{label} 关联项未挂需求:期望 {req_id},实际 {[r.get('serialNumber') for r in rows]}" + ) + + +AUTO_RDO = """## 原始诉求(AutoRDO) + +运维需在故障处置页承接机器人上报的故障,完成处置、挂起与归档,并保留证据链;工作台相关统计口径需与处置页一致 + +待确认: +- 本期是否含真实短信/邮件通道(现口径一般为演示模板) + +## 工作项编号(系统) +- 交付:待建 +- 分析:待建 +- 设计:待建 +""" + + +def summarize_from_create(w: dict, *, status: str, assignee_name: str) -> dict: + return { + "serial": w.get("serialNumber"), + "id": w.get("identifier"), + "subject": w.get("subject"), + "status": status, + "assignee": assignee_name, + "parent": w.get("parentIdentifier"), + } + + +def build_normal() -> dict: + t0 = time.perf_counter() + s = session() + title = "【新增】故障处置(YunxiaoPMapp标准·极速v2)" + req = create(s, req_payload(title, md_to_html(AUTO_RDO + f"\n原型:{PROTO}\n"))) + rid = req["identifier"] + + with ThreadPoolExecutor(max_workers=2) as pool: + f_tag_r = pool.submit(apply_tag, session(), rid) + f_deliv = pool.submit( + create, + session(), + task_payload( + f"【交付】{title}", + f"

{PLACEHOLDER}

", + HEFEI, + associated_req=rid, + ), + ) + deliv = f_deliv.result() + f_tag_r.result() + did = deliv["identifier"] + + with ThreadPoolExecutor(max_workers=3) as pool: + f_tag_d = pool.submit(apply_tag, session(), did) + f_ana = pool.submit( + create, + session(), + task_payload( + f"【分析】{title}", + "

分析阶段:故障处置台账、挂起归档与证据链。

", + WANG, + associated_req=rid, + parent_delivery=did, + ), + ) + f_des = pool.submit( + create, + session(), + task_payload( + f"【设计】{title}", + f"

设计阶段:对齐原型 {PROTO}

", + WANG, + associated_req=rid, + parent_delivery=did, + ), + ) + ana = f_ana.result() + des = f_des.result() + f_tag_d.result() + + with ThreadPoolExecutor(max_workers=3) as pool: + list( + as_completed( + [ + pool.submit(transit, session(), rid, PENDING, DESIGN_DONE), + pool.submit(transit, session(), ana["identifier"], PENDING, TASK_DONE), + pool.submit(transit, session(), des["identifier"], PENDING, TASK_DONE), + ] + ) + ) + transit(s, rid, DESIGN_DONE, PENDING_DEV) + + return { + "path": "normal", + "elapsed_s": round(time.perf_counter() - t0, 3), + "req": summarize_from_create(req, status="待开发", assignee_name="王冕"), + "delivery": summarize_from_create(deliv, status="待处理", assignee_name="何斐"), + "analysis": summarize_from_create(ana, status="已完成", assignee_name="王冕"), + "design": summarize_from_create(des, status="已完成", assignee_name="王冕"), + } + + +def build_fast( + *, + tag_id: str = TAG_FAULT, + has_prototype: bool = True, + delivery_html: str | None = None, +) -> dict: + """无单快轨。 + + - 设计描述 = 需求 document + - 交付描述 = 手工同步需求正文(无原型)或传入 AutoPRD HTML;禁止无故占位 + - 设计 79/80=当日;交付 79=当日 + - 需求/交付/设计同标签 + - 需求预计/实际工时各 2 + - 设计 TASK_SUB→交付后尝试 ASSOCIATED→需求 + """ + t0 = time.perf_counter() + s = session() + title = "【新增】故障处置(YunxiaoPM快轨·极速v5)" + req_html = md_to_html(AUTO_RDO + (f"\n原型:{PROTO}\n" if has_prototype else "\n")) + req = create(s, req_payload(title, req_html)) + rid = req["identifier"] + # 以落库 document 为准(与手动建需后读需求一致) + req_html = req_document_html(s, rid) or req_html + + if delivery_html: + deliv_html = delivery_html + desc_source = "autoprd" + elif req_html.strip(): + deliv_html = req_html + desc_source = "manual_sync" + else: + deliv_html = f"

{PLACEHOLDER}

" + desc_source = "placeholder" + + with ThreadPoolExecutor(max_workers=2) as pool: + f_tag_r = pool.submit(apply_tag, session(), rid, tag_id) + f_deliv = pool.submit( + create, + session(), + task_payload( + f"【交付】{title}", + deliv_html, + HEFEI, + associated_req=rid, + ), + ) + deliv = f_deliv.result() + f_tag_r.result() + did = deliv["identifier"] + + with ThreadPoolExecutor(max_workers=2) as pool: + f_tag_d = pool.submit(apply_tag, session(), did, tag_id) + f_des = pool.submit( + create, + session(), + task_payload( + f"【设计】{title}", + req_html, + WANG, + parent_delivery=did, + ), + ) + des = f_des.result() + f_tag_d.result() + des_id = des["identifier"] + apply_tag(s, des_id, tag_id) + + # 计划时间:设计起止当日;交付开始当日(create 已带 79,再 field/value 加固) + set_fields(s, des_id, [("79", NOON), ("80", EOD)]) + set_fields(s, did, [("79", NOON)]) + + # 设计关联项补挂需求(可能失败,回报 risk) + design_assoc_ok = try_associate_to_req(s, des_id, rid) + + with ThreadPoolExecutor(max_workers=2) as pool: + list( + as_completed( + [ + pool.submit(transit, session(), rid, PENDING, DESIGN_DONE), + pool.submit(transit, session(), des_id, PENDING, TASK_DONE), + ] + ) + ) + transit(s, rid, DESIGN_DONE, PENDING_DEV) + + set_estimate_hours(s, rid, FAST_EST) + set_actual_hours(s, rid, FAST_ACT) + + risk = None + if desc_source == "placeholder": + risk = "交付描述仍为占位" + if not design_assoc_ok: + risk = (risk + ";" if risk else "") + "设计 ASSOCIATED→需求补挂失败(Cookie),须 UI/OpenAPI 兜底" + + return { + "path": "fast", + "elapsed_s": round(time.perf_counter() - t0, 3), + "req": summarize_from_create(req, status="待开发", assignee_name="王冕"), + "delivery": summarize_from_create(deliv, status="待处理", assignee_name="何斐"), + "design": summarize_from_create(des, status="已完成", assignee_name="王冕"), + "analysis": None, + "delivery_desc_source": desc_source, + "design_associated_ok": design_assoc_ok, + "risk": risk, + } + + +def main() -> None: + auth0 = time.perf_counter() + load_auth() + auth_s = round(time.perf_counter() - auth0, 3) + + wall0 = time.perf_counter() + with ThreadPoolExecutor(max_workers=2) as pool: + f_fast = pool.submit(build_fast) + f_normal = pool.submit(build_normal) + fast = f_fast.result() + normal = f_normal.result() + build_s = round(time.perf_counter() - wall0, 3) + + s = session() + assert_associated_to_req(s, normal["delivery"]["id"], normal["req"]["id"], "normal.delivery") + assert_associated_to_req(s, fast["delivery"]["id"], fast["req"]["id"], "fast.delivery") + + def assert_sub(delivery_id: str, child_id: str, label: str) -> None: + rows = api( + s, + "GET", + f"https://devops.aliyun.com/projex/api/workitem/v2/workitem/{delivery_id}/relation/workitem/list/by-relation-category?category=PARENT_SUB&isForward=true", + ).get("result") or [] + ids = {r.get("identifier") for r in rows} + if child_id not in ids: + raise RuntimeError( + f"{label} 未出现在交付子项:期望 {child_id},实际 {[r.get('serialNumber') for r in rows]}" + ) + + assert_sub(normal["delivery"]["id"], normal["analysis"]["id"], "normal.analysis") + assert_sub(normal["delivery"]["id"], normal["design"]["id"], "normal.design") + assert_sub(fast["delivery"]["id"], fast["design"]["id"], "fast.design") + verify = { + "normal_req": get(s, normal["req"]["id"])["status"]["displayName"], + "fast_req": get(s, fast["req"]["id"])["status"]["displayName"], + "normal_delivery_assignee": ( + get(s, normal["delivery"]["id"]).get("assignedTo") or {} + ).get("displayName"), + "fast_delivery_assignee": (get(s, fast["delivery"]["id"]).get("assignedTo") or {}).get( + "displayName" + ), + "delivery_associated_ok": True, + "stage_tasks_as_sub_ok": True, + "fast_design_associated_ok": fast.get("design_associated_ok"), + } + out = { + "normal": normal, + "fast": fast, + "verify": verify, + "auth_elapsed_s": auth_s, + "wall_elapsed_s": build_s, + "created_at": datetime.now(TZ).isoformat(), + "mode": "live_create_fast_v5_fast_track_rules", + "opts": { + "create_includes_plan_start": True, + "skip_plan_end_on_create": True, + "tracked_transit_no_get": True, + "delivery_assignee_hefei_at_create": True, + "overlap_tag_with_create": True, + "fast_req_hours": f"{FAST_EST}+{FAST_ACT}", + "relation": "delivery ASSOCIATED→req; analysis/design TASK_SUB→delivery; design post ASSOCIATED→req", + "http": "requests.Session keep-alive", + }, + } + Path("/tmp/yunxiao_pmapp_fast_v2_result.json").write_text( + json.dumps(out, ensure_ascii=False, indent=2) + ) + print(json.dumps(out, ensure_ascii=False, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/.cursor/skills/YunxiaoPM/model.md b/.cursor/skills/YunxiaoPM/model.md new file mode 100644 index 0000000..a59f700 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/model.md @@ -0,0 +1,60 @@ +# 交付树模型与关联约定 + +## 真相源 + +```text +需求状态 = 阶段看板唯一真相(分析中/设计中/待开发…) +【交付】任务 = 该需求的交付容器(每需求最多 1 条) +【分析】/【设计】 = YunxiaoPMapp 在交付下建的阶段子项 +【开发】/【测试】 = 另属开发 Skill / 测试 Skill,不进本包 +ASSOCIATED = 横向挂钩(【交付】 ↔ 需求) +SUB / TASK_SUB = 纵向拆解(交付 → 分析/设计) +``` + +## 关系验收 + +| 关系 | 用在哪里 | 验收 | 优先级 | +|---|---|---|---| +| ASSOCIATED | **仅【交付】** ↔ 需求 | 交付详情「关联项」能看到需求(**强制**) | 交付必过 | +| TASK_SUB | 【分析】/【设计】 → 【交付】 | 交付详情「子项」能看到阶段任务(**强制**) | 阶段任务必过 | + +建单规则(Cookie 路径 · 同一 create 只能一条 `createWorkitemRelationInfo`): + +| 对象 | `createWorkitemRelationInfo` | 另写字段 | +|---|---|---| +| 【交付】 | `ASSOCIATED` → 需求 | — | +| 【分析】/【设计】 | `TASK_SUB` → 交付 | `parent` + `parentIdentifier` = 交付 | + +**禁止**对分析/设计只写 `parentIdentifier` + `ASSOCIATED→需求`:关联项可能有,但交付「子项」仍为空(ONEOS-246/247 已复现)。 +勿用 `PARENT` 冒充关联项。细则见 [live-api.md](live-api.md)。 + +## 禁止 + +| 禁止 | 说明 | +|---|---| +| 创建或描述【开发】/【测试】 | 口令与回报均不出现 | +| 分析/设计未挂成交付子项(无 TASK_SUB) | 交付「子项」为 0,交棒验收失败 | +| 用任务名称/标题做唯一性或查重 | 不得 `subject` 匹配复用 | +| 同一需求认定多个交付任务编号 | 列出编号请人合并后再继续 | +| 功能模块标签用「分析/设计/交付」 | 模块标签打在需求(可选交付)上 | + +## 任务标题(仅展示) + +- 【交付】+ 需求标题 +- 【分析】+ 需求标题 +- 【设计】+ 需求标题 + +标题可改,**不参与查重**。 + +## 任务编号唯一性 + +**唯一允许的查重/复用渠道:云效任务编号**(如 `ONEOS-99` / `serialNumber`)。 + +| 场景 | 正确 | 错误 | +|---|---|---| +| 首次创建【交付】 | 建单成功后回报并写入「工作项编号(系统)」 | 下次用标题搜 | +| 复用【交付】 | 口令或编号区块带 `交付任务=ONEOS-xx` | list 后按 subject 相等 | +| 判断是否已有交付 | 编号区块或交付 ASSOCIATED 编号集合 | 数【交付】开头标题 | +| 分析/设计幂等 | 仅已登记编号 | 按【分析】+需求标题 | + +按编号命中则复用;**计划开始已有值禁止改写**。 diff --git a/.cursor/skills/YunxiaoPM/references/acceptance.md b/.cursor/skills/YunxiaoPM/references/acceptance.md new file mode 100644 index 0000000..d677730 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/references/acceptance.md @@ -0,0 +1,49 @@ +# 验收清单与回报 + +## apply 后必须自检 + +| # | 项 | 通过标准 | +|---|---|---| +| 1 | 需求编号 | 回报含 ONEOS-xx | +| 2 | 任务编号 | 交付/分析/设计凡新建或操作均回报编号;禁止只报标题 | +| 3 | 编号区块 | 需求「工作项编号(系统)」与实际一致 | +| 4 | ASSOCIATED | **仅交付**详情关联项可见需求(`createWorkitemRelationInfo=ASSOCIATED`;禁止 PARENT 冒充) | +| 5 | SUB | **必过**:分析/设计 create 用 `TASK_SUB→交付`(含 `parent`+`parentIdentifier`);交付「子项」tab 须可见。与 ASSOCIATED 同 create 互斥,阶段任务关联项可空(见 live-api.md) | +| 6 | 计划开始 | 未误覆盖已有值 | +| 7 | 阶段日历工时 | 有计划完成时已写入;脚注存在;回报用语正确 | +| 8 | 交棒 | 需求=待开发;交付负责人=何斐(交棒场景) | +| 9 | 占位风险 | 交付仍占位时首行标红 | +| 10 | 负向 | 本轮**无**【开发】/【测试】任务;未按标题查重 | + +设计完成额外:AutoPRD 段已写;交付非占位(除非失败停下);ZIP+截图已挂或已列出缺项。 + +迭代额外:只挂【交付】(需求不挂);版本号符合递增规则。 + +## 回报模板(精简) + +```text +【YunxiaoPM】 +风险:(若有占位交棒则首行标红) +需求:ONEOS-xx | 状态=… +交付:ONEOS-a | 负责人=… +分析:ONEOS-b | …(无则写无) +设计:ONEOS-c | …(无则写无) +阶段日历工时:分析 Hh / 设计 Hh(若本轮写入) +附件:…(若本轮) +迭代:…(若本轮) +下一步:请技术经理使用开发 Skill(交棒后) +``` + +## 适合全自动 vs 人工门禁 + +| 适合全自动 | 建议半自动/人工门禁 | +|---|---| +| 建单、打标签、挂迭代、写描述、建交付树、改状态、交棒负责人、幂等查重 | **PJ 云效项目点选**、受理(已确认)、是否快轨、设计完成是否真可开发、跨需求优先级与迭代容量、回退与取消 | + +## 实写性能(2026-07-23 复盘) + +| 项 | 要求 | +|---|---| +| 改状态 | 只用 `status/transit`(见 [live-api.md](live-api.md)) | +| 改【交付】负责人 | 只用 `PATCH …/{id}` + `propertyKey=assignedTo` | +| 极速复测 | `scripts/live_create_fast.py`;默认不开浏览器 | diff --git a/.cursor/skills/YunxiaoPM/references/commands.md b/.cursor/skills/YunxiaoPM/references/commands.md new file mode 100644 index 0000000..571bd06 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/references/commands.md @@ -0,0 +1,22 @@ +# 口令面 + +```text +记录需求:…;项目=(必选·Plan 点选);优先级=紧急|高|中|低;标签=…;提交部门=…;提交人=…;推进至=暂不推进|已确认|分析中|设计中|设计完成|待开发|待开发(快轨) +受理确认:ONEOS-xx +开始分析:ONEOS-xx → 【交付】+【分析】并回报编号 +开始设计:ONEOS-xx;交付任务=…;分析任务=… → 【设计】+收口分析 +设计完成:ONEOS-xx;设计任务=…;原型=… +交棒开发:ONEOS-xx;交付任务=… → 待开发 + 该编号负责人=何斐 +快轨待开发:ONEOS-xx → 待开发 +【交付】+【设计】(无【分析】)+ 交付负责人=何斐 +编号直推:分析任务=ONEOS-b / 设计任务=ONEOS-c → 收口未完成计划完成 + 需求待开发 + 交付交棒何斐 +创建迭代:版本类型=主|副|子;交付任务=ONEOS-a,ONEOS-b,…;名称前缀=… +AutoRDO:…(粘贴聊天/附录音)→ 再记录需求 +``` + +版本自动规则:取同前缀下最大 `Vx.y.z`(旧 `Vx.y` 视为 `Vx.y.0`)再按主/副/子递增;迭代名=`{前缀}{新版本}`。 + +**PJ 项目**:新建需求/迭代前须点选云效项目(实时列表);禁止默认直指。见 [project-selection.md](project-selection.md)。 + +后续推进口令**优先显式带任务编号**;未带则读「工作项编号(系统)」;仍无则询问;**禁止按标题补全**。 + +YunxiaoPM 回报止于交棒;可一句「请技术经理使用开发 Skill」。 diff --git a/.cursor/skills/YunxiaoPM/references/compact-select.md b/.cursor/skills/YunxiaoPM/references/compact-select.md new file mode 100644 index 0000000..03756b8 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/references/compact-select.md @@ -0,0 +1,95 @@ +# 压缩点选(`1a2b3a4d`) + +记录需求 Plan **默认**用编号题 + 字母选项;用户可用一行压缩答复确认。 + +## Plan 展示模板 + +```text +请回复压缩点选,例:1a2b3a4d +(数字=题号,字母=选项;大小写不敏感;可写 1A2B3A4D) + +1. 类型 + A. 新增 + B. 优化 + +2. 项目(云效实时列表;建议项可标★) + A. 01_ONEOS(ONEOS)★ + B. 02_小羚羚APP(XLLAPP) + C. … + +3. 优先级 + A. 紧急 + B. 高 + C. 中 + D. 低 + +4. 标签(云效候选;建议项可标★) + A. 故障管理★ + B. 还车应结款 + C. … +``` + +可选续题(口令已齐则可写入清单、压缩串可不含): + +```text +5. 推进至 … +6. 迭代 … +``` + +## 解析规则 + +| 规则 | 说明 | +|---|---| +| 形态 | `(题号)(字母)` 连续拼接,如 `1a2b3a4d` | +| 题号 | `1`=类型 `2`=项目 `3`=优先级 `4`=标签;可扩展 `5` `6` | +| 字母 | `a`→第 1 项 … `z`→第 26 项;超出选项数 → 非法,停 | +| 缺题 | 该题若口令已唯一预填且列表唯一命中,可用预填;否则停并追问缺题 | +| 多答同题 | 后写覆盖先写 | +| 非法字符 | 停,重贴模板 | + +解析成功后 Plan 回显人话确认一行,例如: + +```text +已解析 1a2b3a4d → 类型=新增;项目=02_小羚羚APP;优先级=紧急;标签=(D 项名) +确认后回复「执行」 +``` + +用户再回「执行」才 apply(仍守 Plan 门禁)。 + +## 与口令预填 + +口令已写 `类型:新增;项目:01_ONEOS;优先级:高;标签:故障管理` 时: + +- 对应选项标 ★,并给出**建议压缩串**(如 `1a2a3b4a`),用户可直接改字母或整段重答。 +- **不得**因有预填而跳过展示字母表;压缩确认(或逐题点选)仍是门禁。 + +## 标签未命中 → 自动重拉一次候选并重生选项 + +「标签无法对应」:口令标签名在**当前标签候选**中 0 命中(或多命中无法唯一)。 + +| 步骤 | 动作 | +|---|---| +| 1 | **自动重拉一次**标签候选(见下「标签候选来源」;强制刷新,不用旧缓存) | +| 2 | 用新列表**重新生成 4.A/B/C…** 选择项 | +| 3 | 回报:`已自动重拉标签列表(因无法对应)`;请用户重答 `4x` 或整串 | +| 4 | 仍无法对应 → 停;**禁止**第 3 次空转;**禁止**猜 tagId | + +> 说明:此处重拉的是**标签候选列表**,不是项目列表。项目无法对应仍走 [project-selection.md](project-selection.md) 的「自动重拉一次」。 + +### 标签候选来源(当前已验证路径) + +1. `runtime-ids.json` → `tags` +2. 目标项目(或预填项目)下近期工作项 `tag` 字段聚合去重 +3. 重拉 = 再请求工作项列表聚合 + 合并 runtime;命中后可回写 `tags` 缓存 + +(专用 `tag/list` Cookie API 尚未稳定;有稳定端点后改写入 `live-api.md` 并切换。) + +## 项目列表顺序 + +生成 2. 选项时:口令预填/建议项目置 **A** 并标 ★,其余按云效返回顺序接 B/C/…。 + +## Agent 义务 + +1. 记录需求进 Plan 时必须输出本模板(至少 1–4 题)。 +2. 收到压缩串先解析回显,再等「执行」。 +3. 标签/项目无法对应:各自动重拉**一次**并刷新对应题选项。 diff --git a/.cursor/skills/YunxiaoPM/references/description-split.md b/.cursor/skills/YunxiaoPM/references/description-split.md new file mode 100644 index 0000000..6683bb5 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/references/description-split.md @@ -0,0 +1,69 @@ +# 需求描述 vs 交付描述 + +两类描述职责不同;禁止混写;禁止建【交付】时把 AutoPRD 六大块提前塞进交付描述。 + +## A. 需求描述 · AutoRDO 清洗 + +| 项 | 规则 | +|---|---| +| 触发 | 对话建需求 / 碎片材料入库 | +| 调用 | **必须**使用独立 Skill「**`$AutoRDO`**」(`AutoRDO/SKILL.md`;本 Skill 不内嵌清洗细则) | +| 输入 | 聊天记录、录音、口述材料 | +| 处理 | 保留原意;书面化;去口头禅;**去除结尾句号**(细则在 AutoRDO `references/rules.md`) | +| 输出 | 写入 `## 原始诉求(AutoRDO)` | +| 不做 | 本阶段不写 AutoPRD 六大块;不要求已有原型;不覆盖已有「产品说明」 | + +口令:`AutoRDO:…` → 确认后 `记录需求:【新增】标题;描述=整理稿;推进至=…` + +## B. 交付描述 + +### B1. 标准路径(分析中起建交付) + +| 时机 | 【交付】描述 | +|---|---| +| 首次创建起至设计完成前 | 固定文案:`等待设计任务完成后自动填入` | +| 设计完成时 | 用 AutoPRD「产品说明」正文替换占位 | + +### B2. 无单快轨建交付(覆盖 B1) + +| 口令/材料 | 【交付】描述 | +|---|---| +| 手工描述(无指定原型) | **同步需求**当前手工/AutoRDO 正文;禁止占位 | +| 指定原型页面 | `$oneos-autoprd` 从原型生成写入;禁止占位 | +| 既无正文又无原型 | 才允许占位并标红风险 | + +快轨【设计】描述始终复制需求当前描述正文(见 [fast-track.md](fast-track.md))。 + +## C. 需求描述双段模板(不可互相覆盖) + +```markdown +## 原始诉求(AutoRDO) +(清洗稿;设计完成也不删除) + +## 产品说明(AutoPRD) +(六大块+对象存储链接;未设计完成前可无此节或写「待设计完成后填入」) + +## 工作项编号(系统) +- 交付:… +- 分析:… +- 设计:… +``` + +## D. 设计完成 · AutoPRD + 附件 + +前提:设计任务完成且已关联对应原型页。 + +1. 调用 **`$oneos-autoprd`(AutoPRD)**,产出并落盘 `.spec/requirements-prd.md` + 标注同步: + - 对象存储预览链接:`{baseUrl}/{prototype-id}/index.html`(禁止加 `prototypes/` 前缀、禁止去掉 `index.html`) + - 产品说明 Markdown(总览/角色/流程/状态/风险/交付等,见 AutoPRD 模板) +2. 写入需求 `## 产品说明(AutoPRD)`;**禁止覆盖** `## 原始诉求(AutoRDO)` / `## 工作项编号(系统)`。 +3. 【交付】描述:按**交付任务编号**用产品说明正文**替换**占位(细则见 AutoPRD `references/yunxiao-delivery-sync.md`);可附「原始诉求见需求描述」。**创建【交付】时不得提前灌 MD。** +4. 附件(需求 +【交付】均挂;失败则不得声称成功): + - Make「导出 HTML(含源码)」ZIP → [make-export-attach.md](make-export-attach.md) + - Make「复制截图」全交互页 → 同上 + +缺原型 / AutoPRD 失败 / 导出或截图失败 → **不得**报到设计完成并宣称附件齐全;停下并列出缺项。 + +执行顺序:先 AutoPRD 落盘与附件就绪 → 再改需求/交付描述与设计计划完成 → 最后改需求状态。 + +**禁止**加载 `yunxiao-requirement-lifecycle`;阶段任务树只由本 Skill(YunxiaoPMapp)创建。 diff --git a/.cursor/skills/YunxiaoPM/references/fast-track.md b/.cursor/skills/YunxiaoPM/references/fast-track.md new file mode 100644 index 0000000..4ae6a81 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/references/fast-track.md @@ -0,0 +1,47 @@ +# 无单快轨到待开发 + +适用:口令明确「快轨」或「推进至待开发」且当前需求尚无标准分析路径任务(或用户确认跳过分析)。 + +## 动作表 + +| 对象 | 动作 | +|---|---| +| 需求 | 状态 → **待开发**;**预计工时=2**、**实际工时=2**(覆盖同日 8h 日历工时默认);标签按口令;更新编号区块 | +| 【交付】 | 无则新建;ASSOCIATED→需求(勿用 PARENT);负责人→何斐;**计划开始=创建当日**(`79`);**不写**计划完成;**描述双路径**(见下);**标签与需求相同**;更新编号区块 | +| 【设计】 | **必须新建**;**TASK_SUB→交付**(交付「子项」须可见);**描述=复制需求当前描述正文**;计划开始/完成均=当日(create 后 `field/value` 写 `79`+`80`);任务→完成态;**标签与需求相同**;建后再尝试 **ASSOCIATED→原始需求**(Cookie 常失败,见 live-api;失败须标红,**子项优先于关联项**);更新编号区块 | +| 【分析】 | **禁止创建** | + +## 交付描述双路径(A1) + +| 口令/材料 | 【交付】描述 | +|---|---| +| **手工描述**(无指定原型) | 同步需求当前手工/AutoRDO 正文(与设计一致可复制 document) | +| **指定原型页面** | 调用 `$oneos-autoprd`,从原型生成 AutoPRD「产品说明」写入【交付】 | +| 既无手工可同步正文、又无原型 | 才允许占位 `等待设计任务完成后自动填入`,Plan 勾选交棒占位风险,回报首行标红 | + +有手工或原型时**禁止**交棒占位。 + +## AutoPRD + +- 快轨默认可不跑 AutoPRD;**例外**:口令指定原型 → 交付走 AutoPRD 路径(上表)。 +- 口令同时「设计完成 + 原型」时仍按步骤 4 灌需求产品说明段。 + +## 查重 + +只认任务编号。已有分析单不得因「快轨」被删除。 + +## 回报必含 + +- 需求 / 交付 / 设计编号 +- 「快轨:未建分析;设计已同步需求描述并当日收口」 +- 交付描述来源:手工同步 / AutoPRD / 占位(若占位则首行标红) +- 需求预计工时=2、实际工时=2 +- 设计关联项含需求编号;交付子项含设计编号 + +## 与标准 / 编号直推对比 + +```text +标准:分析中→交付+分析 → 设计中→设计+收口分析 → 设计完成→收口设计 → 待开发交棒 +快轨(无既有阶段单):→ 待开发:新建交付+设计+交棒何斐(无分析;设计当日收口;需求工时 2+2) +编号直推:已有分析/设计编号 → 收口空计划完成 → 待开发+交付交棒(不新建冗余单) +``` diff --git a/.cursor/skills/YunxiaoPM/references/handoff-and-rollback.md b/.cursor/skills/YunxiaoPM/references/handoff-and-rollback.md new file mode 100644 index 0000000..44edcf6 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/references/handoff-and-rollback.md @@ -0,0 +1,20 @@ +# 交棒门禁与回退最小集 + +## 交棒门禁(待开发) + +| 情形 | 规则 | +|---|---| +| 【交付】描述已是 AutoPRD 正式说明(非占位) | 允许交棒;Plan 正常列交付任务编号 + 负责人→何斐 | +| 【交付】描述仍为 `等待设计任务完成后自动填入` | **仍允许交棒**,但 Plan **必须**勾选风险项「交付描述仍为占位,技术侧仅可凭需求 AutoRDO 稿理解」;回报**首行标红**该风险;**禁止**静默交棒假装材料齐全 | +| 口令同时带原型且要求设计完成 | 先走步骤 4(AutoPRD+附件)成功,再交棒,不再勾占位风险 | + +## 回退 / 变更(最小集 · 兼容计划开始不篡改) + +| 场景 | 规则 | +|---|---| +| 待开发 → 退回设计中 | 需求状态回退;【交付】负责人可改回创建人/产品;**交付计划开始不改**;若需重做设计 → **新开**设计任务编号(旧设计保持已收口,不改其计划开始);更新「工作项编号(系统)」中的设计编号为新号 | +| 设计完成后需求大变 | 不自动改状态;Plan 询问是否回退设计中并新开设计编号;更新 AutoPRD 段;AutoRDO 段保留并追加「变更纪要」 | +| 取消需求 | 关联任务标取消/废止;不删编号区块;不改已写计划开始 | +| 度量含义 | 交付计划开始保留 = 全周期仍从首次开工起算;回退重做会拉长日历工时——回报注明「含回退重做」 | + +凡写云效的回退仍须走 Plan 门禁。 diff --git a/.cursor/skills/YunxiaoPM/references/handoff-contract.md b/.cursor/skills/YunxiaoPM/references/handoff-contract.md new file mode 100644 index 0000000..ab9c712 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/references/handoff-contract.md @@ -0,0 +1,14 @@ +# 交接契约(开发 Skill 入口) + +YunxiaoPMapp 与开发 Skill **不要**互相 include 全文;仅认下列契约。 + +```text +PM 完成交棒 → 需求=待开发;【交付】任务编号=ONEOS-xx;负责人=何斐;ASSOCIATED 需求 +若交付描述仍为占位 → 回报已标红风险 +开发 Skill 入口 → 认「需求编号 + 交付任务编号」 +占位时只可信需求「原始诉求(AutoRDO)」并有权要求产品补设计完成 +``` + +共享常量(项目 ID、何斐 ID、节假日日历)可引用本 Skill 的 `assets/` 短路径,勿加载整份对方规则。 + +测试 Skill 另开;本契约不覆盖提测/缺陷。 diff --git a/.cursor/skills/YunxiaoPM/references/live-api.md b/.cursor/skills/YunxiaoPM/references/live-api.md new file mode 100644 index 0000000..f2bab69 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/references/live-api.md @@ -0,0 +1,242 @@ +# 云效实写 API(YunxiaoPMapp 已验证) + +`verified_at`: 2026-07-25 · **项目须门禁 PJ 点选**(见 [project-selection.md](project-selection.md));历史验证样本项目为 `01_ONEOS` / 原「统一运营管理平台」(`last_selected.spaceIdentifier` 见 `assets/runtime-ids.json`,禁止未点选即使用)。 + +本文件只记**已跑通**的写法;禁止再盲试 `updateStatus` / 错误 `updateFieldValue` POST。 + +## 认证 + +- Cookie:Chrome 域 `.aliyun.com` / `devops.aliyun.com`(`browser_cookie3` 或 Playwright storage) +- Header:`x-xsrf-token` = cookie `XSRF-TOKEN`(URL 解码后) +- `Origin` / `Referer`:`https://devops.aliyun.com` + +## 建单 + +`POST|PUT /projex/api/workitem/workitem?_input_charset=utf-8` + +创建响应 `result.identifier` / `serialNumber` 即编号真相;**禁止按标题查重**。 + +## 改负责人(已通) + +```http +PATCH /projex/api/workitem/workitem/{id}?_input_charset=utf-8 +{"propertyKey":"assignedTo","propertyValue":"","operateType":"COVER"} +``` + +交棒:【交付】`propertyValue` = 何斐 ID。 + +## 打标签(已通) + +```http +PATCH /projex/api/workitem/workitem/{id}?_input_charset=utf-8 +{"workitemIdentifier":"{id}","propertyKey":"tag","propertyValue":"[,]","operateType":"COVER"} +``` + +## 改状态(已通 · 唯一推荐) + +```http +POST /projex/api/workitem/workitem/{id}/status/transit?_input_charset=utf-8 +{"fromStatus":"<当前status.identifier>","toStatus":"<目标status.identifier>"} +``` + +成功:`code=200` 且 `result=true`。失败时 `errorMsg` 含「不能流转」。 + +### 需求状态 ID(本项目) + +| 显示名 | identifier | +|---|---| +| 待处理 | `100005` | +| 已确认 | `32` | +| 分析中 | `154395` | +| 设计中 | `156603` | +| 设计完成 | `307012` | +| 待开发 | `1582fc929d429111b925309493` | + +### 任务状态 ID + +| 显示名 | identifier | +|---|---| +| 待处理 | `100005` | +| 已完成 | `100014` | + +### 极速交棒跳转(工作流允许) + +从「待处理」菜单可见直达「设计完成」;推荐最少跳: + +```text +待处理 → 设计完成 → 待开发 +``` + +标准路径若需看板留痕,可走完整链:已确认→分析中→设计中→设计完成→待开发(仍用本 API,勿开 UI)。 + +### 禁止(已证伪) + +| 写法 | 结果 | +|---|---| +| `PATCH …/updateStatus` + `statusIdentifier` | `400 不能为空` | +| `PATCH …/{id}` + `propertyKey=status` | `property not found` | +| Playwright 点左侧/列表上的状态色块(`x<1100`) | 假成功、状态不落库 | + +仅当 `status/transit` 不可用时,才用 UI:右侧详情状态钮(`getBoundingClientRect().x > 1100`)+ `.next-menu-item`。 + +## 计划开始/完成 · 提交部门/人 · 预计工时(2026-07-27 修订) + +**通用字段写入(已通):** + +```http +POST /projex/api/workitem/workitem/field/value/{workitemId}?_input_charset=utf-8 +Content-Type: application/x-www-form-urlencoded + +fieldValueList=[{"fieldIdentifier":"79","value":"2026-07-27 12:00:00"},{"fieldIdentifier":"3132597a9718d1c282b7ba5a0c","value":"业务管理部"},{"fieldIdentifier":"9e01269e96f91fbb97d36bf5b3","value":"何苗苗"}] +``` + +| 字段 | fieldIdentifier | value | +|---|---|---| +| 计划开始 | `79` | `YYYY-MM-DD HH:mm:ss`(推荐正午)或 epoch ms 字符串 | +| 计划完成 | `80` | 同上 | +| 提交部门 | `3132597a9718d1c282b7ba5a0c` | 纯文本 | +| 提交人 | `9e01269e96f91fbb97d36bf5b3` | 纯文本 | + +**预计工时 `101586`:禁止直接改字段**(报「不可直接修改」)。须登记: + +```http +POST /projex/api/workitem/workitem/time/estimate?_input_charset=utf-8 +{"workitemIdentifier":"","spentTime":8,"type":"develop","description":"阶段日历工时","recordUserIdentifier":"","forCreate":false,"containsRestDay":false} +``` + +删除多余预估:`DELETE /projex/api/workitem/workitem/time/estimate/{workitemId}/{estimateId}` +列表:`GET …/time/estimate/list?workitemIdentifier=` + +旧写法 `PATCH …/updateWorkitemFieldValue` 对上述自定义字段常 `400 不能为空`,勿再优先使用。 + +## 父子 / 子项 / 关联项(已通 · 2026-07-23 修订 · 子项优先) + +### 关联项(【交付】强制 · ASSOCIATED) + +任务详情「关联项」只认 `ASSOCIATED`。**仅【交付】**建单时 `createWorkitemRelationInfo` 必须指向**需求**: + +```json +{ + "createWorkitemRelationInfo": { + "relatedWorkitemIdentifier": "<需求id>", + "relatedToRelationIdentifier": "ASSOCIATED" + } +} +``` + +校验: + +```http +GET /projex/api/workitem/v2/workitem/{交付id}/relation/workitem/list/by-relation-category?category=ASSOCIATED&isForward=true +``` + +`result` 含该需求即通过。 + +### 禁止(关联项) + +| 写法 | 结果 | +|---|---| +| `relatedToRelationIdentifier=PARENT` 把交付挂需求 | 详情可能有 parent,**关联项仍为空** | +| 分析/设计只用 `ASSOCIATED→需求` + `parentIdentifier` | 关联项可能有,**交付子项仍为空**(ONEOS-246/247) | +| 建后再 `POST …/relation/record` 补关系 | Cookie 路径下常报「不能关联相同的工作项」 | +| `createWorkitemRelationList` | 不落 ASSOCIATED | + +### 子项(【分析】/【设计】强制 · TASK_SUB) + +「子项」页读 `PARENT_SUB` / `TASK_SUB`,分析/设计**必须**: + +```json +{ + "parent": "<交付id>", + "parentIdentifier": "<交付id>", + "createWorkitemRelationInfo": { + "relatedWorkitemIdentifier": "<交付id>", + "relatedToRelationIdentifier": "TASK_SUB" + } +} +``` + +同一 create 只能带一条 `createWorkitemRelationInfo`。 +`ASSOCIATED→需求` 与 `TASK_SUB→交付` **不能同时写**。 + +**产品优先级:交付「子项」tab > 阶段任务「关联项」。** +交付本身仍必须 `ASSOCIATED→需求`。标准路径下分析/设计的「关联项」允许为空。 + +**无单快轨例外(设计双挂):** create 用 `TASK_SUB→交付` 后,须再补 **ASSOCIATED→原始需求**,使设计详情「关联项」可见需求。 + +```http +POST /projex/api/workitem/workitem/{设计id}/relation/record?_input_charset=utf-8 +{"relationIdentifier":"ASSOCIATED","toWorkitemIdentifier":"<需求id>"} +``` + +**已证伪(Cookie · 2026-07-27):** 凡工作项**已创建**后再 `relation/record` 补挂(含 ASSOCIATED / TASK_SUB),常报「不能关联相同的工作项」;与是否已有父项无关。`createWorkitemRelationList` 亦不落 ASSOCIATED。 +**可行路径:** + +| 目标 | 做法 | +|---|---| +| 交付「子项」可见设计(优先) | create 带 `TASK_SUB→交付` | +| 设计「关联项」可见需求 | create 带 `ASSOCIATED→需求`(与上互斥,同 create 只能一条) | +| 双挂 | 需个人 `x-yunxiao-token` OpenAPI;Cookie 路径**不得**声称成功 | + +产品默认:**子项优先**;关联项失败须在回报中标红并列出缺项。 + +校验子项: + +```http +GET /projex/api/workitem/v2/workitem/{交付id}/relation/workitem/list/by-relation-category?category=PARENT_SUB&isForward=true +``` + +结果须含对应分析/设计 identifier。 + +校验设计关联项: + +```http +GET /projex/api/workitem/v2/workitem/{设计id}/relation/workitem/list/by-relation-category?category=ASSOCIATED&isForward=true +``` + +`result` 须含原始需求 identifier。 + +### 无单快轨字段默认(2026-07-27) + +| 对象 | 规则 | +|---|---| +| 【设计】描述 | 复制需求 document HTML | +| 【设计】`79`/`80` | 当日 `12:00:00` / `23:59:59`,create 后 `field/value`(勿 create 同时带 79+80) | +| 【交付】描述 | 手工同步需求正文,或原型→AutoPRD;禁止无故占位 | +| 【交付】`79` | 创建当日;不写 `80` | +| 【交付】/【设计】标签 | 与需求相同,`PATCH propertyKey=tag` | +| 需求预计工时 | `time/estimate` **spentTime=2**(先删多余预估) | +| 需求实际工时 | `POST …/workitem/time`,body 用 **`actualTime`**(非 spentTime)+ `gmtStart`/`gmtEnd` epoch ms 字符串;见 `runtime-ids.json` `fields.actual_hours` | +| 描述更新 | `PATCH …/workitem/{id}/document`,`{"content":"","formatType":"RICHTEXT"}` | + +## 迭代挂接(已通 · 2026-07-27 · 只挂交付) + +创建迭代:`POST /projex/api/workspace/sprint`(必填 `staffIds`;可写 `capacityHours`)。 + +挂【交付】到迭代: + +```http +PATCH /projex/api/workitem/workitem/{交付id}?_input_charset=utf-8 +{"workitemIdentifier":"{交付id}","propertyKey":"sprint","propertyValue":"{sprintId}","operateType":"COVER"} +``` + +清空误挂(如需求):`propertyValue:""` + `operateType:"COVER"`。 + +**校验**必须读 `/extra`(详情主接口常不含 sprint 字段,禁止据此判失败): + +```http +GET /projex/api/workitem/workitem/{id}/extra?_input_charset=utf-8 +→ result.sprint[].identifier / name +``` + +产品规则:**只挂【交付】**;需求 / 分析 / 设计默认不挂(除非口令显式)。 + +### 极速建单注意 + +1. Cookie 只刷一次;全程纯 HTTP,默认**不开浏览器**。 +2. 交付建完后,【分析】与【设计】**并行**创建(均 TASK_SUB→交付);标准/快轨两树可并行。 +3. 状态用 `transit` + **本地追踪 fromStatus**(禁止每次 GET);负责人在交棒场景下**创建时即何斐**。 +4. 建单 `fieldValueList` 可带计划开始 `79`;**不要**在 create 同时写 `79+80`(同日会 400)。 +5. 标签必须 PATCH(create 带 tag 不落库);可与建子任务重叠;快轨交付/设计须与需求同标签。 +6. `requests.Session` keep-alive;**禁止**对共享 opener 加全局锁。 +7. 脚本入口:`scripts/live_create_fast.py`(v5:快轨描述/计划/标签/工时 2+2/设计 ASSOCIATED 补挂)。 diff --git a/.cursor/skills/YunxiaoPM/references/live-perf-2026-07-23.md b/.cursor/skills/YunxiaoPM/references/live-perf-2026-07-23.md new file mode 100644 index 0000000..a0f97d8 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/references/live-perf-2026-07-23.md @@ -0,0 +1,59 @@ +# 2026-07-23 真实建单复盘与极速优化 + +## 测试结论 + +标准 + 快轨均可建到「待开发」交棒;交付树、标签「故障管理」、快轨无【分析】均正确。 + +| 轮次 | 编号 | +|---|---| +| 第一轮(探测+UI) | ONEOS-141~147 | +| 极速 v1 | ONEOS-148~154 | +| 极速 v2 | ONEOS-164~177(含中间探针) | + +## 过程问题(已修) + +| # | 问题 | 根因 | 修复 | +|---|---|---|---| +| 1 | 状态改不动 / 假成功 | 误用 `updateStatus`;UI 点列表区 | `POST …/status/transit` | +| 2 | 负责人改不成 | `updateFieldValue` 错 | `PATCH …/{id}` + `assignedTo` | +| 3 | 状态 ID 不全 | 未抓网络 | 写入 `runtime-ids.json` | +| 4 | 极慢 | Playwright + 串行探测 | 纯 HTTP + 并行 | +| 5 | v1 仍偏慢 | 多余 GET、串行 PATCH 计划、交棒再改负责人 | 见下节 v2 | + +## 性能对比 + +| 口径 | 优化前(第一轮) | 极速 v1 | 极速 v2(本轮) | +|---|---|---|---| +| 会话墙钟 | ≈ **17.9 分钟** | — | — | +| 两路径并行建单墙钟 | ≈4.1 分钟自动化 | **4.4 s** | **2.2 s** | +| 标准单路径 | 失败反复 | 3.7 s | **2.2 s** | +| 快轨单路径 | 失败反复 | 3.7 s | **2.1 s** | +| 浏览器 | 多次 | 无 | 无 | + +相对第一轮会话 ≈ **490×**;相对 v1 再快约 **2×**。 + +## v2 压榨点(已落地) + +1. `status/transit` **本地追踪** `fromStatus`,跳过每次 GET。 +2. 建单 `fieldValueList` 带**计划开始(79)**;计划完成不在 create 写(同日会 400)。 +3. 【交付】创建时负责人直接 **何斐**(省 PATCH)。 +4. `requests.Session` keep-alive;**去掉全局锁**(否则并行失效)。 +5. 打标签与建子任务 **重叠**;两路径并行。 +6. 交棒后汇总用创建响应,校验 GET 移出主路径计时。 + +## 探针结论(未采用) + +| 尝试 | 结果 | +|---|---| +| create `fieldValueList` 带 tag | 建单成功但标签不落库 → 仍须 PATCH | +| create 同时写 79+80 | `400 计划…转化异常` | +| urllib 全局 lock + 单 opener | 并行被串行化,单路径 ≈4 s | + +## 仍可再压(收益变小) + +1. 标签 API 若将来支持 create 落库,可再少 2~4 次 PATCH。 +2. HTTP/2 或连接预热(DNS/TLS 复用到进程级)。 +3. 业务允许「建单即待开发」且平台支持初始状态 → 少 2 次 transit。 +4. 计划完成改为交棒后异步补写(当前故意跳过)。 + +脚本:`scripts/live_create_fast.py`(mode=`live_create_fast_v2`)。 diff --git a/.cursor/skills/YunxiaoPM/references/make-export-attach.md b/.cursor/skills/YunxiaoPM/references/make-export-attach.md new file mode 100644 index 0000000..e6bc09f --- /dev/null +++ b/.cursor/skills/YunxiaoPM/references/make-export-attach.md @@ -0,0 +1,53 @@ +# Make 导出与截图附件 + +设计完成(步骤 4)且已关联原型页时:需求与【交付】均挂附件。任一侧失败 → 不得声称设计完成附件齐全。 + +## 1. 导出 HTML(含源码)ZIP + +与 Make「发布 → 导出 HTML(含源码)」对齐。 + +优先本地 Make Admin: + +```http +GET {adminOrigin}/api/export-html?path={prototypePath}&projectId={projectId}&includeSource=true +``` + +| 参数 | 要求 | +|---|---| +| `path` | 原型路径(如 `prototypes/oneos-h5-vehicle-assets`) | +| `includeSource` | **必须** `true` | +| 响应 | ZIP(`PK` 头);文件名建议 `{prototype-id}-html-source.zip` | + +失败时:提示用户在 Make 客户端对该原型执行「发布 → 导出 HTML(含源码)」,把 ZIP 路径发回。 +**禁止**用「仅对象存储链接」冒充已附带源码包。 + +## 2. 复制截图(全交互页) + +1. Make「发布 → **复制截图**」(或等价:导出该原型主界面及所有交互页截图)。 +2. 上传到需求附件与【交付】附件。 +3. 缺页/失败 → 列出缺项,不得报成功。 + +## 3. 挂载范围 + +同一 ZIP / 同一批截图: + +1. 上传到需求 identifier +2. 上传到【交付】任务 identifier + +优先 API;未知 upload 端点时用已登录浏览器在详情页「附件」上传。 + +## 4. 与对象存储的关系 + +| 产物 | 用途 | +|---|---| +| `{baseUrl}/{id}/index.html` | AutoPRD 描述内可点预览链接 | +| 导出 ZIP(含源码) | 附件,供开发离线打开 | +| 交互页截图 | 附件,供评审/开发对照 | + +三者职责不同,不可互相替代。 + +## 负向 + +- 不得导出错误原型页(须 Plan 确认原型路径/名称)。 +- 不得把别的需求的旧 ZIP 复用到新需求。 +- 快轨交棒默认不跑本附件流程(除非口令同时设计完成+原型)。 diff --git a/.cursor/skills/YunxiaoPM/references/model.md b/.cursor/skills/YunxiaoPM/references/model.md new file mode 100644 index 0000000..a59f700 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/references/model.md @@ -0,0 +1,60 @@ +# 交付树模型与关联约定 + +## 真相源 + +```text +需求状态 = 阶段看板唯一真相(分析中/设计中/待开发…) +【交付】任务 = 该需求的交付容器(每需求最多 1 条) +【分析】/【设计】 = YunxiaoPMapp 在交付下建的阶段子项 +【开发】/【测试】 = 另属开发 Skill / 测试 Skill,不进本包 +ASSOCIATED = 横向挂钩(【交付】 ↔ 需求) +SUB / TASK_SUB = 纵向拆解(交付 → 分析/设计) +``` + +## 关系验收 + +| 关系 | 用在哪里 | 验收 | 优先级 | +|---|---|---|---| +| ASSOCIATED | **仅【交付】** ↔ 需求 | 交付详情「关联项」能看到需求(**强制**) | 交付必过 | +| TASK_SUB | 【分析】/【设计】 → 【交付】 | 交付详情「子项」能看到阶段任务(**强制**) | 阶段任务必过 | + +建单规则(Cookie 路径 · 同一 create 只能一条 `createWorkitemRelationInfo`): + +| 对象 | `createWorkitemRelationInfo` | 另写字段 | +|---|---|---| +| 【交付】 | `ASSOCIATED` → 需求 | — | +| 【分析】/【设计】 | `TASK_SUB` → 交付 | `parent` + `parentIdentifier` = 交付 | + +**禁止**对分析/设计只写 `parentIdentifier` + `ASSOCIATED→需求`:关联项可能有,但交付「子项」仍为空(ONEOS-246/247 已复现)。 +勿用 `PARENT` 冒充关联项。细则见 [live-api.md](live-api.md)。 + +## 禁止 + +| 禁止 | 说明 | +|---|---| +| 创建或描述【开发】/【测试】 | 口令与回报均不出现 | +| 分析/设计未挂成交付子项(无 TASK_SUB) | 交付「子项」为 0,交棒验收失败 | +| 用任务名称/标题做唯一性或查重 | 不得 `subject` 匹配复用 | +| 同一需求认定多个交付任务编号 | 列出编号请人合并后再继续 | +| 功能模块标签用「分析/设计/交付」 | 模块标签打在需求(可选交付)上 | + +## 任务标题(仅展示) + +- 【交付】+ 需求标题 +- 【分析】+ 需求标题 +- 【设计】+ 需求标题 + +标题可改,**不参与查重**。 + +## 任务编号唯一性 + +**唯一允许的查重/复用渠道:云效任务编号**(如 `ONEOS-99` / `serialNumber`)。 + +| 场景 | 正确 | 错误 | +|---|---|---| +| 首次创建【交付】 | 建单成功后回报并写入「工作项编号(系统)」 | 下次用标题搜 | +| 复用【交付】 | 口令或编号区块带 `交付任务=ONEOS-xx` | list 后按 subject 相等 | +| 判断是否已有交付 | 编号区块或交付 ASSOCIATED 编号集合 | 数【交付】开头标题 | +| 分析/设计幂等 | 仅已登记编号 | 按【分析】+需求标题 | + +按编号命中则复用;**计划开始已有值禁止改写**。 diff --git a/.cursor/skills/YunxiaoPM/references/number-push.md b/.cursor/skills/YunxiaoPM/references/number-push.md new file mode 100644 index 0000000..284f91d --- /dev/null +++ b/.cursor/skills/YunxiaoPM/references/number-push.md @@ -0,0 +1,35 @@ +# 编号直推到待开发 + +适用:标准路径已建过【分析】和/或【设计】,用**任务编号**一口气推到待开发交棒。 + +## 入口(只认编号) + +```text +交棒开发:需求=ONEOS-xx;交付任务=ONEOS-a;分析任务=ONEOS-b;设计任务=ONEOS-c +交棒开发:分析任务=ONEOS-b → 由分析 ASSOCIATED/SUB 反查需求与交付 +交棒开发:设计任务=ONEOS-c → 同上反查 +``` + +禁止用标题猜。编号对不上关联树 → 停下列出关联,不瞎改。 + +## 动作表 + +| 对象 | 动作 | +|---|---| +| 需求 | 状态 → **待开发**(按云效连跳) | +| 【交付】 | **必须**用交付任务编号定位;负责人→何斐;不改计划开始;不写计划完成;描述仍占位则保持(除非同时设计完成+原型+AutoPRD) | +| 【分析】 | 若有编号:计划完成若为空 → 写当日并推工时;**不新建**第二条 | +| 【设计】 | 若有编号:计划完成若为空 → 写当日并推工时;**不新建**第二条 | +| 仅有分析、尚无设计 | **不强制补建【设计】**(与无单快轨区分);回报注明「无设计任务编号」 | +| 仅有设计、无分析 | 收口设计 + 交棒;**不补建分析** | + +## 与无单快轨区别 + +| | 无单快轨 | 编号直推 | +|---|---|---| +| 前提 | 尚无分析/设计阶段单(或明确跳过分析) | 已有分析和/或设计**任务编号** | +| 【分析】 | 禁止创建 | 有编号则收口,无则不建 | +| 【设计】 | 必须新建且当日收口 | 有编号则收口;仅分析无设计时不强制新建 | +| 定位 | 新建后登记编号 | **仅任务编号**定位/反查 | + +仍不创建【开发】/【测试】。交棒占位风险见 [handoff-and-rollback.md](handoff-and-rollback.md)。 diff --git a/.cursor/skills/YunxiaoPM/references/project-selection.md b/.cursor/skills/YunxiaoPM/references/project-selection.md new file mode 100644 index 0000000..2a8e516 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/references/project-selection.md @@ -0,0 +1,73 @@ +# 门禁 PJ · 云效项目选择(强制点选) + +凡 **新建需求 / 新建任务树 / 创建迭代 / 其它写入依赖 `spaceIdentifier`** 的操作,项目必须由用户从云效实时列表**点选**。禁止静默使用 `runtime-ids.json` 里的默认 `project.spaceIdentifier`。 + +## 何时触发 + +| 场景 | 是否必须选项目 | +|---|---| +| 记录需求 / 无单快轨新建 | **必须** | +| 创建迭代(新挂项目空间) | **必须**(与需求同项目时沿用已锁定项,仍须在 Plan 写出项目名+ID) | +| 仅推进已有编号(受理确认、开始分析…) | **不必重选**;以该编号所在项目为准,Plan 写出项目名 | +| 只读查询 | 不强制 | + +## 执行顺序(建单前) + +1. **实时拉取**云效项目列表(Cookie + XSRF): + +```http +GET /projex/api/workspace/project/search/list + ?category=Project&scope=all&toPage=1&pageSize=100 + &conditions={"conditionGroups":[[]]} + &extraConditions=…(可与页面一致:当前用户参与 + public) + &orderBy={"fieldIdentifier":"gmtCreate","format":"input","order":"desc","className":"date"} + &_input_charset=utf-8 +``` + +2. 将结果整理为单选列表:`{name}({customCode})`;选项 id 用 `identifier`(spaceId)。 +3. **AskQuestion / Plan 单选**「PJ. 云效项目」;未点选 → **禁止 create**。 +4. 「批准 Plan」≠ 已选项目;Plan 里仍是空选项或写「默认 01_ONEOS」而未点选 → 停。 +5. 用户点选后:本轮 apply 全程使用该 `spaceIdentifier`;回报写清项目名 + customCode + spaceId。 +6. 拉取成功后可回写 `assets/runtime-ids.json` → `projects_catalog`(缓存);**缓存不得当作已选项**。 + +## 无法对应 → 自动重拉一次 + +「无法对应」任一成立即触发: + +| # | 情形 | +|---|---| +| 1 | 口令/预填的项目名、customCode、别名在**当前列表**中 0 命中 | +| 2 | 口令给出的 spaceId / identifier 不在当前列表 | +| 3 | 用户点选的选项 id 在 apply 前校验时已不在列表(列表过期) | +| 4 | 仅命中 `projects_catalog` 缓存、与本次实时列表不一致 | +| 5 | 首次拉取失败后改用了缓存,用户按缓存点选后 create 报项目/空间无效 | + +**动作(固定一次):** + +1. **立即再调一次**同一 `project/search/list`(强制网络,不用缓存)。 +2. 用新列表重新做匹配 / 刷新 Plan 单选选项;可回写 `projects_catalog`。 +3. 回报注明:`已自动重拉项目列表(因无法对应)`。 +4. **仍无法对应** → 停,展示最新列表请用户重选;**禁止**再自动拉第 3 次;**禁止**猜一个 spaceId 继续建单。 + +匹配规则(名/码):忽略大小写;`name` 全等或包含;`customCode` 全等;`name_aliases` 全等。多命中视为无法唯一对应 → 重拉后仍多命中则请用户点选,不自动选定。 + +## 禁止 + +- 未询问就写入 `project.spaceIdentifier`(即便 catalog 标了 `suggested`) +- 按口令里的「统一运营管理平台」字符串静默映射而不展示列表点选(口令仅作**预填建议**,仍须用户确认点选) +- API 失败时擅自沿用上次默认;应展示 `projects_catalog` 缓存并标明「离线缓存,请确认」,仍须点选;用户确认后若仍无法对应 → 走上一节「自动重拉一次」 +- 多项目并行建单却共用一个未确认的 spaceId +- 「无法对应」时循环重拉超过 1 次,或跳过点选直接建单 + +## 口令预填 + +```text +记录需求:…;项目=01_ONEOS;… +``` + +若口令已含项目名/编号前缀且在实时列表中**唯一命中**:Plan 可预勾该选项,但仍须用户确认;0 命中或多命中 → **先自动重拉一次**再匹配;仍 0/多 → 不预勾,只展示最新列表。 + +## 脚本 + +`scripts/list_projects.py`:stdout JSON `projects[]`。 +带查询时:`python3 scripts/list_projects.py --match '01_ONEOS'` —— 0 命中/多命中则自动重拉一次再匹配(`refetched: true`)。 diff --git a/.cursor/skills/YunxiaoPM/references/record-meta-fields.md b/.cursor/skills/YunxiaoPM/references/record-meta-fields.md new file mode 100644 index 0000000..90469f9 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/references/record-meta-fields.md @@ -0,0 +1,58 @@ +# 记录需求 · 元字段(优先级 / 标签 / 提交部门 / 提交人) + +建单前消费网页 / AutoRDO / 口令中的四类元信息。细则与 `assets/runtime-ids.json` 对齐;**禁止臆造未验证的 fieldIdentifier**。 + +## 口令形态 + +```text +记录需求:…;优先级=紧急|高|中|低;标签=…;提交部门=…;提交人=…;推进至=… +``` + +四字段均可选出现;网页或 AutoRDO 提示里已带的值与口令等价。 + +## Plan 是否追问 + +记录需求 **一律**走 [compact-select.md](compact-select.md) 字母表(类型/项目/优先级/标签),即使用户口令已写齐;给出建议压缩串,等用户回 `1a2b3a4d`(或改字母)并「执行」。 + +| 条件 | 行为 | +|---|---| +| 口令已含类型/项目/优先级/标签 | 对应选项标 ★ + 建议压缩串;**仍须**压缩确认或显式点选 | +| 标签名 0 命中 / 多命中 | **自动重拉一次**标签候选并重生 4.A/B/C…;仍失败则停 | +| 仅缺提交部门/提交人 | 压缩 1–4 确认后,缺则追问或写入描述页脚 | +| 仅缺「推进至」等 | 按既有口令规则;可作 5. 题字母表 | + +## 写入策略 + +### 优先级(已验证) + +- 映射:`runtime-ids.json` → `priority`(紧急 / 高 / 中 / 低)。 +- create 时写入 `fieldIdentifier: "priority"`(与 [live-api.md](live-api.md) / `live_create_fast.py` 一致)。 +- 未给优先级时:Plan 追问;脚本默认「中」仅作既有兜底,口令/网页有值时以用户值为准。 + +### 标签(已验证) + +- 映射:`runtime-ids.json` → `tags`(按显示名取 tagId)。 +- 标签须 **PATCH** `propertyKey: "tag"`(create 带 tag 不落库)。 +- 口令/网页给出的标签名若不在候选:按 [compact-select.md](compact-select.md) **自动重拉一次**标签列表并重生选项;仍无则停在 Plan,勿猜 ID。 + +### 提交部门 / 提交人(已验证 · 2026-07-27 · ONEOS-293) + +| 字段 | fieldIdentifier | +|---|---| +| 提交部门 | `3132597a9718d1c282b7ba5a0c` | +| 提交人 | `9e01269e96f91fbb97d36bf5b3` | + +| 规则 | 说明 | +|---|---| +| **写入** | `POST /projex/api/workitem/workitem/field/value/{workitemId}`,`Content-Type: application/x-www-form-urlencoded`,参数 `fieldValueList` = JSON 数组字符串 `[{"fieldIdentifier","value"}]` | +| **形态** | 均为普通文本 input(非人员选择器);口令值原样写入 | +| **页脚** | 仍可在描述页脚重复一份,便于检索;**不能替代**字段写入 | +| **计划开始** | 字段 `79`,同一 `field/value` API;推荐 `YYYY-MM-DD 12:00:00`。快轨:【交付】/【设计】创建当日写入;【设计】另写计划完成 `80`=`当日 23:59:59` | +| **预计工时** | 字段 `101586` **禁止**直接 PATCH;用 `POST …/time/estimate`(`spentTime`)登记;删多余用 `DELETE …/time/estimate/{workitemId}/{estimateId}` | +| **实际工时** | `POST …/workitem/time`,body **`actualTime`**(非 spentTime)+ `gmtStart`/`gmtEnd`(epoch ms 字符串);快轨待开发需求默认 **2** | +| **描述更新** | `PATCH …/workitem/{id}/document`,`{"content","formatType":"RICHTEXT"}` | +| **快轨标签** | 【交付】【设计】建单后 `PATCH propertyKey=tag`,`propertyValue` 与需求 tagId 一致(多标签逗号拼接) | + +## 与描述双段的关系 + +页脚元信息写在「原始诉求」段末或整篇描述末尾均可,**不得覆盖** `## 产品说明(AutoPRD)` / `## 工作项编号(系统)` 区块(见 [description-split.md](description-split.md))。 diff --git a/.cursor/skills/YunxiaoPM/references/sprint.md b/.cursor/skills/YunxiaoPM/references/sprint.md new file mode 100644 index 0000000..4506e39 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/references/sprint.md @@ -0,0 +1,63 @@ +# 创建迭代并关联交付 + +产品经理可将已交棒(或已有编号)的多条【交付】任务打进同一云效迭代。 + +## 口令 + +```text +创建迭代:版本类型=副;交付任务=ONEOS-a,ONEOS-b,ONEOS-c;名称前缀=统一运营管理平台PC端 +创建迭代:版本类型=子;交付任务=ONEOS-99;名称前缀=统一运营管理平台PC端 +``` + +| 参数 | 规则 | +|---|---| +| 交付任务 | **1..N 个任务编号**(逗号分隔);禁止用标题凑数 | +| 版本类型 | 主 / 副 / 子;未点选则停下询问,禁止默认猜 | +| 名称前缀 | 可选;缺省 `统一运营管理平台PC端`。最终名=`{前缀}{版本号}` | + +## 版本号 `V{主}.{副}.{子}` + +| 版本类型 | 含义 | 递增 | +|---|---|---| +| **主** | 功能重制 | 主+1,副→0,子→0(V1.3.2→V2.0.0) | +| **副** | 新功能上线 | 副+1,子→0(V1.3.2→V1.4.0) | +| **子** | bug/优化 | 子+1(V1.3.2→V1.3.3) | + +### 取基线再递增 + +1. 拉取当前项目云效迭代列表。 +2. 从迭代**名称**解析 `V数字.数字.数字`;旧名 `V数字.数字` 视为 `.0`。 +3. 同名称前缀内取**最大**版本为基线(主→副→子比较);避免 PC 与小程序串号。 +4. 按用户点选类型递增,写入新迭代名称。 +5. 无一可解析版本:基线 `V0.0.0` 再递增(主→V1.0.0,副→V0.1.0,子→V0.0.1);或口令显式给起始版本。 + +禁止:手填与规则冲突的版本号却声称自动生成;禁止用交付标题推断版本类型。 + +## 创建与关联 + +| 步骤 | 动作 | +|---|---| +| 1 | 算出新版本 → 拼迭代全名 | +| 2 | 创建迭代(起止日期口令未给则询问或用项目默认,禁止瞎填) | +| 3 | **只挂【交付】**:N 个交付任务挂迭代;交付失败不得报完成 | +| 4 | 幂等:同名迭代已存在 → 不新建,补挂尚未关联的交付,回报「复用迭代」 | +| 5 | 回报:迭代名、版本号、identifier、已关联交付编号、失败编号 | + +**禁止**把需求挂进迭代(需求侧「迭代」字段保持空)。不挂【分析】/【设计】除非口令显式点名。 +**不**在本步创建【开发】/【测试】。仍须 Plan 门禁。 + +### 挂接 API(已通 · 校验走 `/extra`) + +```http +PATCH /projex/api/workitem/workitem/{交付id}?_input_charset=utf-8 +{"workitemIdentifier":"{交付id}","propertyKey":"sprint","propertyValue":"{sprintId}","operateType":"COVER"} +``` + +校验(详情主接口**不**含 sprint,勿用其判空): + +```http +GET /projex/api/workitem/workitem/{id}/extra?_input_charset=utf-8 +→ result.sprint[].identifier +``` + +清空误挂的需求迭代:同上 PATCH,`propertyValue:""` + `operateType:"COVER"`(或 `CLEAR`)。 diff --git a/.cursor/skills/YunxiaoPM/references/stage-flow.md b/.cursor/skills/YunxiaoPM/references/stage-flow.md new file mode 100644 index 0000000..e782f18 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/references/stage-flow.md @@ -0,0 +1,63 @@ +# 标准路径:待处理 → 待开发交棒 + +人员/状态等常量见 [../assets/runtime-ids.json](../assets/runtime-ids.json)。**项目空间**须按 [project-selection.md](project-selection.md) 点选,禁止默认直指。查重只用任务编号。 + +## 步骤 0|创建需求 + +| 云效动作 | 落地 | +|---|---| +| 新建产品类需求 | POST 建单;标题 `【新增】`/`【优化】` | +| 需求描述 | **先** AutoRDO 清洗再写入 `## 原始诉求(AutoRDO)`;本步不写 AutoPRD | +| 默认状态 | **待处理**;不建任务、不改负责人 | +| 可选推进 | 口令点选目标状态;未选则停在待处理 | + +## 步骤 1|受理 → 已确认 + +| 云效动作 | 落地 | +|---|---| +| 状态 | 待处理 → **已确认** | +| 任务 | **仍不建**交付/分析/设计 | + +若用户只「创建」未受理:只提示「受理后请推进至已确认」,不自动跳。 + +## 步骤 2|分析中 + +| 对象 | 动作 | +|---|---| +| 【交付】 | 无则新建;标题 `【交付】`+需求标题;**ASSOCIATED→需求**(勿用 PARENT);负责人=创建人/产品;**计划开始仅空时写当日**;不写计划完成;描述=`等待设计任务完成后自动填入`;更新编号区块 | +| 【分析】 | 新建;**TASK_SUB→交付**(`parent`+`parentIdentifier`+`createWorkitemRelationInfo=TASK_SUB`);交付「子项」须可见;计划开始=当日(仅空时);负责人默认可=创建人;更新编号区块 | + +| 需求 | 状态=`分析中` | + +## 步骤 3|设计中 + +| 对象 | 动作 | +|---|---| +| 【交付】 | 已存在则按编号复用;没有则补建;**不改交付计划开始**;不写交付计划完成 | +| 【设计】 | 新建;**TASK_SUB→交付**(同分析);计划开始=当日(仅空时);更新编号区块 | +| 【分析】 | **计划完成**=状态更新日;推算阶段日历工时(见 work-hours) | +| 需求 | 状态=`设计中` | + +## 步骤 4|设计完成 + +| 对象 | 动作 | +|---|---| +| 【设计】 | 计划完成=当日;推算工时;任务→完成态 | +| 需求 | AutoPRD 写入 `## 产品说明`;挂 ZIP+截图;状态=`设计完成` | +| 【交付】 | 不换负责人;不改计划开始;不写计划完成;描述改为 AutoPRD 正文;同样挂附件 | + +顺序与失败门禁见 [description-split.md](description-split.md)。 + +## 步骤 5|待开发交棒(本 Skill 终点 · 标准路径) + +| 对象 | 动作 | +|---|---| +| 需求 | 状态=`待开发` | +| 【交付】 | 按**任务编号**定位;**负责人→何斐**;不改计划开始;不写计划完成 | +| 【分析】/【设计】 | 不新建;不改已有计划开始;前序未收口按步骤 3/4 处理 | + +执行 [handoff-and-rollback.md](handoff-and-rollback.md) §交棒门禁。 + +**不做:** 【开发】/【测试】、仓库、分支、提测。 + +快轨 / 编号直推见专文。创建迭代见 [sprint.md](sprint.md)。 diff --git a/.cursor/skills/YunxiaoPM/references/work-hours.md b/.cursor/skills/YunxiaoPM/references/work-hours.md new file mode 100644 index 0000000..b121161 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/references/work-hours.md @@ -0,0 +1,73 @@ +# 计划时间与阶段日历工时 + +适用于【交付】/【分析】/【设计】统一口径。 + +## 用途 + +| 对象 | 计划开始→计划完成 | +|---|---| +| 【交付】 | 需求开工→上线全周期(本 Skill **不**写交付计划完成;上线由后续段) | +| 【分析】 | 分析阶段时长 | +| 【设计】 | 设计阶段时长 | + +## 计划开始(不可篡改) + +| 对象 | 首次写入 | 之后 | +|---|---|---| +| 【交付】 | 首次进入分析中且字段为空 → 当日 | **禁止覆盖** | +| 【分析】 | 首次创建且为空 → 当日 | **禁止覆盖** | +| 【设计】 | 首次创建且为空 → 当日 | **禁止覆盖** | + +日期写入形态:中国时区正午 epoch ms 字符串,见 [../assets/runtime-ids.json](../assets/runtime-ids.json) `fields.plan_start`。 + +## 计划完成 + +| 环节 | 写计划完成的对象 | +|---|---| +| 推进到设计中 | 【分析】= 状态更新日 | +| 推进到设计完成 | 【设计】= 状态更新日 | +| 无单快轨建设计 | 【设计】= 当日(同轮收口) | +| 编号直推收口 | 口令涉及的分析/设计若计划完成为空 → 当日 | +| 上线/关闭 | 【交付】计划完成(**本 Skill 暂不写**) | + +## 预计工时 = 阶段日历工时(Lead Time) + +有计划完成且已有计划开始时,写入该对象云效「预计工时」: + +```text +workdays = 0 +从计划开始日到计划完成日(含首尾)逐日: + 周六或周日 → 跳过(除非该日是调休补班) + 法定放假日 → 跳过 + 调休补班日(含周末上班)→ 计入 + 其余周一~周五 → 计入 +预计工时(小时) = workdays × 8 +``` + +**禁止**用「(结束日 − 开始日 + 1)× 8」日历天数算法。 + +### 语义与脚注(强制) + +- 正式名称:**阶段日历工时**,**不是**投入人天/排期负荷。 +- 任务描述末尾固定脚注: + `【系统】预计工时=阶段日历工时(工作日×8),非人力投入预估` +- 回报写「阶段日历工时 Hh」,**禁止**写「需投入 Hh」。 +- 本 Skill **不自动写**「人力投入预估」。 + +### 日历数据源 + +- 使用 [../assets/cn-workday-calendar.json](../assets/cn-workday-calendar.json)(按自然年;跨年须覆盖起止两侧年份)。 +- 缺当年日历 → **停下**提示补日历;**禁止**静默按「仅去周末」算完并报成功。 +- 计算脚本:[../scripts/workday_hours.py](../scripts/workday_hours.py) + +```bash +python3 scripts/workday_hours.py 2026-03-06 2026-03-09 +``` + +缺计划开始则只写结束、不推工时,回报提示。 + +## 示例 + +- 开始=周五、结束=下周一(无节假日)→ 五+一 → 16h +- 区间全在法定放假内 → 0h +- 某周日为调休补班且落在区间内 → 该日计入 8h diff --git a/.cursor/skills/YunxiaoPM/references/workitem-ids.md b/.cursor/skills/YunxiaoPM/references/workitem-ids.md new file mode 100644 index 0000000..e35fb26 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/references/workitem-ids.md @@ -0,0 +1,22 @@ +# 工作项编号(系统) + +## 权威源 + +需求描述固定区块(机器可解析;人工只读即可): + +```markdown +## 工作项编号(系统) +- 交付:ONEOS-xx +- 分析:ONEOS-yy(无则写无) +- 设计:ONEOS-zz(无则写无) +``` + +## 规则 + +1. **禁止**仅靠本地会话映射当唯一真相。 +2. 每次新建/登记交付·分析·设计编号后,**立即 PATCH 更新该区块**(只改本区块,不动 AutoRDO / AutoPRD 正文)。 +3. 操作顺序:口令显式编号 > 读本区块 > ASSOCIATED/SUB 反查校验。 +4. 三者冲突 → 停下人工;**仍禁止按标题猜**。 +5. 本地缓存仅加速,启动以云效该区块为准。 +6. 区块出现两个交付编号 → 停止并列号请人合并。 +7. 取消需求:关联任务标取消/废止;**不删**本区块(留痕)。 diff --git a/.cursor/skills/YunxiaoPM/runtime-ids.json b/.cursor/skills/YunxiaoPM/runtime-ids.json new file mode 100644 index 0000000..d4579ac --- /dev/null +++ b/.cursor/skills/YunxiaoPM/runtime-ids.json @@ -0,0 +1,221 @@ +{ + "schema_version": 1, + "skill": "YunxiaoPM", + "verified_at": "2026-07-25", + "note": "YunxiaoPM constants. Project space MUST be user-selected (PJ gate); project.spaceIdentifier is last-known cache only — never auto-apply. See references/project-selection.md.", + "project": { + "selection_mode": "user_pick_required", + "name": null, + "name_aliases": [ + "01_ONEOS", + "ONEOS", + "统一运营管理平台", + "统一运营管理平台PC端", + "统一运营管理平台 PC 端" + ], + "spaceIdentifier": null, + "customCode": null, + "spaceType": "Project", + "organizationIdentifier": "697c54a19df7fdfa65466405", + "default_sprint_name_prefix": "统一运营管理平台PC端", + "list_api": "GET /projex/api/workspace/project/search/list", + "last_selected": { + "name": "01_ONEOS", + "spaceIdentifier": "1280be963a5a2cc126a4118dca", + "customCode": "ONEOS", + "note": "历史常用项;仅作预填建议,禁止未点选即使用" + }, + "refreshed_at": "2026-07-25" + }, + "projects_catalog": [ + { + "name": "05_羚牛碳资产平台", + "identifier": "ff104a3bce09463da136a97999", + "customCode": "CARBON" + }, + { + "name": "06_对外客户项目", + "identifier": "baca8b4d676c5af67e475caec0", + "customCode": "CUST" + }, + { + "name": "07_LNBOX", + "identifier": "35e1e915a052379bbdfb993aac", + "customCode": "LNBOX" + }, + { + "name": "04_AI应用", + "identifier": "d9002ea72c6c3a1a97b02be750", + "customCode": "AIAPP" + }, + { + "name": "03_数据中台", + "identifier": "a801cef5c9a68fa051c07432c7", + "customCode": "DATA" + }, + { + "name": "02_小羚羚APP", + "identifier": "db771a2cca07bed43b369af077", + "customCode": "XLLAPP" + }, + { + "name": "01_ONEOS", + "identifier": "1280be963a5a2cc126a4118dca", + "customCode": "ONEOS" + }, + { + "name": "敏捷研发示例项目", + "identifier": "65eca0c2e16a23939081e19e14", + "customCode": "DEMO" + } + ], + "people": { + "wangmian": { + "displayName": "王冕", + "identifier": "6811df000601d2fea60144a9", + "role": "产品创建人/默认负责人" + }, + "hefei": { + "displayName": "何斐", + "identifier": "695f0400562f09713f9c3a93", + "role": "待开发交棒【交付】负责人" + } + }, + "tags": { + "故障管理": "ceb526a7343995577645317e9a", + "还车应结款": "4204960ce658c94b17abb7c5f6", + "工作台": "b62d21beac55c0b43389915eab", + "交车管理": "b6947b5aca82a8c759612ba039", + "合同管理": "23873be81931cb0dc1bf87a456", + "安全培训": "76988b5f73ef515de5930d59b9", + "证照管理": "11f4c2ec65901a8f3199c81706", + "还车管理": "1e0fce2d6929ff4ac13b310e97" + }, + "status": { + "req": { + "待处理": "100005", + "已确认": "32", + "分析中": "154395", + "设计中": "156603", + "设计完成": "307012", + "待开发": "1582fc929d429111b925309493" + }, + "task": { + "待处理": "100005", + "已完成": "100014" + }, + "transit_api": "POST /projex/api/workitem/workitem/{id}/status/transit", + "fast_handoff_hops": [ + "设计完成", + "待开发" + ], + "note": "见 references/live-api.md;禁止再用 updateStatus" + }, + "workitem_types": { + "product_req": { + "name": "产品类需求", + "category": "Req", + "identifier": "9uy29901re573f561d69jn40" + }, + "task": { + "name": "任务", + "category": "Task", + "identifier": "ba102e46bc6a8483d9b7f25c" + } + }, + "priority": { + "紧急": "646004e97f54bb77fec7b455df", + "高": "95b89e0a524d9693e1f335ffe5", + "中": "fa155d1214f9f8db222d39db3b", + "低": "92924feff9c1085891e7511872" + }, + "fields": { + "plan_start": { + "fieldIdentifier": "79", + "name": "计划开始时间", + "value_shape": "YYYY-MM-DD HH:mm:ss China wall time preferred; epoch ms string also accepted", + "example": "2026-07-27 12:00:00", + "update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON" + }, + "plan_end": { + "fieldIdentifier": "80", + "name": "计划完成时间", + "value_shape": "YYYY-MM-DD HH:mm:ss or epoch ms string", + "update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON" + }, + "estimated_hours": { + "fieldIdentifier": "101586", + "name": "预计工时", + "note": "不可直接改字段;须工时预估登记", + "create_api": "POST /projex/api/workitem/workitem/time/estimate body spentTime,type,recordUserIdentifier,workitemIdentifier", + "delete_api": "DELETE /projex/api/workitem/workitem/time/estimate/{workitemId}/{estimateId}", + "list_api": "GET /projex/api/workitem/workitem/time/estimate/list?workitemIdentifier=" + }, + "actual_hours": { + "name": "实际工时", + "note": "快轨待开发默认 2", + "create_api": "POST /projex/api/workitem/workitem/time body actualTime,type,recordUserIdentifier,workitemIdentifier,gmtStart,gmtEnd (epoch ms string)", + "list_api": "GET /projex/api/workitem/workitem/time/list?workitemIdentifier=", + "fast_track_default": 2, + "verified_at": "2026-07-27", + "verified_on": "ONEOS-293" + }, + "fast_track": { + "req_estimated_hours": 2, + "req_actual_hours": 2, + "design_plan_start_end_same_day": true, + "design_description": "copy_req_document", + "delivery_description": "manual_sync_or_autoprd", + "delivery_design_same_tags_as_req": true, + "design_associated_to_req_after_task_sub": true, + "document_update_api": "PATCH /projex/api/workitem/workitem/{id}/document {content,formatType:RICHTEXT}" + }, + "submit_department": { + "fieldIdentifier": "3132597a9718d1c282b7ba5a0c", + "name": "提交部门", + "format": "string input", + "update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON", + "verified_at": "2026-07-27", + "verified_on": "ONEOS-293" + }, + "submitter": { + "fieldIdentifier": "9e01269e96f91fbb97d36bf5b3", + "name": "提交人", + "format": "string input", + "update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON", + "verified_at": "2026-07-27", + "verified_on": "ONEOS-293" + }, + "tag": { + "fieldIdentifier": "tag", + "name": "标签", + "propertyKey": "tag" + } + }, + "assignee_rules": { + "待开发_交付任务": "hefei", + "分析中_交付与分析": "creator", + "设计中_设计": "creator" + }, + "relation": { + "delivery_to_req": "ASSOCIATED", + "stage_to_delivery": "TASK_SUB", + "create_field": "createWorkitemRelationInfo", + "forbid": [ + "PARENT_as_关联项", + "ASSOCIATED_only_for_stage_tasks_without_TASK_SUB" + ], + "note": "交付必须 ASSOCIATED→需求。分析/设计默认 TASK_SUB→交付(子项 tab)。二者同 create 互斥。见 references/live-api.md" + }, + "delivery_placeholder": "等待设计任务完成后自动填入", + "task_title_prefixes": { + "delivery": "【交付】", + "analysis": "【分析】", + "design": "【设计】" + }, + "publish_url": { + "shape": "{baseUrl}/{prototype-id}/index.html", + "forbid_prototypes_prefix": true, + "require_index_html": true + } +} diff --git a/.cursor/skills/YunxiaoPM/scripts/list_projects.py b/.cursor/skills/YunxiaoPM/scripts/list_projects.py new file mode 100644 index 0000000..9a7efd2 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/scripts/list_projects.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +"""拉取云效项目列表(供 YunxiaoPMapp 门禁 PJ 点选)。stdout = JSON。 + +无法对应时自动重拉一次: + python3 scripts/list_projects.py --match '01_ONEOS' + python3 scripts/list_projects.py --match-id 1280be963a5a2cc126a4118dca +""" +from __future__ import annotations + +import argparse +import json +import urllib.parse +from datetime import datetime, timedelta, timezone +from pathlib import Path +from typing import Any + +import requests + +ROOT = Path(__file__).resolve().parents[1] +RUNTIME = json.loads((ROOT / "assets" / "runtime-ids.json").read_text()) +ORG = RUNTIME.get("project", {}).get("organizationIdentifier", "697c54a19df7fdfa65466405") +WANG = RUNTIME.get("people", {}).get("wangmian", {}).get("identifier", "6811df000601d2fea60144a9") +ALIASES = [a.lower() for a in (RUNTIME.get("project", {}).get("name_aliases") or [])] +TZ = timezone(timedelta(hours=8)) + + +def load_jar() -> dict[str, str]: + jar: dict[str, str] = {} + try: + import browser_cookie3 + + for domain in (".aliyun.com", "devops.aliyun.com", ".devops.aliyun.com"): + try: + for c in browser_cookie3.chrome(domain_name=domain): + jar[c.name] = c.value + except Exception: + pass + except Exception: + pass + if not jar.get("XSRF-TOKEN"): + p = Path("/tmp/yunxiao_cookies.json") + if p.exists(): + raw = json.loads(p.read_text()) + jar = ( + raw + if isinstance(raw, dict) and "XSRF-TOKEN" in raw + else {c["name"]: c["value"] for c in raw.get("cookies", [])} + ) + if not jar.get("XSRF-TOKEN"): + raise RuntimeError("缺少 XSRF-TOKEN:请先在 Chrome 登录 devops.aliyun.com") + return jar + + +def fetch_projects() -> list[dict[str, Any]]: + jar = load_jar() + x = jar.get("XSRF-TOKEN", "") + xsrf = urllib.parse.unquote(x) if "%" in x else x + cookie = "; ".join(f"{k}={v}" for k, v in jar.items()) + headers = { + "Cookie": cookie, + "x-xsrf-token": xsrf, + "X-XSRF-TOKEN": xsrf, + "Origin": "https://devops.aliyun.com", + "Referer": "https://devops.aliyun.com/projex", + "accept": "application/json", + } + extra = json.dumps( + { + "conditionGroups": [ + [ + { + "className": "user", + "fieldIdentifier": "users", + "format": "multiList", + "operator": "CONTAINS", + "value": [WANG], + } + ], + [ + { + "className": "string", + "fieldIdentifier": "scope", + "format": "list", + "operator": "CONTAINS", + "value": ["public"], + } + ], + ] + }, + separators=(",", ":"), + ) + params = { + "extraConditions": extra, + "conditions": json.dumps({"conditionGroups": [[]]}, separators=(",", ":")), + "orderBy": json.dumps( + { + "fieldIdentifier": "gmtCreate", + "format": "input", + "order": "desc", + "className": "date", + }, + separators=(",", ":"), + ), + "scope": "all", + "category": "Project", + "toPage": 1, + "pageSize": 100, + "_input_charset": "utf-8", + } + r = requests.get( + "https://devops.aliyun.com/projex/api/workspace/project/search/list", + headers=headers, + params=params, + timeout=30, + ) + r.raise_for_status() + rows = r.json().get("result") or [] + return [ + { + "name": p.get("name"), + "identifier": p.get("identifier"), + "customCode": p.get("customCode"), + "logicalStatus": p.get("logicalStatus"), + "status": (p.get("status") or {}).get("displayName"), + "scope": p.get("scope"), + } + for p in rows + ] + + +def match_projects( + projects: list[dict[str, Any]], + *, + query: str | None = None, + space_id: str | None = None, +) -> list[dict[str, Any]]: + if space_id: + sid = space_id.strip() + return [p for p in projects if p.get("identifier") == sid] + if not query: + return [] + q = query.strip().lower() + hits: list[dict[str, Any]] = [] + for p in projects: + name = (p.get("name") or "").lower() + code = (p.get("customCode") or "").lower() + if q == name or q == code or q in name or name in q: + hits.append(p) + continue + if q in ALIASES and (code == "oneos" or "oneos" in name or "运营" in name): + hits.append(p) + # de-dupe by identifier + seen: set[str] = set() + out: list[dict[str, Any]] = [] + for p in hits: + i = p.get("identifier") or "" + if i in seen: + continue + seen.add(i) + out.append(p) + return out + + +def list_projects( + *, + match: str | None = None, + match_id: str | None = None, +) -> dict: + projects = fetch_projects() + payload: dict[str, Any] = { + "fetched_at": datetime.now(TZ).isoformat(), + "organizationIdentifier": ORG, + "count": len(projects), + "projects": projects, + "selection_required": True, + "refetched": False, + "note": "禁止默认直指;须 AskQuestion/Plan 点选后再写入 spaceIdentifier", + } + if not match and not match_id: + return payload + + hits = match_projects(projects, query=match, space_id=match_id) + if len(hits) == 1: + payload["matches"] = hits + payload["match_status"] = "unique" + return payload + + # 无法对应(0 或多)→ 自动重拉一次 + projects2 = fetch_projects() + hits2 = match_projects(projects2, query=match, space_id=match_id) + payload["projects"] = projects2 + payload["count"] = len(projects2) + payload["refetched"] = True + payload["refetch_reason"] = "无法对应" if len(hits) != 1 else "unexpected" + if len(hits) == 0: + payload["refetch_reason"] = "首次0命中" + elif len(hits) > 1: + payload["refetch_reason"] = "首次多命中" + payload["matches"] = hits2 + if len(hits2) == 1: + payload["match_status"] = "unique_after_refetch" + elif len(hits2) == 0: + payload["match_status"] = "none_after_refetch" + payload["note"] = "已自动重拉一次仍无法对应;请用户从最新列表点选;禁止再拉第3次" + else: + payload["match_status"] = "ambiguous_after_refetch" + payload["note"] = "已自动重拉一次仍多命中;请用户点选;禁止自动选定" + payload["fetched_at"] = datetime.now(TZ).isoformat() + return payload + + +def main() -> None: + ap = argparse.ArgumentParser(description="YunxiaoPMapp 项目列表 / 匹配(无法对应则重拉一次)") + ap.add_argument("--match", help="按项目名或 customCode 匹配") + ap.add_argument("--match-id", help="按 spaceIdentifier 匹配") + args = ap.parse_args() + print( + json.dumps( + list_projects(match=args.match, match_id=args.match_id), + ensure_ascii=False, + indent=2, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/.cursor/skills/YunxiaoPM/scripts/list_tags.py b/.cursor/skills/YunxiaoPM/scripts/list_tags.py new file mode 100644 index 0000000..858c18e --- /dev/null +++ b/.cursor/skills/YunxiaoPM/scripts/list_tags.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 +"""标签候选列表(聚合工作项 tag + runtime);未命中可 --match 后自动重拉一次。""" +from __future__ import annotations + +import argparse +import json +import urllib.parse +from datetime import datetime, timedelta, timezone +from pathlib import Path +from typing import Any + +import requests + +ROOT = Path(__file__).resolve().parents[1] +RUNTIME = json.loads((ROOT / "assets" / "runtime-ids.json").read_text()) +TZ = timezone(timedelta(hours=8)) + + +def load_jar() -> dict[str, str]: + jar: dict[str, str] = {} + try: + import browser_cookie3 + + for domain in (".aliyun.com", "devops.aliyun.com", ".devops.aliyun.com"): + try: + for c in browser_cookie3.chrome(domain_name=domain): + jar[c.name] = c.value + except Exception: + pass + except Exception: + pass + if not jar.get("XSRF-TOKEN"): + p = Path("/tmp/yunxiao_cookies.json") + if p.exists(): + raw = json.loads(p.read_text()) + jar = ( + raw + if isinstance(raw, dict) and "XSRF-TOKEN" in raw + else {c["name"]: c["value"] for c in raw.get("cookies", [])} + ) + if not jar.get("XSRF-TOKEN"): + raise RuntimeError("缺少 XSRF-TOKEN") + return jar + + +def session() -> requests.Session: + jar = load_jar() + x = jar.get("XSRF-TOKEN", "") + xsrf = urllib.parse.unquote(x) if "%" in x else x + s = requests.Session() + s.headers.update( + { + "Cookie": "; ".join(f"{k}={v}" for k, v in jar.items()), + "x-xsrf-token": xsrf, + "X-XSRF-TOKEN": xsrf, + "Origin": "https://devops.aliyun.com", + "Referer": "https://devops.aliyun.com/projex", + "accept": "application/json", + "Content-Type": "application/json", + } + ) + return s + + +def fetch_tags(space_id: str) -> list[dict[str, str]]: + s = session() + by_id: dict[str, str] = {} + for name, tid in (RUNTIME.get("tags") or {}).items(): + by_id[tid] = name + body = { + "spaceIdentifier": space_id, + "spaceType": "Project", + "category": "Req", + "toPage": 1, + "pageSize": 100, + "conditions": json.dumps({"conditionGroups": [[]]}), + } + rows = ( + s.post( + "https://devops.aliyun.com/projex/api/workitem/workitem/list?_input_charset=utf-8", + json=body, + timeout=30, + ) + .json() + .get("result") + or [] + ) + for row in rows: + for t in row.get("tag") or []: + tid = t.get("identifier") + name = t.get("name") or t.get("displayName") + if tid and name: + by_id[tid] = name + # Task 再扫一页,补标签 + body["category"] = "Task" + rows = ( + s.post( + "https://devops.aliyun.com/projex/api/workitem/workitem/list?_input_charset=utf-8", + json=body, + timeout=30, + ) + .json() + .get("result") + or [] + ) + for row in rows: + for t in row.get("tag") or []: + tid = t.get("identifier") + name = t.get("name") or t.get("displayName") + if tid and name: + by_id[tid] = name + return [{"name": n, "identifier": i} for i, n in sorted(by_id.items(), key=lambda x: x[1])] + + +def match_tags(tags: list[dict[str, str]], query: str) -> list[dict[str, str]]: + q = query.strip().lower() + hits = [] + for t in tags: + name = (t.get("name") or "").lower() + if q == name or q in name or name in q: + hits.append(t) + return hits + + +def list_tags(*, space_id: str, match: str | None = None, prefer: str | None = None) -> dict[str, Any]: + tags = fetch_tags(space_id) + # prefer 置顶 + if prefer: + prefer_l = prefer.strip().lower() + tags = sorted( + tags, + key=lambda t: (0 if (t.get("name") or "").lower() == prefer_l else 1, t.get("name") or ""), + ) + out: dict[str, Any] = { + "fetched_at": datetime.now(TZ).isoformat(), + "spaceIdentifier": space_id, + "count": len(tags), + "tags": tags, + "refetched": False, + "note": "标签未命中须自动重拉一次并重生 4.A/B/C 选项;见 compact-select.md", + } + if not match: + return out + hits = match_tags(tags, match) + if len(hits) == 1: + out["matches"] = hits + out["match_status"] = "unique" + return out + # 无法对应 → 重拉一次 + tags2 = fetch_tags(space_id) + if prefer: + prefer_l = prefer.strip().lower() + tags2 = sorted( + tags2, + key=lambda t: (0 if (t.get("name") or "").lower() == prefer_l else 1, t.get("name") or ""), + ) + hits2 = match_tags(tags2, match) + out["tags"] = tags2 + out["count"] = len(tags2) + out["refetched"] = True + out["refetch_reason"] = "首次0命中" if len(hits) == 0 else "首次多命中" + out["matches"] = hits2 + if len(hits2) == 1: + out["match_status"] = "unique_after_refetch" + elif len(hits2) == 0: + out["match_status"] = "none_after_refetch" + out["note"] = "已自动重拉标签列表仍无法对应;请用户点选 4.x;禁止再拉第3次" + else: + out["match_status"] = "ambiguous_after_refetch" + out["fetched_at"] = datetime.now(TZ).isoformat() + return out + + +def main() -> None: + ap = argparse.ArgumentParser() + ap.add_argument("--space", required=True, help="spaceIdentifier") + ap.add_argument("--match", help="标签名") + ap.add_argument("--prefer", help="置顶标签名") + args = ap.parse_args() + print( + json.dumps( + list_tags(space_id=args.space, match=args.match, prefer=args.prefer), + ensure_ascii=False, + indent=2, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/.cursor/skills/YunxiaoPM/scripts/live_create_fast.py b/.cursor/skills/YunxiaoPM/scripts/live_create_fast.py new file mode 100644 index 0000000..4e5be22 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/scripts/live_create_fast.py @@ -0,0 +1,660 @@ +#!/usr/bin/env python3 +"""YunxiaoPM 极速真实建单 v5:快轨描述/计划/标签/工时 2+2/设计 ASSOCIATED 补挂。""" +from __future__ import annotations + +import json +import time +import urllib.parse +from concurrent.futures import ThreadPoolExecutor, as_completed +from datetime import datetime, timedelta, timezone +from pathlib import Path +from typing import Any + +try: + import browser_cookie3 +except ImportError: # pragma: no cover + browser_cookie3 = None + +import requests + +ROOT = Path(__file__).resolve().parents[1] +RUNTIME = json.loads((ROOT / "assets" / "runtime-ids.json").read_text()) +STATUS = RUNTIME["status"]["req"] +TASK_STATUS = RUNTIME["status"]["task"] +FAST = RUNTIME.get("fields", {}).get("fast_track") or {} + +SPACE = ( + RUNTIME.get("project", {}).get("spaceIdentifier") + or (RUNTIME.get("project", {}).get("last_selected") or {}).get("spaceIdentifier") +) +if not SPACE: + raise RuntimeError( + "未选定项目 spaceIdentifier:须先走门禁 PJ 点选,或设置 runtime project.last_selected" + ) +REQ_TYPE = RUNTIME["workitem_types"]["product_req"]["identifier"] +TASK_TYPE = RUNTIME["workitem_types"]["task"]["identifier"] +HEFEI = RUNTIME["people"]["hefei"]["identifier"] +WANG = RUNTIME["people"].get("wangmian", {}).get("identifier", "6811df000601d2fea60144a9") +PRI = RUNTIME["priority"]["中"] +TAG_FAULT = RUNTIME.get("tags", {}).get("故障管理", "ceb526a7343995577645317e9a") +PLACEHOLDER = RUNTIME["delivery_placeholder"] +PROTO = "https://prototype.lnoneos.com/vehicle-fault-handling/index.html" +TZ = timezone(timedelta(hours=8)) +TODAY = datetime.now(TZ).strftime("%Y-%m-%d") +NOON = f"{TODAY} 12:00:00" +EOD = f"{TODAY} 23:59:59" +NOON_MS = str( + int(datetime.now(TZ).replace(hour=12, minute=0, second=0, microsecond=0).timestamp() * 1000) +) +START_MS = str( + int(datetime.now(TZ).replace(hour=9, minute=0, second=0, microsecond=0).timestamp() * 1000) +) +END_MS = str( + int(datetime.now(TZ).replace(hour=11, minute=0, second=0, microsecond=0).timestamp() * 1000) +) +FAST_EST = int(FAST.get("req_estimated_hours", 2)) +FAST_ACT = int(FAST.get("req_actual_hours", 2)) + +PENDING = STATUS["待处理"] +DESIGN_DONE = STATUS["设计完成"] +PENDING_DEV = STATUS["待开发"] +TASK_DONE = TASK_STATUS["已完成"] + +_COOKIE = "" +_XSRF = "" + + +def load_auth() -> None: + global _COOKIE, _XSRF + jar: dict[str, str] = {} + if browser_cookie3: + for domain in (".aliyun.com", "devops.aliyun.com", ".devops.aliyun.com"): + try: + for c in browser_cookie3.chrome(domain_name=domain): + jar[c.name] = c.value + except Exception: + pass + if not jar: + p = Path("/tmp/yunxiao_cookies.json") + if p.exists(): + raw = json.loads(p.read_text()) + jar = ( + raw + if isinstance(raw, dict) and "XSRF-TOKEN" in raw + else {c["name"]: c["value"] for c in raw.get("cookies", [])} + ) + _COOKIE = "; ".join(f"{k}={v}" for k, v in jar.items()) + x = jar.get("XSRF-TOKEN", "") + _XSRF = urllib.parse.unquote(x) if "%" in x else x + if not _XSRF: + raise RuntimeError("缺少 XSRF-TOKEN:请先在 Chrome 登录 devops.aliyun.com") + + +def session() -> requests.Session: + s = requests.Session() + s.headers.update( + { + "Content-Type": "application/json", + "Cookie": _COOKIE, + "x-xsrf-token": _XSRF, + "X-XSRF-TOKEN": _XSRF, + "Origin": "https://devops.aliyun.com", + "Referer": f"https://devops.aliyun.com/projex/project/{SPACE}/req", + "accept": "application/json", + "User-Agent": "YunxiaoPM-live_create_fast/5.0", + "Connection": "keep-alive", + } + ) + return s + + +def api(s: requests.Session, method: str, url: str, body: Any = None) -> dict: + r = s.request(method, url, json=body, timeout=60) + try: + return r.json() + except Exception: + r.raise_for_status() + raise + + +def create(s: requests.Session, payload: dict) -> dict: + url = "https://devops.aliyun.com/projex/api/workitem/workitem?_input_charset=utf-8" + j = api(s, "POST", url, payload) + r = j.get("result") or {} + if j.get("code") == 200 and isinstance(r, dict) and r.get("identifier"): + return r + j = api(s, "PUT", url, payload) + r = j.get("result") or {} + if j.get("code") == 200 and isinstance(r, dict) and r.get("identifier"): + return r + raise RuntimeError(f"create failed: {j}") + + +def get(s: requests.Session, wid: str) -> dict: + return api( + s, + "GET", + f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}?_input_charset=utf-8", + )["result"] + + +def apply_tag(s: requests.Session, wid: str, tag_id: str = TAG_FAULT) -> None: + j = api( + s, + "PATCH", + f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}?_input_charset=utf-8", + { + "workitemIdentifier": wid, + "propertyKey": "tag", + "propertyValue": tag_id, + "operateType": "COVER", + }, + ) + if j.get("code") != 200: + raise RuntimeError(f"tag failed {wid}: {j}") + + +def set_document(s: requests.Session, wid: str, html: str) -> None: + j = api( + s, + "PATCH", + f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}/document?_input_charset=utf-8", + {"content": html, "formatType": "RICHTEXT"}, + ) + if j.get("code") != 200 or j.get("errorMsg"): + raise RuntimeError(f"document failed {wid}: {j}") + + +def set_fields(s: requests.Session, wid: str, pairs: list[tuple[str, str]]) -> None: + data = urllib.parse.urlencode( + { + "fieldValueList": json.dumps( + [{"fieldIdentifier": k, "value": v} for k, v in pairs], ensure_ascii=False + ) + } + ) + r = s.post( + f"https://devops.aliyun.com/projex/api/workitem/workitem/field/value/{wid}?_input_charset=utf-8", + data=data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + timeout=60, + ) + j = r.json() + if j.get("code") != 200: + raise RuntimeError(f"field/value failed {wid}: {j}") + + +def set_estimate_hours(s: requests.Session, wid: str, hours: int, user: str = WANG) -> None: + est = api( + s, + "GET", + f"https://devops.aliyun.com/projex/api/workitem/workitem/time/estimate/list?workitemIdentifier={wid}", + ).get("result") or [] + for row in est: + eid = row.get("identifier") + if eid: + api( + s, + "DELETE", + f"https://devops.aliyun.com/projex/api/workitem/workitem/time/estimate/{wid}/{eid}", + ) + j = api( + s, + "POST", + "https://devops.aliyun.com/projex/api/workitem/workitem/time/estimate?_input_charset=utf-8", + { + "workitemIdentifier": wid, + "spentTime": hours, + "type": "develop", + "description": "快轨默认预计工时", + "recordUserIdentifier": user, + "forCreate": False, + "containsRestDay": False, + }, + ) + if j.get("code") != 200: + raise RuntimeError(f"estimate failed {wid}: {j}") + + +def set_actual_hours(s: requests.Session, wid: str, hours: int, user: str = WANG) -> None: + j = api( + s, + "POST", + "https://devops.aliyun.com/projex/api/workitem/workitem/time?_input_charset=utf-8", + { + "workitemIdentifier": wid, + "actualTime": hours, + "type": "develop", + "description": "快轨默认实际工时", + "recordUserIdentifier": user, + "gmtStart": START_MS, + "gmtEnd": END_MS, + }, + ) + if j.get("code") != 200 or not j.get("result"): + raise RuntimeError(f"actual time failed {wid}: {j}") + + +def try_associate_to_req(s: requests.Session, stage_id: str, req_id: str) -> bool: + """建后补 ASSOCIATED→需求。Cookie 下常失败,成功返回 True。""" + bodies = [ + {"relationIdentifier": "ASSOCIATED", "toWorkitemIdentifier": req_id}, + { + "relationIdentifier": "ASSOCIATED", + "fromWorkitemIdentifier": stage_id, + "toWorkitemIdentifier": req_id, + }, + ] + for body in bodies: + for url in ( + f"https://devops.aliyun.com/projex/api/workitem/workitem/{stage_id}/relation/record?_input_charset=utf-8", + f"https://devops.aliyun.com/projex/api/workitem/v2/workitem/{stage_id}/relation/record?_input_charset=utf-8", + ): + j = api(s, "POST", url, body) + if j.get("code") == 200 and j.get("result") not in (None, False): + if not (isinstance(j.get("result"), dict) and j["result"].get("status") in (404, 405)): + rows = list_associated(s, stage_id) + if req_id in {r.get("identifier") for r in rows}: + return True + return False + + +def transit(s: requests.Session, wid: str, from_status: str, to_status: str) -> None: + if from_status == to_status: + return + j = api( + s, + "POST", + f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}/status/transit?_input_charset=utf-8", + {"fromStatus": from_status, "toStatus": to_status}, + ) + if not (j.get("code") == 200 and j.get("result") is True): + raise RuntimeError(f"transit {wid} {from_status}->{to_status}: {j}") + + +def md_to_html(md: str) -> str: + parts = [] + for line in md.splitlines(): + if line.startswith("## "): + parts.append(f"

{line[3:]}

") + elif line.startswith("### "): + parts.append(f"

{line[4:]}

") + elif line.startswith("- "): + parts.append(f"

• {line[2:]}

") + elif line.strip(): + parts.append(f"

{line}

") + return "".join(parts) + + +def req_document_html(s: requests.Session, rid: str) -> str: + w = get(s, rid) + return ((w.get("document") or {}).get("content") or w.get("description") or "").strip() + + +def req_payload(subject: str, html: str) -> dict: + return { + "subject": subject, + "description": html, + "formatType": "RICHTEXT", + "document": {"content": html, "formatType": "RICHTEXT"}, + "spaceIdentifier": SPACE, + "space": SPACE, + "spaceType": "Project", + "workitemTypeIdentifier": REQ_TYPE, + "workitemType": REQ_TYPE, + "categoryIdentifier": "Req", + "category": "Req", + "assignedTo": WANG, + "fieldValueList": [ + {"fieldIdentifier": "priority", "value": PRI}, + {"fieldIdentifier": "assignedTo", "value": WANG}, + ], + "attachmentIdList": [], + "cloneFrom": None, + "createWorkitemRelationList": [], + } + + +def task_payload( + subject: str, + html: str, + assignee: str, + *, + plan_start: bool = True, + associated_req: str | None = None, + parent_delivery: str | None = None, +) -> dict: + fvl = [ + {"fieldIdentifier": "priority", "value": PRI}, + {"fieldIdentifier": "assignedTo", "value": assignee}, + ] + if plan_start: + fvl.append({"fieldIdentifier": "79", "value": NOON_MS}) + payload: dict[str, Any] = { + "subject": subject, + "description": html, + "formatType": "RICHTEXT", + "spaceIdentifier": SPACE, + "space": SPACE, + "spaceType": "Project", + "workitemTypeIdentifier": TASK_TYPE, + "workitemType": TASK_TYPE, + "categoryIdentifier": "Task", + "category": "Task", + "assignedTo": assignee, + "fieldValueList": fvl, + "attachmentIdList": [], + "cloneFrom": None, + } + if parent_delivery: + payload["parent"] = parent_delivery + payload["parentIdentifier"] = parent_delivery + payload["createWorkitemRelationInfo"] = { + "relatedWorkitemIdentifier": parent_delivery, + "relatedToRelationIdentifier": "TASK_SUB", + } + else: + if not associated_req: + raise ValueError("associated_req required for delivery: ASSOCIATED→需求") + payload["createWorkitemRelationInfo"] = { + "relatedWorkitemIdentifier": associated_req, + "relatedToRelationIdentifier": "ASSOCIATED", + } + return payload + + +def list_associated(s: requests.Session, wid: str) -> list[dict]: + j = api( + s, + "GET", + f"https://devops.aliyun.com/projex/api/workitem/v2/workitem/{wid}/relation/workitem/list/by-relation-category?category=ASSOCIATED&isForward=true", + ) + return j.get("result") or [] + + +def assert_associated_to_req(s: requests.Session, wid: str, req_id: str, label: str) -> None: + rows = list_associated(s, wid) + ids = {r.get("identifier") for r in rows} + if req_id not in ids: + raise RuntimeError( + f"{label} 关联项未挂需求:期望 {req_id},实际 {[r.get('serialNumber') for r in rows]}" + ) + + +AUTO_RDO = """## 原始诉求(AutoRDO) + +运维需在故障处置页承接机器人上报的故障,完成处置、挂起与归档,并保留证据链;工作台相关统计口径需与处置页一致 + +待确认: +- 本期是否含真实短信/邮件通道(现口径一般为演示模板) + +## 工作项编号(系统) +- 交付:待建 +- 分析:待建 +- 设计:待建 +""" + + +def summarize_from_create(w: dict, *, status: str, assignee_name: str) -> dict: + return { + "serial": w.get("serialNumber"), + "id": w.get("identifier"), + "subject": w.get("subject"), + "status": status, + "assignee": assignee_name, + "parent": w.get("parentIdentifier"), + } + + +def build_normal() -> dict: + t0 = time.perf_counter() + s = session() + title = "【新增】故障处置(YunxiaoPMapp标准·极速v2)" + req = create(s, req_payload(title, md_to_html(AUTO_RDO + f"\n原型:{PROTO}\n"))) + rid = req["identifier"] + + with ThreadPoolExecutor(max_workers=2) as pool: + f_tag_r = pool.submit(apply_tag, session(), rid) + f_deliv = pool.submit( + create, + session(), + task_payload( + f"【交付】{title}", + f"

{PLACEHOLDER}

", + HEFEI, + associated_req=rid, + ), + ) + deliv = f_deliv.result() + f_tag_r.result() + did = deliv["identifier"] + + with ThreadPoolExecutor(max_workers=3) as pool: + f_tag_d = pool.submit(apply_tag, session(), did) + f_ana = pool.submit( + create, + session(), + task_payload( + f"【分析】{title}", + "

分析阶段:故障处置台账、挂起归档与证据链。

", + WANG, + associated_req=rid, + parent_delivery=did, + ), + ) + f_des = pool.submit( + create, + session(), + task_payload( + f"【设计】{title}", + f"

设计阶段:对齐原型 {PROTO}

", + WANG, + associated_req=rid, + parent_delivery=did, + ), + ) + ana = f_ana.result() + des = f_des.result() + f_tag_d.result() + + with ThreadPoolExecutor(max_workers=3) as pool: + list( + as_completed( + [ + pool.submit(transit, session(), rid, PENDING, DESIGN_DONE), + pool.submit(transit, session(), ana["identifier"], PENDING, TASK_DONE), + pool.submit(transit, session(), des["identifier"], PENDING, TASK_DONE), + ] + ) + ) + transit(s, rid, DESIGN_DONE, PENDING_DEV) + + return { + "path": "normal", + "elapsed_s": round(time.perf_counter() - t0, 3), + "req": summarize_from_create(req, status="待开发", assignee_name="王冕"), + "delivery": summarize_from_create(deliv, status="待处理", assignee_name="何斐"), + "analysis": summarize_from_create(ana, status="已完成", assignee_name="王冕"), + "design": summarize_from_create(des, status="已完成", assignee_name="王冕"), + } + + +def build_fast( + *, + tag_id: str = TAG_FAULT, + has_prototype: bool = True, + delivery_html: str | None = None, +) -> dict: + """无单快轨。 + + - 设计描述 = 需求 document + - 交付描述 = 手工同步需求正文(无原型)或传入 AutoPRD HTML;禁止无故占位 + - 设计 79/80=当日;交付 79=当日 + - 需求/交付/设计同标签 + - 需求预计/实际工时各 2 + - 设计 TASK_SUB→交付后尝试 ASSOCIATED→需求 + """ + t0 = time.perf_counter() + s = session() + title = "【新增】故障处置(YunxiaoPM快轨·极速v5)" + req_html = md_to_html(AUTO_RDO + (f"\n原型:{PROTO}\n" if has_prototype else "\n")) + req = create(s, req_payload(title, req_html)) + rid = req["identifier"] + # 以落库 document 为准(与手动建需后读需求一致) + req_html = req_document_html(s, rid) or req_html + + if delivery_html: + deliv_html = delivery_html + desc_source = "autoprd" + elif req_html.strip(): + deliv_html = req_html + desc_source = "manual_sync" + else: + deliv_html = f"

{PLACEHOLDER}

" + desc_source = "placeholder" + + with ThreadPoolExecutor(max_workers=2) as pool: + f_tag_r = pool.submit(apply_tag, session(), rid, tag_id) + f_deliv = pool.submit( + create, + session(), + task_payload( + f"【交付】{title}", + deliv_html, + HEFEI, + associated_req=rid, + ), + ) + deliv = f_deliv.result() + f_tag_r.result() + did = deliv["identifier"] + + with ThreadPoolExecutor(max_workers=2) as pool: + f_tag_d = pool.submit(apply_tag, session(), did, tag_id) + f_des = pool.submit( + create, + session(), + task_payload( + f"【设计】{title}", + req_html, + WANG, + parent_delivery=did, + ), + ) + des = f_des.result() + f_tag_d.result() + des_id = des["identifier"] + apply_tag(s, des_id, tag_id) + + # 计划时间:设计起止当日;交付开始当日(create 已带 79,再 field/value 加固) + set_fields(s, des_id, [("79", NOON), ("80", EOD)]) + set_fields(s, did, [("79", NOON)]) + + # 设计关联项补挂需求(可能失败,回报 risk) + design_assoc_ok = try_associate_to_req(s, des_id, rid) + + with ThreadPoolExecutor(max_workers=2) as pool: + list( + as_completed( + [ + pool.submit(transit, session(), rid, PENDING, DESIGN_DONE), + pool.submit(transit, session(), des_id, PENDING, TASK_DONE), + ] + ) + ) + transit(s, rid, DESIGN_DONE, PENDING_DEV) + + set_estimate_hours(s, rid, FAST_EST) + set_actual_hours(s, rid, FAST_ACT) + + risk = None + if desc_source == "placeholder": + risk = "交付描述仍为占位" + if not design_assoc_ok: + risk = (risk + ";" if risk else "") + "设计 ASSOCIATED→需求补挂失败(Cookie),须 UI/OpenAPI 兜底" + + return { + "path": "fast", + "elapsed_s": round(time.perf_counter() - t0, 3), + "req": summarize_from_create(req, status="待开发", assignee_name="王冕"), + "delivery": summarize_from_create(deliv, status="待处理", assignee_name="何斐"), + "design": summarize_from_create(des, status="已完成", assignee_name="王冕"), + "analysis": None, + "delivery_desc_source": desc_source, + "design_associated_ok": design_assoc_ok, + "risk": risk, + } + + +def main() -> None: + auth0 = time.perf_counter() + load_auth() + auth_s = round(time.perf_counter() - auth0, 3) + + wall0 = time.perf_counter() + with ThreadPoolExecutor(max_workers=2) as pool: + f_fast = pool.submit(build_fast) + f_normal = pool.submit(build_normal) + fast = f_fast.result() + normal = f_normal.result() + build_s = round(time.perf_counter() - wall0, 3) + + s = session() + assert_associated_to_req(s, normal["delivery"]["id"], normal["req"]["id"], "normal.delivery") + assert_associated_to_req(s, fast["delivery"]["id"], fast["req"]["id"], "fast.delivery") + + def assert_sub(delivery_id: str, child_id: str, label: str) -> None: + rows = api( + s, + "GET", + f"https://devops.aliyun.com/projex/api/workitem/v2/workitem/{delivery_id}/relation/workitem/list/by-relation-category?category=PARENT_SUB&isForward=true", + ).get("result") or [] + ids = {r.get("identifier") for r in rows} + if child_id not in ids: + raise RuntimeError( + f"{label} 未出现在交付子项:期望 {child_id},实际 {[r.get('serialNumber') for r in rows]}" + ) + + assert_sub(normal["delivery"]["id"], normal["analysis"]["id"], "normal.analysis") + assert_sub(normal["delivery"]["id"], normal["design"]["id"], "normal.design") + assert_sub(fast["delivery"]["id"], fast["design"]["id"], "fast.design") + verify = { + "normal_req": get(s, normal["req"]["id"])["status"]["displayName"], + "fast_req": get(s, fast["req"]["id"])["status"]["displayName"], + "normal_delivery_assignee": ( + get(s, normal["delivery"]["id"]).get("assignedTo") or {} + ).get("displayName"), + "fast_delivery_assignee": (get(s, fast["delivery"]["id"]).get("assignedTo") or {}).get( + "displayName" + ), + "delivery_associated_ok": True, + "stage_tasks_as_sub_ok": True, + "fast_design_associated_ok": fast.get("design_associated_ok"), + } + out = { + "normal": normal, + "fast": fast, + "verify": verify, + "auth_elapsed_s": auth_s, + "wall_elapsed_s": build_s, + "created_at": datetime.now(TZ).isoformat(), + "mode": "live_create_fast_v5_fast_track_rules", + "opts": { + "create_includes_plan_start": True, + "skip_plan_end_on_create": True, + "tracked_transit_no_get": True, + "delivery_assignee_hefei_at_create": True, + "overlap_tag_with_create": True, + "fast_req_hours": f"{FAST_EST}+{FAST_ACT}", + "relation": "delivery ASSOCIATED→req; analysis/design TASK_SUB→delivery; design post ASSOCIATED→req", + "http": "requests.Session keep-alive", + }, + } + Path("/tmp/yunxiao_pmapp_fast_v2_result.json").write_text( + json.dumps(out, ensure_ascii=False, indent=2) + ) + print(json.dumps(out, ensure_ascii=False, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/.cursor/skills/YunxiaoPM/scripts/workday_hours.py b/.cursor/skills/YunxiaoPM/scripts/workday_hours.py new file mode 100755 index 0000000..289b867 --- /dev/null +++ b/.cursor/skills/YunxiaoPM/scripts/workday_hours.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""Compute YunxiaoPMapp stage calendar hours = workdays × 8. + +Usage: + python3 workday_hours.py YYYY-MM-DD YYYY-MM-DD + python3 workday_hours.py YYYY-MM-DD YYYY-MM-DD --calendar /path/to/cn-workday-calendar.json +""" + +from __future__ import annotations + +import argparse +import json +import sys +from datetime import date, datetime, timedelta +from pathlib import Path + + +def parse_day(s: str) -> date: + return datetime.strptime(s, "%Y-%m-%d").date() + + +def load_calendar(path: Path) -> dict: + data = json.loads(path.read_text(encoding="utf-8")) + years = data.get("years") or {} + holidays: set[str] = set() + makeup: set[str] = set() + for _y, block in years.items(): + holidays.update(block.get("holidays") or []) + makeup.update(block.get("workdays_on_weekend") or []) + return {"holidays": holidays, "makeup": makeup, "years": set(years.keys())} + + +def ensure_years_covered(start: date, end: date, years: set[str]) -> None: + y = start.year + while y <= end.year: + if str(y) not in years: + raise SystemExit( + f"缺少 {y} 年日历:请在 assets/cn-workday-calendar.json 补全后再计算。" + "禁止仅去周末静默估算。" + ) + y += 1 + + +def is_workday(d: date, holidays: set[str], makeup: set[str]) -> bool: + key = d.isoformat() + if key in holidays: + return False + if key in makeup: + return True + return d.weekday() < 5 # Mon=0 .. Fri=4 + + +def count_workdays(start: date, end: date, holidays: set[str], makeup: set[str]) -> int: + if end < start: + raise SystemExit("结束日早于开始日") + n = 0 + cur = start + while cur <= end: + if is_workday(cur, holidays, makeup): + n += 1 + cur += timedelta(days=1) + return n + + +def main() -> None: + parser = argparse.ArgumentParser(description="阶段日历工时 = 工作日×8") + parser.add_argument("start", help="计划开始 YYYY-MM-DD") + parser.add_argument("end", help="计划完成 YYYY-MM-DD") + parser.add_argument( + "--calendar", + default=str(Path(__file__).resolve().parent.parent / "assets" / "cn-workday-calendar.json"), + help="日历 JSON 路径", + ) + args = parser.parse_args() + start = parse_day(args.start) + end = parse_day(args.end) + cal = load_calendar(Path(args.calendar)) + ensure_years_covered(start, end, cal["years"]) + days = count_workdays(start, end, cal["holidays"], cal["makeup"]) + hours = days * 8 + out = { + "start": start.isoformat(), + "end": end.isoformat(), + "workdays": days, + "stage_calendar_hours": hours, + "label": "阶段日历工时(工作日×8),非人力投入预估", + } + print(json.dumps(out, ensure_ascii=False, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/.cursor/skills/YunxiaoPM/stage-flow.md b/.cursor/skills/YunxiaoPM/stage-flow.md new file mode 100644 index 0000000..3c97b2f --- /dev/null +++ b/.cursor/skills/YunxiaoPM/stage-flow.md @@ -0,0 +1,63 @@ +# 标准路径:待处理 → 待开发交棒 + +项目常量见 [../assets/runtime-ids.json](../assets/runtime-ids.json)。查重只用任务编号。 + +## 步骤 0|创建需求 + +| 云效动作 | 落地 | +|---|---| +| 新建产品类需求 | POST 建单;标题 `【新增】`/`【优化】` | +| 需求描述 | **先** AutoRDO 清洗再写入 `## 原始诉求(AutoRDO)`;本步不写 AutoPRD | +| 默认状态 | **待处理**;不建任务、不改负责人 | +| 可选推进 | 口令点选目标状态;未选则停在待处理 | + +## 步骤 1|受理 → 已确认 + +| 云效动作 | 落地 | +|---|---| +| 状态 | 待处理 → **已确认** | +| 任务 | **仍不建**交付/分析/设计 | + +若用户只「创建」未受理:只提示「受理后请推进至已确认」,不自动跳。 + +## 步骤 2|分析中 + +| 对象 | 动作 | +|---|---| +| 【交付】 | 无则新建;标题 `【交付】`+需求标题;**ASSOCIATED→需求**(勿用 PARENT);负责人=创建人/产品;**计划开始仅空时写当日**;不写计划完成;描述=`等待设计任务完成后自动填入`;更新编号区块 | +| 【分析】 | 新建;**TASK_SUB→交付**(`parent`+`parentIdentifier`+`createWorkitemRelationInfo=TASK_SUB`);交付「子项」须可见;计划开始=当日(仅空时);负责人默认可=创建人;更新编号区块 | + +| 需求 | 状态=`分析中` | + +## 步骤 3|设计中 + +| 对象 | 动作 | +|---|---| +| 【交付】 | 已存在则按编号复用;没有则补建;**不改交付计划开始**;不写交付计划完成 | +| 【设计】 | 新建;**TASK_SUB→交付**(同分析);计划开始=当日(仅空时);更新编号区块 | +| 【分析】 | **计划完成**=状态更新日;推算阶段日历工时(见 work-hours) | +| 需求 | 状态=`设计中` | + +## 步骤 4|设计完成 + +| 对象 | 动作 | +|---|---| +| 【设计】 | 计划完成=当日;推算工时;任务→完成态 | +| 需求 | AutoPRD 写入 `## 产品说明`;挂 ZIP+截图;状态=`设计完成` | +| 【交付】 | 不换负责人;不改计划开始;不写计划完成;描述改为 AutoPRD 正文;同样挂附件 | + +顺序与失败门禁见 [description-split.md](description-split.md)。 + +## 步骤 5|待开发交棒(本 Skill 终点 · 标准路径) + +| 对象 | 动作 | +|---|---| +| 需求 | 状态=`待开发` | +| 【交付】 | 按**任务编号**定位;**负责人→何斐**;不改计划开始;不写计划完成 | +| 【分析】/【设计】 | 不新建;不改已有计划开始;前序未收口按步骤 3/4 处理 | + +执行 [handoff-and-rollback.md](handoff-and-rollback.md) §交棒门禁。 + +**不做:** 【开发】/【测试】、仓库、分支、提测。 + +快轨 / 编号直推见专文。创建迭代见 [sprint.md](sprint.md)。 diff --git a/.cursor/skills/oneos-autoprd/SKILL.md b/.cursor/skills/oneos-autoprd/SKILL.md index a10d84f..6668282 100644 --- a/.cursor/skills/oneos-autoprd/SKILL.md +++ b/.cursor/skills/oneos-autoprd/SKILL.md @@ -1,146 +1,108 @@ --- name: oneos-autoprd description: >- - Generates and keeps in sync OneOS AutoPRD (PM-facing requirements) plus Axhub - Make annotation directory Markdown/PRD; on 需求定稿/定稿/确认定稿/本次定稿 appends - functional release changelog below the PRD since last baseline. When a Yunxiao - requirement advances to 分析中/设计中/待开发, creates a same-titled linked task - (tag+create-time follow the requirement; 分析中/设计中 assignee=creator, 待开发 - assignee=何斐). Use eagerly for prototypes, AutoPRD, or Yunxiao requirement - description/更新内容/stage advance—do not skip annotation sync, 定稿 changelog, - or stage-task creation. + OneOS AutoPRD: generates PM-facing requirements (总览/目标/边界/角色/用户故事 + 起点→运作→闭环/故事点/正逆向/流程图/关键逻辑/状态/风险/交付) and syncs Axhub Make + annotation PRD. On 需求定稿/本轮定稿 appends functional changelog with auto + V主.副.子 (user 主/副/子 override wins). With YunxiaoPMapp design-complete, + writes Markdown into 【交付】 by task number (not at create; placeholder until then). + Use for AutoPRD, prototypes, 产品说明, 本轮定稿. Never create same-titled stage + tasks; never load yunxiao-requirement-lifecycle—cloud combo is YunxiaoPMapp only. --- -# OneOS AutoPRD +# AutoPRD(oneos-autoprd) 为 OneOS 业务模块生成**产品经理可读、可评审、可排期**的需求说明,并**自动挂到 Axhub Make 标注工具 → 原型目录**。 -另支持:**需求定稿**时汇总功能/逻辑变更记录;与云效组合时提供「需求说明 + 更新内容」;需求进入**分析中 / 设计中 / 待开发**时**自动创建与需求同名的关联任务**(规则见下,写在本 Skill,不改云效 Skill)。 +另支持:**本轮定稿 / 需求定稿**时汇总功能变更并**自动递增 PRD 版本号**;与 **`$YunxiaoPMapp`** 组合时,在**设计完成**(或显式同步交付)把 Markdown 写入【交付】任务描述。 -颗粒度对齐「保险采购」全模块 PRD:讲清做什么、谁用、故事点、正逆向、流程图与关键业务逻辑;**不写**表结构、接口、字段代码名、文件路径、实现清单。 +**禁止加载** `yunxiao-requirement-lifecycle`。云效状态机与【交付】/【分析】/【设计】树**只**由 YunxiaoPMapp 负责;本 Skill **不**建同名无前缀阶段任务。 + +颗粒度:讲清做什么、谁用、故事点、正逆向、流程图与关键业务逻辑;**不写**表结构、接口、字段代码名、文件路径、实现清单。 ## 何时使用(含自动触发) -**主动调用时** +**主动调用** -- AutoPRD、OneOS 需求说明、整模块 PRD、故事点 + 流程图 +- AutoPRD、OneOS 需求说明、整模块 PRD、故事点 + 流程图、产品说明 -**改原型时必须自动跟进(全局规则)** +**改原型时必须自动跟进** -- 正在修改 `src/prototypes//` 下页面、交互、文案、判定、验收相关内容 -- 同一轮交付内同步更新 PRD Markdown + 标注目录,不得只改代码 +- 修改 `src/prototypes//` 下页面、交互、文案、判定、验收相关内容 +- 同一轮同步更新 PRD Markdown + 标注目录;**纯样式且无产品语义变化可跳过全量重写** **需求定稿时(强制)** -- 用户回复含:`需求定稿` / `定稿` / `确认定稿` / `本次定稿` -- 执行 [references/release-changelog.md](references/release-changelog.md):写第 10 章 + 更新基线 +- 关键字:`需求定稿` / `定稿` / `确认定稿` / `本次定稿` / **`本轮定稿`** +- 执行 [references/release-changelog.md](references/release-changelog.md):第 10 章 + `prdVersion` 基线 -**云效建需求 / 完善需求时(强制组合)** +**与 YunxiaoPMapp 组合(强制 · 唯一云效组合)** -- 与 `$yunxiao-requirement-lifecycle` 一起使用时:先跑本 Skill,再写云效描述 -- **需求说明** ← PRD 正文(或浓缩交付口径 + 关键章节) -- **更新内容** ← 第 10 章中**自上次定稿以来**的条目(无增量则「首版定稿 / 本轮无功能增量」) - -**云效需求推进至分析中 / 设计中 / 待开发时(强制)** - -- 无论口令来自本 Skill 还是云效 Skill,只要本轮把需求推到上述状态,就执行 [references/yunxiao-stage-tasks.md](references/yunxiao-stage-tasks.md) -- 自动建**与需求同名**的任务并正式关联;标签与创建时间口径沿用需求;分析中/设计中负责人=创建人,待开发负责人=**何斐** - -不要用本 Skill 替代:需求探索访谈、设计比稿、纯样式微调(无产品语义变化时可跳过全量重写)。 +- 设计完成或完善「产品说明」:先本 Skill 落盘 MD,再由 YunxiaoPMapp / 本 Skill 按 [yunxiao-description.md](references/yunxiao-description.md) 写入需求 `## 产品说明(AutoPRD)`(**不覆盖** `## 原始诉求(AutoRDO)` / `## 工作项编号(系统)`) +- **创建【交付】时不写 PRD 正文**(占位由 YunxiaoPMapp 写入);设计完成或口令「同步交付说明」时按 [yunxiao-delivery-sync.md](references/yunxiao-delivery-sync.md) 用任务**编号**回填【交付】 +- 入库前聊天/录音清洗:先 `$AutoRDO`,再 YunxiaoPMapp 记录需求 ## 工作流 ### 主流程(写/同步 PRD) -1. **定模块**:确认 OneOS 模块名与 `src/prototypes//`。 +1. **定模块**:OneOS 模块名与 `src/prototypes//`。 2. **读上下文(只取产品语义)** - 用户说明、已确认口径、原型标注、`.spec/`、业务条线说明(`lines.ts`) - 忽略实现细节;字段名/接口改写成业务语言。 3. **收敛边界**:做什么 / 不做什么、外部依赖、与其它模块关系。 -4. **按模板成文**:下方「输出结构」;缺关键信息最多问 1~2 个问题,其余写「假设」。 -5. **落盘 + 标注同步(强制)** — 见 [references/annotation-sync.md](references/annotation-sync.md)。 +4. **按模板成文**:见「输出结构」与 [references/template.md](references/template.md)。 +5. **落盘 + 标注同步(强制)** — [references/annotation-sync.md](references/annotation-sync.md)。 | 顺序 | 动作 | |------|------| | A | 写/更新 `src/prototypes//.spec/requirements-prd.md` | | B | 写/更新 `src/resources/prd/-autoprd.md` | - | C | 更新 `annotation-source.json` **顶层** `directory.nodes`(PRD 全文 + 推荐分章);禁止只写 `data.directory` | + | C | 更新 `annotation-source.json` **顶层** `directory.nodes`(PRD 全文 + 推荐分章) | | D | 若存在 `scripts/sync-annotation-directory.mjs`,执行之 | -6. **交付说明**:路径、标注目录入口、故事点合计、开放问题/假设。 +6. **交付说明**:路径、标注入口、故事点合计、开放问题/假设。 -### 定稿流程(关键字触发) +### 定稿流程 -见 [references/release-changelog.md](references/release-changelog.md)。摘要: +见 [references/release-changelog.md](references/release-changelog.md)。含自动 `V主.副.子`;**用户显式指定主/副/子时以用户为准**。 -1. 读 PRD + `.spec/autoprd-baseline.json` -2. 汇总自上次定稿以来的**功能/逻辑**变更(排除样式/UI/表结构) -3. 追加到 `## 10. 功能变更记录`(最新在上) -4. 更新基线 JSON + 标注目录 -5. 若同时发云效:本次定稿块 →「更新内容」;旧段 →「更新内容·历史」 +### 云效需求「产品说明」(与 YunxiaoPMapp 双段模板) -### 云效交接格式(给 yunxiao skill 直接粘贴) +见 [references/yunxiao-description.md](references/yunxiao-description.md)。 -```markdown -## 原型链接 -<对象存储或预览 URL> +### 云效【交付】回填(设计完成 · 非创建时) -## 需求说明 - - -## 更新内容 -<第 10 章本次定稿块;无则「首版定稿」或「本轮无功能/逻辑增量」> - -## 更新内容·历史 -<以往更新内容倒序,勿删除> -``` - -### 云效阶段任务(状态推进时强制) - -见 [references/yunxiao-stage-tasks.md](references/yunxiao-stage-tasks.md)。摘要: - -| 需求状态 | 任务标签 | 负责人 | 标题 | -|----------|----------|--------|------| -| 分析中 | 分析 | 需求创建人 | 与需求同名 | -| 设计中 | 设计 | 需求创建人 | 与需求同名 | -| 待开发 | 交付(或开发) | 何斐 | 与需求同名 | - -正式关联需求;创建时间口径沿用需求;同阶段未取消任务不重复建。 +见 [references/yunxiao-delivery-sync.md](references/yunxiao-delivery-sync.md)。 ## 写作硬约束 -**必须写** +**必须写(映射用户概要)** -- 一句话定位 + 目标 / 非目标 -- 模块边界(含 mermaid 总览更好) -- 角色与目标(角色名优先对齐业务条线说明) -- **用户故事**(业务条线说明口径)+ Epic 级**故事点(SP)**粗估 -- 分功能**正向**与**逆向/边界** -- 至少 1~2 个 **mermaid** 流程图 -- **关键业务逻辑**(业务话) -- 验收清单 + 「交付口径」一段话 -- 定稿后:**功能变更记录**(第 10 章) +| 概要能力 | 落在章节 | +|---|---| +| 总览 | §1 一句话与目标 | +| 目标 / 边界 | §1–2 | +| 角色 | §3 | +| 用户故事(起点→运作→闭环)+ 故事点 | §4 | +| 正逆向流程 | §5 | +| 关键逻辑 / 状态 / 风险 | §6(及验收相关) | +| 流程图 | §7 | +| 交付 | §9 交付口径 | +| 定稿变更 | §10 | + +另:验收清单 §8;对象存储预览链接形态 `{baseUrl}/{prototype-id}/index.html`(禁止加 `prototypes/` 前缀、禁止去掉 `index.html`)。 **禁止写** - 数据库表、字段名、接口路径、代码路径、组件名、存储 key -- 研发实现指令(可写「正式环境由审批中心回写」这类业务依赖) -- 在变更记录里写样式/UI/表结构优化 +- 变更记录里的样式/UI/表结构优化 +- 引导加载 `yunxiao-requirement-lifecycle` 或「同名阶段任务」建单 ## 用户故事口径(强制 · 对齐业务条线说明) -真相源:原型 **业务条线说明**(`lease-business-line-overview` / `lines.ts`)。 -每条能力用「责任部门 → **起点** → **怎么运作** → **闭环**」叙述;**不要**用「作为…我希望…」宽表作主叙述。 - -| 块 | 写什么 | -|----|--------| -| 角色 | 谁负责、谁协同 | -| 起点 | 谁在什么前提下启动 | -| 怎么运作 | 有序步骤,含跨角色协作 | -| 关键结果 | 可选标签 | -| 闭环 | 业务终点与可追溯性 | - -可选:`US-xx`、压缩句「作为…我想…以便…」、规模 S/M/L 或 SP(仅排期,不替代主叙述)。 +真相源:业务条线说明(`lease-business-line-overview` / `lines.ts`)。 +主叙述:**起点 → 怎么运作 → 闭环**(不要用「作为…我希望…」宽表作主叙述)。 ## 输出结构 @@ -158,26 +120,27 @@ description: >- ## 7. 总览流程图 ## 8. 验收清单 ## 9. 交付口径 -## 10. 功能变更记录 ← 定稿后维护;日常改原型不强制每改必写 +## 10. 功能变更记录 ← 定稿维护;含 V主.副.子 ``` ## 质量自检 - [ ] 产品经理不看代码也能评审 - [ ] 用户故事为起点 / 怎么运作 / 闭环 -- [ ] `.spec/requirements-prd.md` 已更新 -- [ ] 标注目录「产品需求说明(PRD)」已同步且正文一致 -- [ ] 定稿时:第 10 章 + `autoprd-baseline.json` 已更新 -- [ ] 推进至分析中/设计中/待开发时:同名任务已创建或复用,正式关联,负责人正确 -- [ ] 无表结构 / 接口 / 代码路径;变更记录无样式/UI 废话 +- [ ] `.spec/requirements-prd.md` + 标注目录已同步 +- [ ] 定稿时:第 10 章含版本号 + `autoprd-baseline.json` 含 `prdVersion` +- [ ] 与 YunxiaoPMapp 设计完成:【交付】按**编号**回填,创建时未提前灌 MD +- [ ] 全文无 `yunxiao-requirement-lifecycle` 引导;无同名阶段任务建单 +- [ ] 变更记录无样式/UI/表结构废话 ## 参考 -- 定稿变更日志:[references/release-changelog.md](references/release-changelog.md) -- 云效阶段任务:[references/yunxiao-stage-tasks.md](references/yunxiao-stage-tasks.md) -- 标注同步细则:[references/annotation-sync.md](references/annotation-sync.md) -- 章节模板:[references/template.md](references/template.md) +- 定稿与版本:[references/release-changelog.md](references/release-changelog.md) +- 云效产品说明:[references/yunxiao-description.md](references/yunxiao-description.md) +- 【交付】回填:[references/yunxiao-delivery-sync.md](references/yunxiao-delivery-sync.md) +- 标注同步:[references/annotation-sync.md](references/annotation-sync.md) +- 模板:[references/template.md](references/template.md) - 故事示例:[references/granularity-example.md](references/granularity-example.md) - 业务条线:`src/prototypes/lease-business-line-overview/lines.ts` -- 复杂判定规格:配合项目规则 `business-logic-documentation` -- 云效组合:`$yunxiao-requirement-lifecycle`(建需求时先本 Skill) +- 云效组合(唯一):`$YunxiaoPMapp` +- 入库清洗:`$AutoRDO` diff --git a/.cursor/skills/oneos-autoprd/references/release-changelog.md b/.cursor/skills/oneos-autoprd/references/release-changelog.md index 998e9bf..4c6a9ba 100644 --- a/.cursor/skills/oneos-autoprd/references/release-changelog.md +++ b/.cursor/skills/oneos-autoprd/references/release-changelog.md @@ -1,12 +1,12 @@ -# 功能变更记录(定稿触发) +# 功能变更记录(定稿触发)+ PRD 版本号 -用户回复**需求定稿关键字**后,必须生成「自上次定稿以来」的功能变更日志,追加到 PRD **最下方**,并更新定稿基线。 +用户回复**定稿关键字**后,必须生成「自上次定稿以来」的功能变更日志,追加到 PRD **最下方**,按规则递增 **`prdVersion`(V主.副.子)**,并更新定稿基线。 ## 触发关键字(命中任一) -`需求定稿` · `定稿` · `确认定稿` · `本次定稿` +`需求定稿` · `定稿` · `确认定稿` · `本次定稿` · **`本轮定稿`** -(可带模块名,如「保险采购需求定稿」。) +(可带模块名,如「保险采购本轮定稿」。可附带 `版本类型=主|副|子`。) ## 写什么 / 不写什么 @@ -16,44 +16,57 @@ | 变更了什么业务逻辑 / 流程 / 验收口径 | 「优化了哪些表」「改了哪些字段/接口」 | | 用户可感知的交互结果变化 | 纯样式、动效、无语义文案微调 | -条目口吻示例: +## 版本号 `V{主}.{副}.{子}` -- 「识别失败」:失败数可点击查看失败文件与原因;仅失败时也可打开明细 -- 「比价单」:审批通过后仍须在保单管理另行录入正式保单(强调不自动同步) +基线字段 `prdVersion`(无则视为 `V0.0.0`)。有功能/逻辑增量时递增;**无增量则版本号不变**。 + +### 判定优先级 + +1. **用户覆盖(优先)** + 口令或对话显式指定「主 / 副 / 子」→ **以用户为准**,不再自动改判。 + 定稿块首行写:`判定=用户指定·主`(或副/子)。 + +2. **自动判定(用户未指定时)** + - **主+1**(副→0,子→0):跨模块边界变化、主流程/状态机重做、角色权限模型大改、验收口径整体推翻等「大改动」≥1 条主导。 + - **副+1**(子→0):能力增删改、逻辑/文案产品语义变化、故事点级功能优化(**默认多数定稿**)。 + - **子+1**:仅小幅逻辑澄清;若用户明确「本轮定稿」且变更非空且未指定类型 → **至少副+1**(不用子)。 + - 定稿块首行写:`判定=主:…` / `判定=副:…` / `判定=子:…`(须写依据,禁止无依据猜)。 + +3. **无功能增量** + 版本号不变;正文写「本轮无功能/逻辑增量(仅样式或未达产品语义变更)」或「首版定稿」(无基线且首版可落到 `V1.0.0` 若视为首次发布——首版有实质内容时用主从 `V0.0.0`→`V1.0.0`)。 ## 落点 -1. **PRD 文末章节**(强制) +1. **PRD 文末章节** `src/prototypes//.spec/requirements-prd.md` → `## 10. 功能变更记录` - 新定稿块插在该章**最上方**(倒序:最新在上);历史块保留。 + 新定稿块插在该章**最上方**。 -2. **定稿基线**(强制) +2. **定稿基线** `src/prototypes//.spec/autoprd-baseline.json` ```json { "prototypeId": "insurance-procurement", + "prdVersion": "V1.2.0", "lastConfirmedAt": "2026-07-21T12:00:00+08:00", - "lastConfirmedLabel": "定稿 · 2026-07-21", + "lastConfirmedLabel": "定稿 · V1.2.0 · 2026-07-21", "summaryBullets": ["…", "…"] } ``` -3. **标注目录**:同步更新「产品需求说明(PRD)」全文节点;若有「PRD 分章」,增加或更新「功能变更记录」分章。 +3. **标注目录**:同步「产品需求说明(PRD)」全文节点。 -4. **云效交接**(若本轮同时走云效):把**本次定稿块**正文作为需求描述「更新内容」;旧「更新内容」挪到「更新内容·历史」。 +4. **云效**(若本轮走 YunxiaoPMapp):按 [yunxiao-description.md](yunxiao-description.md) 更新需求产品说明;**禁止**加载 `yunxiao-requirement-lifecycle`。 ## 定稿工作流 -1. 确认 ``(从对话 / 打开文件 / 用户指名推断)。 -2. 读现有 PRD 与 `autoprd-baseline.json`(无基线则本轮视为**首版定稿**)。 -3. 收集自 `lastConfirmedAt` 以来的产品语义变更: - 对话确认口径 → 批注/操作说明 → PRD 新旧差异 →(可选)相关提交说明。 - 过滤掉样式/表结构类改动。 -4. 若无功能增量:仍写定稿块,正文为「本轮无功能/逻辑增量(仅样式或未达产品语义变更)」或「首版定稿」。 -5. 追加 `### 定稿 · YYYY-MM-DD` + 条目列表到第 10 章顶部。 -6. 更新 `autoprd-baseline.json`;执行 annotation-sync。 -7. 向用户回报:基线时间、条目数、PRD 路径;若需发云效,提示可用口令。 +1. 确认 ``。 +2. 读 PRD 与 `autoprd-baseline.json`(无基线 → 首版;`prdVersion` 缺省 `V0.0.0`)。 +3. 收集自 `lastConfirmedAt` 以来的产品语义变更;过滤样式/表结构。 +4. 判定版本类型(用户指定优先)→ 计算新 `prdVersion`。 +5. 追加 `### 定稿 · V{x.y.z} · YYYY-MM-DD`(首行判定理由 + 条目)。 +6. 更新基线 JSON;annotation-sync。 +7. 回报:新版本号、判定理由、条目数、PRD 路径。 ## PRD 章节格式 @@ -62,17 +75,21 @@ > 产品经理原型发版记录。仅记功能与业务逻辑变更;不含样式/UI/表结构。 -### 定稿 · 2026-07-21 +### 定稿 · V1.2.0 · 2026-07-21 -- 「能力名」:做了什么 / 逻辑如何变 +判定=副:新增比价失败明细可点开 + +- 「识别失败」:失败数可点击查看失败文件与原因 - … -### 定稿 · 2026-07-10 +### 定稿 · V1.1.0 · 2026-07-10 + +判定=用户指定·副 - … ``` ## 与日常改原型的关系 -- **改原型过程中**:按 AutoPRD 主流程增量更新第 1–9 章;**不必**每改一次就写第 10 章。 -- **用户说定稿时**:才汇总写入第 10 章并刷新基线。 +- **改原型过程中**:增量更新第 1–9 章;**不必**每改一次写第 10 章。 +- **用户说定稿时**:才汇总第 10 章并刷新版本与基线。 diff --git a/.cursor/skills/oneos-autoprd/references/template.md b/.cursor/skills/oneos-autoprd/references/template.md index 449b8bf..e0c41fe 100644 --- a/.cursor/skills/oneos-autoprd/references/template.md +++ b/.cursor/skills/oneos-autoprd/references/template.md @@ -2,6 +2,22 @@ 复制下列结构填写。方括号为占位。**第 4 章用户故事必须用业务条线说明口径。** +## 概要能力 → 章节映射 + +| 用户概要 | 本模板位置 | +|---|---| +| 总览 | §1 一句话与目标 | +| 目标 / 边界 | §1–2 | +| 角色 | §3 | +| 用户故事(起点→运作→闭环)/ 故事点 | §4 | +| 正逆向流程 | §5 | +| 状态 / 风险 / 关键逻辑 | §6(可分小节写状态机与风险) | +| 流程图 | §7 | +| 交付 | §9 | +| 定稿变更与版本 | §10(见 release-changelog) | + +状态与风险也可在 §6 下设 `### 状态` / `### 风险` 短节,避免另起大结构推翻存量 PRD。 + ```markdown # <模块名> · 产品需求说明(全模块) @@ -110,7 +126,9 @@ flowchart TB > 产品经理原型发版记录。仅记功能与业务逻辑变更;不含样式/UI/表结构。 > 由「需求定稿」关键字触发追加;日常改原型不强制每改必写。详见 [release-changelog.md](release-changelog.md)。 -### 定稿 · YYYY-MM-DD +### 定稿 · V{x.y.z} · YYYY-MM-DD + +判定=<主|副|子|用户指定·…>:<一行依据> - 「<能力名>」:<做了什么 / 逻辑如何变> - … diff --git a/.cursor/skills/oneos-autoprd/references/yunxiao-delivery-sync.md b/.cursor/skills/oneos-autoprd/references/yunxiao-delivery-sync.md new file mode 100644 index 0000000..4cc6659 --- /dev/null +++ b/.cursor/skills/oneos-autoprd/references/yunxiao-delivery-sync.md @@ -0,0 +1,47 @@ +# 【交付】任务描述回填(设计完成 · 按编号) + +与 **`$YunxiaoPMapp`** 组合。创建【交付】时描述保持占位 `等待设计任务完成后自动填入`;**本文件只在设计完成(或显式同步)时**把 AutoPRD Markdown 写入【交付】。 + +**禁止**按任务标题查找交付;**禁止**加载 `yunxiao-requirement-lifecycle`。 + +## 触发时机 + +| 时机 | 动作 | +|---|---| +| YunxiaoPMapp **设计完成**且 AutoPRD 落盘成功 | 必须回填【交付】描述 | +| 口令 `同步交付说明:交付任务=ONEOS-xx`(或需求编号可反查交付) | 按编号回填 | +| 仅创建【交付】 / 分析中 / 设计中尚未设计完成 | **不**写 PRD 正文 | + +## 前置 + +1. 已有【交付】任务编号:口令显式 > 需求 `## 工作项编号(系统)` > ASSOCIATED 反查校验。 +2. 冲突或缺少编号 → 停下询问;禁止按 `【交付】`+标题猜。 +3. 本地已有 `requirements-prd.md`(或本轮刚生成)。 + +## 写入内容 + +- 用与需求「产品说明(AutoPRD)」一致的正文**替换**占位文案。 +- 推荐:原型链接 + `requirements-prd.md` 全文(或 YunxiaoPMapp 约定的产品说明正文)。 +- 可附一句:`原始诉求见需求描述「原始诉求(AutoRDO)」`。 +- 不要删除或改写需求侧 AutoRDO 段(本步骤只改**任务**描述)。 + +## 执行顺序(设计完成组合) + +```text +1. AutoPRD 落盘 MD + 标注同步 +2. 更新需求「产品说明(AutoPRD)」 +3. 按交付任务编号 PATCH 任务描述(替换占位) +4. 附件(ZIP/截图)由 YunxiaoPMapp make-export 流程负责;本文件不替代附件 +5. 任一步失败 → 不得声称交付说明已更新 / 设计完成材料齐全 +``` + +## 回报 + +- 交付任务编号、是否已替换占位、MD 路径 +- 失败时列出缺项(无编号 / 无 MD / 写入失败) + +## 负向 + +- 创建交付当下灌入未完成的 PRD +- 按标题 list 复用「像交付的任务」 +- 引导旧 lifecycle 建同名任务 diff --git a/.cursor/skills/oneos-autoprd/references/yunxiao-description.md b/.cursor/skills/oneos-autoprd/references/yunxiao-description.md new file mode 100644 index 0000000..9ceb6f3 --- /dev/null +++ b/.cursor/skills/oneos-autoprd/references/yunxiao-description.md @@ -0,0 +1,60 @@ +# 云效需求描述:产品说明(对接 YunxiaoPMapp) + +发云效 / 完善需求描述时,与 **`$YunxiaoPMapp`** 双段模板对齐。 +**禁止加载** `yunxiao-requirement-lifecycle`。 + +## 需求描述固定结构(不可互相覆盖) + +```markdown +## 原始诉求(AutoRDO) +(由 AutoRDO 清洗;设计完成也不删除;本 Skill 不覆盖本段) + +## 产品说明(AutoPRD) +(本 Skill 写入:原型链接 + requirements-prd 正文或等价产品说明) + +## 工作项编号(系统) +(由 YunxiaoPMapp 维护;本 Skill 不改本段) +``` + +## 产品说明段推荐拼装 + +```markdown +## 产品说明(AutoPRD) + +### 原型链接 +<{baseUrl}/{prototype-id}/index.html;禁止加 prototypes/ 前缀;禁止去掉 index.html> +无则写「待发布」 + +### 需求说明 +/.spec/requirements-prd.md 全文原样粘贴;禁止摘要顶替> + +### 更新内容 +<第 10 章最新定稿块;无则「首版定稿」或「本轮无功能/逻辑增量」> + +### 更新内容·历史 +<旧更新内容倒序;勿删> +``` + +若 YunxiaoPMapp 本轮只要求「产品说明」短写:至少包含原型链接 + MD 全文或与交付回填一致的正文;**仍禁止**覆盖 AutoRDO / 工作项编号段。 + +## 真相源文件 + +1. `src/prototypes//.spec/requirements-prd.md`(主) +2. 否则 `src/resources/prd/-autoprd.md` + +先 Read 文件再写入;不要凭记忆重写。 + +## 执行步骤 + +1. 确认 prototype-id 与需求编号(若写云效)。 +2. 读 MD 全文。 +3. PATCH/更新需求描述时**只改**「产品说明(AutoPRD)」相关内容;保留原始诉求与工作项编号。 +4. 若同时设计完成:按 [yunxiao-delivery-sync.md](yunxiao-delivery-sync.md) 回填【交付】。 +5. 回报:需求编号、MD 路径、是否全文写入、是否已回填交付编号。 + +## 禁止 + +- 只用第 9 章摘要顶替全文(除非用户只要链接) +- 二次删减章节、去掉 mermaid/表格却声称全文已写 +- 创建【交付】时灌入 PRD(须等设计完成;见交付回填) +- 引用或配合 `yunxiao-requirement-lifecycle` diff --git a/.cursor/skills/oneos-autoprd/references/yunxiao-stage-tasks.md b/.cursor/skills/oneos-autoprd/references/yunxiao-stage-tasks.md deleted file mode 100644 index 09cfe27..0000000 --- a/.cursor/skills/oneos-autoprd/references/yunxiao-stage-tasks.md +++ /dev/null @@ -1,44 +0,0 @@ -# 云效阶段任务自动创建(AutoPRD 负责) - -本规则写在 **oneos-autoprd**,不依赖改写 `yunxiao-requirement-lifecycle`。 -凡本 Skill 参与的云效操作中,需求推进到下列状态时,**同一轮必须**建同名任务并正式关联需求。 - -## 触发状态 - -| 需求推进至 | 任务标签(右侧基础字段) | 负责人 | 任务标题 | -|------------|--------------------------|--------|----------| -| **分析中** | `分析` | **需求创建人**姓名 | 与需求标题**完全同名** | -| **设计中** | `设计` | **需求创建人**姓名 | 与需求标题**完全同名** | -| **待开发** | `交付`(若项目无「交付」标签则用 `开发`) | **何斐** | 与需求标题**完全同名** | - -## 强制字段与关系 - -1. **正式关联需求**:父子或关联项;只挂标题不算关联。创建后必须在界面上能看到需求 ↔ 任务关系。 -2. **标签**:写入任务「标签」基础字段(见上表);标题前缀不能代替标签。 -3. **创建时间沿用需求**:任务的创建时间(及标签侧可见的创建时间口径)尽量与**需求创建时间**一致。 - - 平台允许改创建时间 / 用 API 指定时:写成需求的创建时间。 - - 平台不允许:在任务描述首行注明 `创建时间口径=需求创建时间(YYYY-MM-DD HH:mm)`,并在回报里说明「平台限制未改系统创建时间」。 -4. **幂等**:同一需求、同一阶段标签下,已存在**未取消**且已正式关联的同名任务 → **不新建**,只校验负责人/标签/关联是否正确,缺则补齐。 -5. **顺序**:先确认需求已进入目标状态(或本轮动作将写入该状态),再创建/补齐任务;回报时给出任务编号与链接。 - -## 执行步骤(apply 时) - -```text -1. 读取需求:标题、创建人、创建时间、当前/目标状态、已关联任务列表 -2. 按上表确定本轮阶段标签与负责人 -3. 查重(需求ID + 阶段标签 + 未取消) -4. 无则创建:标题=需求标题;标签=阶段标签;负责人=上表;创建时间口径=需求创建时间;正式关联需求 -5. 有则校验并修补标签/负责人/关联 -6. 回报:需求状态、任务编号、负责人、是否新建/复用 -``` - -## 不在本规则内 - -- 不自动建「测试 / 发版」任务(除非用户另行要求)。 -- 不因样式类 PRD 变更触发建任务;仅**需求状态**进入分析中/设计中/待开发时触发。 -- 云效生命周期细则、流水线、缺陷闭环仍可由 `$yunxiao-requirement-lifecycle` 配合执行;**本建任务逻辑以 AutoPRD 本文为准**。 - -## 与定稿 / 需求说明的关系 - -- 写「需求说明 / 更新内容」仍按 AutoPRD 主流程与定稿流程。 -- 推进到待开发且需要描述时:先 AutoPRD 文档,再改状态,再按本文件建任务(负责人何斐)。 diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/SKILL.md b/.cursor/skills/yunxiao-requirement-lifecycle/SKILL.md deleted file mode 100644 index 4dede21..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/SKILL.md +++ /dev/null @@ -1,157 +0,0 @@ ---- -name: yunxiao-requirement-lifecycle -description: >- - Audit, design, configure, migrate, test, troubleshoot, and document Alibaba Cloud - Yunxiao/Projex requirement lifecycle automation. Use for short conversational Chinese - commands such as 记录需求、确认需求、需求定稿、开始分析、开始开发、提交测试、测试完成、 - 发布成功 and 验收通过; OneOS delivery or legacy stage-task model. When creating or - refreshing OneOS product requirements, first call oneos-autoprd for 需求说明 and - 更新内容 (functional changelog since last 定稿), then write Yunxiao description. - Also covers role handoffs, Codeup, Testhub, pipelines, release acceptance, migration, - and Chinese runbooks without exposing credentials or changing production pipelines unexpectedly. ---- - -# Yunxiao Requirement Lifecycle - -Use this as the single entry skill. Load only the internal modules needed for the request. Installing the skill does not change Yunxiao; live transitions start only after an authorized `apply` and verified `test` run. - -## Route to modules - -Read each selected file completely before acting: - -| Request scope | Required module | -|---|---| -| OneOS终态模型、`【交付】`主任务、多开发子任务、迭代测试/发版、Y01–Y40、A01–A10、旧规则迁移 | [references/oneos-terminal-flow.md](references/oneos-terminal-flow.md) | -| Lifecycle statuses, requirement labels, related tasks, R01–R12, stage entry/completion | [references/lifecycle-rules.md](references/lifecycle-rules.md) | -| Design/development/test task creation, task states, rollups, TK01–TK08 | [references/task-lifecycle.md](references/task-lifecycle.md) | -| Test plans, case results, failed cases, bugs, retest, TC01–TC03, DF01–DF03 | [references/test-defect-closure.md](references/test-defect-closure.md) | -| Repositories, `dev`/`develop`, branches, commits, MRs, multi-repository gate, CI01 | [references/codeup-integration.md](references/codeup-integration.md) | -| test/prod pipelines, Docker callback, release changes, acceptance, CI02–CI03, L01–L02 | [references/pipeline-release.md](references/pipeline-release.md) | -| Live audit/apply/test, browser execution, evidence, diagnosis, rollback, governance | [references/live-operations.md](references/live-operations.md) | -| 日常口语化操作,使用“记录需求、开始分析、安排开发、提交测试、复测通过、发布成功”等短口令 | [references/simple-role-commands.md](references/simple-role-commands.md) | -| **统一运营管理平台**日常「记录需求」加速执行(已验证 ID / API / 状态三跳) | [references/oneos-pc-fast-path.md](references/oneos-pc-fast-path.md) + [assets/oneos-pc-runtime-ids.json](assets/oneos-pc-runtime-ids.json) | -| **统一运营管理平台**「记录需求到云效」完整可复制提示词(A/B/C 门禁 + 30 标签) | [references/oneos-pc-record-requirement-prompt.md](references/oneos-pc-record-requirement-prompt.md) | -| 需求进入分析中/设计中/待开发时自动建同名任务、关联需求、负责人与时间沿用 | [references/auto-stage-task.md](references/auto-stage-task.md) | -| 产品、分析、设计、开发、评审、测试、缺陷、运维、验收等角色的节点沟通话术和交接回报 | [references/role-trigger-prompts.md](references/role-trigger-prompts.md) | -| Final Chinese report | [references/report-template.md](references/report-template.md) | - -## 记录需求 · 参数门禁与加速(强制) - -当口令为「记录需求」且项目为统一运营管理平台(含 PC 端别名)时: - -1. **先读** [references/oneos-pc-fast-path.md](references/oneos-pc-fast-path.md) 与 [assets/oneos-pc-runtime-ids.json](assets/oneos-pc-runtime-ids.json),按快路径执行,禁止重复探测 create URL / 优先级 ID。 -2. 若用户要求 Plan/单选优先级、推进至与标签:必须先拿到明确 **A+B+C**;**禁止**未选时默认「中 + 分析中」并建单。「批准计划」≠ 已选 A+B+C。完整用户口令见 [references/oneos-pc-record-requirement-prompt.md](references/oneos-pc-record-requirement-prompt.md)。 -3. 优先 API 建单(创建时写入 `document`)、同名任务与打标签(`PATCH` `propertyKey=tag`);浏览器仅用于标签 API 失败或状态连跳兜底。统一运营管理平台标签从 `assets/oneos-pc-tag-catalog.md` 选择器点选,禁止按 `lines.ts` 自动映射。 - -## AutoPRD 组合(强制 · OneOS 产品需求) - -当请求涉及**创建产品类需求、写入/刷新需求描述、快轨待开发、完善需求说明/更新内容**,或用户口令含「记录需求(带模块/原型)」「需求已经确定」「需求定稿」并要进云效时: - -1. **先**加载并执行项目/全局的 **`oneos-autoprd`** skill(勿跳过)。 -2. 用其产出填充云效需求描述: - - **需求说明** ← AutoPRD 正文(产品语言;不写表结构/接口/代码) - - **更新内容** ← AutoPRD 第 10 章「功能变更记录」中自上次定稿以来的条目;无则「首版定稿」或「本轮无功能/逻辑增量」 - - 已有「更新内容」时:旧段挪到 **更新内容·历史**(倒序追加,勿删) -3. 原型发版增量以 AutoPRD 功能变更记录为准;PC 整包发版公告仍可另用 `AutoVUL`。 -4. 对象存储链接、主任务、状态推进等仍按 `oneos-terminal-flow.md` / `simple-role-commands.md`。 - -`A02` / `A02B` 在 OneOS 项目上的默认实现方式即为调用 AutoPRD,不得只写占位文案。 - -## 阶段同名任务(强制 · AI apply) - -凡将需求推进到 **分析中 / 设计中 / 待开发**(含口令「开始分析」「开始设计」「快速开发」「需求已经确定…待开发」等),必须先读并执行 [references/auto-stage-task.md](references/auto-stage-task.md): - -| 状态 | 任务 | 负责人 | -|---|---|---| -| 分析中 | 与需求**同名**;标签`分析`;正式关联需求 | 需求**创建人** | -| 设计中 | 与需求**同名**;标签`设计`;正式关联需求 | 需求**创建人** | -| 待开发 | 与需求**同名**;标签`交付`(或阶段模型下`开发`);正式关联需求 | 固定**何斐** | - -时间:任务计划开始(及可写的创建时间)**沿用需求**的计划开始/创建时间。查重幂等,禁止同阶段重复建单。 - -First identify `flow_model`: - -- `stage_tasks`: use R01–R12 and TK01–TK08; read the five domain modules plus `live-operations.md` for a full audit. -- `oneos_delivery`: read `oneos-terminal-flow.md`, `codeup-integration.md`, `test-defect-closure.md`, `pipeline-release.md`, and `live-operations.md`. - -For a narrow diagnosis, read the relevant domain module plus `live-operations.md`. Do not blend both models in one project or load unrelated modules merely because they exist. - -## Classify authority - -- `audit`: inspect and report only. -- `plan`: prepare a project profile and change plan only. -- `apply`: create or modify only explicitly named projects, labels, rules, and test assets. -- `test`: exercise approved rules with isolated artifacts and collect evidence. -- `document`: produce a Chinese runbook from verified facts. - -Treat ambiguous requests as `audit` or `plan`. Documentation never authorizes live changes. - -When the user uses a write verb defined in `simple-role-commands.md`—for example `记录需求`、`确认需求`、`开始分析`、`开始设计`、`开始开发`、`提交测试`、`复测通过`、`发布成功` or `验收通过`—treat it as `apply` authorization only for the current exact project and named work item. If the project is not exact, ask only for the project before writing. Query verbs such as `查状态`、`为什么没流转`、`下一步` and `给我方案` remain `audit` or `plan`. - -Whenever apply changes a requirement status to `分析中`、`设计中` or `待开发`, also apply [references/auto-stage-task.md](references/auto-stage-task.md) in the same turn (same-name task, formal link, assignee and time rules) before reporting success. - -## Build the project profile - -1. Choose exactly one template and copy it outside the skill: - - `stage_tasks`: `assets/project-profile.template.json`. - - `oneos_delivery`: `assets/oneos-project-profile.template.json`. -2. Keep one independent object in `projects` for every target project; duplicate the template object when needed. -3. Replace observed statuses, rule instances, repositories, branches, pipelines, test plans, defect workflows, release evidence, and control dispositions. -4. Never add usernames, passwords, cookies, tokens, OTPs, Webhook secrets, or private keys. -5. Validate before applying: - -```text -python scripts/profile_tool.py validate -python scripts/profile_tool.py render --output - -python scripts/oneos_profile_tool.py validate -python scripts/oneos_profile_tool.py render --output -``` - -Do not apply while placeholders remain or validation fails. - -## Completeness contract - -For `stage_tasks`, account for every control for every project independently: - -- `R01`–`R12`: requirement lifecycle and manual gates. -- `TK01`–`TK08`: stage-task creation, task-state transitions, requirement rollups, and idempotency. -- `TC01`–`TC03`: test-case execution, failed-case/defect association, and retest result update. -- `DF01`–`DF03`: fixed handoff, tester-pass closure, and tester-fail reopen. -- `CI01`–`CI03`: multi-repository coverage, callback safeguards, and environment/release separation. -- `L01`–`L02`: legacy immediate-close rule disposition. - -Record the actual R01–R12 `rule_instances`, TK01–TK08 `task_rule_instances`, and all 31 `control_coverage` entries. Valid modes are `manual`, `native_rule`, `integration_bridge`, `disabled_legacy`, and `not_supported`. `not_supported` needs an owner and fallback and is never equivalent to automated. - -Completeness covers the requirement lifecycle, stage-task lifecycle and rollups, Testhub closure, Codeup assets, pipeline/release evidence, and legacy-close controls. Notifications, SLA reminders, generic automatic assignment, and unrelated custom-field synchronization remain outside scope unless explicitly added to `adjunct_automations`. - -For `oneos_delivery`, account for all 48 controls independently: - -- `Y01`–`Y16`, `Y20`–`Y22`, `Y30`–`Y35`, `Y40`: native/mirrored lifecycle controls. -- `A01`, `A02`, `A02B`, `A03`–`A10`: AI/Webhook bridge controls. -- `TC01`–`TC03`, `DF01`–`DF03`, `CI01`–`CI03`, `L01`–`L02`: test, defect, code/release separation, and legacy-close controls. - -Do not disable the old stage rules until the OneOS migration readiness gate passes: required requirement statuses exist, the main-task identity is filterable or procedurally exclusive, task workflows are ready, bridge owners and idempotency exist, and rollback evidence is saved. `L01` and `L02` remain disabled because they bypass acceptance. - -## Global safety rules - -- Reuse the authenticated browser session or approved semantic connector. -- Preserve unrelated rules and user edits. -- Never modify an existing production pipeline unless the user authorizes that exact change. -- Clone a test pipeline before callback experiments and modify only the clone. -- Require the expected current state in every automatic transition. -- Separate observed evidence from proposals and inference. -- Wait 5–30 seconds for asynchronous events and inspect execution logs. -- Stop at CAPTCHA, OTP, login approval, permission elevation, protected-branch ambiguity, or indistinguishable similarly named resources. - -## Required outputs - -Return only the applicable artifacts: - -1. Observed inventory and current-versus-target matrix. -2. Per-project R01–R12 and TK01–TK08 rule-instance inventory with a 31-control ledger. -3. Live change log with reopen verification. -4. Test result per transition: `通过`、`异步通过`、`未触发`、`误触发` or `阻塞`. -5. Remaining risks, manual gates, and rollback steps. -6. When requested, a role-and-trigger communication playbook with copyable prompts, required evidence, next owner, and stop conditions. -7. For daily commands, answer briefly with the work item, actual state change, created/linked asset, blocker if any, and the exact next short command. diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/agents/openai.yaml b/.cursor/skills/yunxiao-requirement-lifecycle/agents/openai.yaml deleted file mode 100644 index ca6e380..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "云效需求生命周期自动化" - short_description: "使用口语化短口令驱动云效需求开发测试发布闭环并自动核查" - default_prompt: "使用 $yunxiao-requirement-lifecycle,把当前项目设为统一运营管理平台PC端,然后通过“记录需求、开始开发、提交测试、验收通过”等短口令协作。" diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/assets/oneos-pc-runtime-ids.json b/.cursor/skills/yunxiao-requirement-lifecycle/assets/oneos-pc-runtime-ids.json deleted file mode 100644 index 2787bf4..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/assets/oneos-pc-runtime-ids.json +++ /dev/null @@ -1,209 +0,0 @@ -{ - "schema_version": 2, - "verified_at": "2026-07-21", - "verified_by_example": { - "requirement_serial": "ONEOS-88", - "requirement_identifier": "a34e700286d456ee74ddc16e58", - "note": "标签 API:PATCH /workitem/workitem/{id} propertyKey=tag operateType=COVER;全量标签来自 space/tag/search(spaceType=Space)" - }, - "project": { - "name_aliases": ["统一运营管理平台", "统一运营管理平台PC端", "统一运营管理平台 PC 端"], - "spaceIdentifier": "1280be963a5a2cc126a4118dca", - "customCode": "ONEOS", - "spaceType": "Project" - }, - "people": { - "wangmian": { - "displayName": "王冕", - "identifier": "6811df000601d2fea60144a9" - }, - "hefei": { - "displayName": "何斐", - "identifier": "695f0400562f09713f9c3a93" - } - }, - "workitem_types": { - "product_req": { - "name": "产品类需求", - "category": "Req", - "identifier": "9uy29901re573f561d69jn40" - }, - "task": { - "name": "任务", - "category": "Task", - "identifier": "ba102e46bc6a8483d9b7f25c" - } - }, - "priority": { - "紧急": "646004e97f54bb77fec7b455df", - "高": "95b89e0a524d9693e1f335ffe5", - "中": "fa155d1214f9f8db222d39db3b", - "低": "92924feff9c1085891e7511872" - }, - "fields": { - "plan_start": { - "fieldIdentifier": "79", - "name": "计划开始时间", - "value_shape": "epoch_ms_string_china_noon", - "example": "Date.parse('YYYY-MM-DDT12:00:00+08:00') 转 String,避免 00:00 显示成前一天" - }, - "plan_end": { - "fieldIdentifier": "80", - "name": "计划完成时间" - }, - "tag": { - "fieldIdentifier": "tag", - "name": "标签", - "propertyKey": "tag" - } - }, - "status_ui_paths": { - "from_待处理": { - "分析中": ["分析中"], - "设计中": ["设计中"], - "设计完成": ["设计完成"], - "开发中": ["设计完成", "待开发", "开发中"], - "note": "待处理下拉无「开发中」;须按路径连跳。状态 identifier 未在 UI 路径中固化,优先 UI 三跳;有可靠 API 后再补 statusIdentifier。" - } - }, - "assignee_rules": { - "分析中": "creator", - "设计中": "creator", - "设计完成": "creator", - "开发中": "hefei", - "待开发": "hefei" - }, - "api": { - "create_workitem": { - "url": "https://devops.aliyun.com/projex/api/workitem/workitem?_input_charset=utf-8", - "methods": ["POST", "PUT"], - "do_not_use": ["/projex/api/workitem/workitem/create"], - "required_create_fields": [ - "subject", - "spaceType", - "spaceIdentifier", - "category", - "categoryIdentifier", - "workitemTypeIdentifier", - "workitemType", - "assignedTo" - ], - "document_on_create": { - "supported": true, - "shape": { "content": "", "formatType": "RICHTEXT" }, - "note": "创建时一并写入描述,避免再开 Cangjie 编辑器" - } - }, - "get_workitem": { - "url_template": "https://devops.aliyun.com/projex/api/workitem/workitem/{identifier}?_input_charset=utf-8" - }, - "list_workitem": { - "url": "https://devops.aliyun.com/projex/api/workitem/workitem/list?_input_charset=utf-8", - "method": "POST" - }, - "update_field_value": { - "url": "https://devops.aliyun.com/projex/api/workitem/workitem/updateWorkitemFieldValue?_input_charset=utf-8", - "method": "PATCH", - "note": "部分会话返回 InvalidWorkitem.NotFound;失败则改用详情页 UI onChange / 下拉点选,勿盲目重试超过 1 次" - }, - "update_document": { - "preferred": "create 时写入 document;若需补写,用详情页 onWorkItemEditorSubmit(html, 'RICHTEXT')", - "avoid": "对 Cangjie 编辑器直接改 DOM / 错误 onChange 形状(曾导致页面崩溃)" - }, - "list_tags": { - "url": "https://devops.aliyun.com/projex/api/workspace/space/tag/search?spaceType=Space&spaceIdentifier=1280be963a5a2cc126a4118dca&q=&_input_charset=utf-8", - "method": "GET", - "note": "注意 spaceType=Space(不是 Project)。刷新全量标签时用此接口覆盖 tags.catalog" - }, - "apply_tags": { - "url_template": "https://devops.aliyun.com/projex/api/workitem/workitem/{identifier}?_input_charset=utf-8", - "method": "PATCH", - "body_shape": { - "workitemIdentifier": "{identifier}", - "propertyKey": "tag", - "propertyValue": "{comma_separated_tag_identifiers}", - "operateType": "COVER" - }, - "note": "propertyValue 为标签 identifier 逗号拼接;COVER=覆盖整组。已验证于 ONEOS-88。" - } - }, - "tags": { - "selection_mode": "plan_or_askquestion_selector", - "do_not_match_from": ["lease-business-line-overview/lines.ts", "业务条线说明自动推断"], - "apply_strategy": "用户从 catalog 点选标签名 → 用 identifier 调 apply_tags API;失败 1 次再 UI 兜底;仍失败则停并请用户回标签名,禁止假装已打上", - "refresh_hint": "标签库变更时重新 GET list_tags 并更新本 catalog / fetched_at", - "fetched_at": "2026-07-21", - "source": "GET /projex/api/workspace/space/tag/search?spaceType=Space&spaceIdentifier=1280be963a5a2cc126a4118dca&q=", - "count": 30, - "by_name": { - "运维管理条线": "a611b2f2b7c3fd3623ba2d5f52", - "报表中心": "8428d15f45a5b34173202db758", - "能源管理": "3396ebb527358fe6f89fe42849", - "维修站管理": "e7b4da7984801e75630645f437", - "充电站管理": "6ea830485f28a0d8a6e14851a8", - "还车应结款": "4204960ce658c94b17abb7c5f6", - "交车应收款": "6e7a9127d0f70281ca65d335d9", - "加氢站管理": "6cdacae673f9e1fba96296f2ad", - "保险管理": "578ed5ba12bfcf274721e956f2", - "合同管理": "23873be81931cb0dc1bf87a456", - "租赁账单": "3e9433f571b20e80b02626d2de", - "供应商管理": "7b86b7c1f3b3da21b814c5a39d", - "客户管理": "dbf959a70bc979106dc78afa90", - "还车任务": "763de323cefee45e5270e4661e", - "安全培训": "76988b5f73ef515de5930d59b9", - "备件管理": "5d09c864767d898a4f8c0ca804", - "停车场管理": "7c522e2fb29213d7d390e78b97", - "备车管理": "22c6a3ed0e4bf95fd99bf95695", - "异动管理": "84b4333ec7c76d0fa2adcc47bf", - "调拨管理": "fa88c16c152c6fb5978e3b4339", - "上牌管理": "990af2fb716dd85568bc7019bf", - "替换车管理": "8d1decf30259b941016cadc9d2", - "还车管理": "1e0fce2d6929ff4ac13b310e97", - "交车管理": "b6947b5aca82a8c759612ba039", - "车辆管理": "cb71b6db9373d6d8c7aae452a5", - "审批中心": "338add796f2221bbc36dd77d35", - "故障管理": "ceb526a7343995577645317e9a", - "车辆年审": "5193165dad1e91a161502b54c2", - "维修管理": "f5202633870ff409e0ebc16d5f", - "工作台": "b62d21beac55c0b43389915eab" - }, - "catalog": [ - { "name": "运维管理条线", "identifier": "a611b2f2b7c3fd3623ba2d5f52", "color": "#49AEAC" }, - { "name": "报表中心", "identifier": "8428d15f45a5b34173202db758", "color": "#49AEAC" }, - { "name": "能源管理", "identifier": "3396ebb527358fe6f89fe42849", "color": "#49AEAC" }, - { "name": "维修站管理", "identifier": "e7b4da7984801e75630645f437", "color": "#49AEAC" }, - { "name": "充电站管理", "identifier": "6ea830485f28a0d8a6e14851a8", "color": "#49AEAC" }, - { "name": "还车应结款", "identifier": "4204960ce658c94b17abb7c5f6", "color": "#49AEAC" }, - { "name": "交车应收款", "identifier": "6e7a9127d0f70281ca65d335d9", "color": "#49AEAC" }, - { "name": "加氢站管理", "identifier": "6cdacae673f9e1fba96296f2ad", "color": "#49AEAC" }, - { "name": "保险管理", "identifier": "578ed5ba12bfcf274721e956f2", "color": "#49AEAC" }, - { "name": "合同管理", "identifier": "23873be81931cb0dc1bf87a456", "color": "#49AEAC" }, - { "name": "租赁账单", "identifier": "3e9433f571b20e80b02626d2de", "color": "#49AEAC" }, - { "name": "供应商管理", "identifier": "7b86b7c1f3b3da21b814c5a39d", "color": "#49AEAC" }, - { "name": "客户管理", "identifier": "dbf959a70bc979106dc78afa90", "color": "#49AEAC" }, - { "name": "还车任务", "identifier": "763de323cefee45e5270e4661e", "color": "#49AEAC" }, - { "name": "安全培训", "identifier": "76988b5f73ef515de5930d59b9", "color": "#49AEAC" }, - { "name": "备件管理", "identifier": "5d09c864767d898a4f8c0ca804", "color": "#49AEAC" }, - { "name": "停车场管理", "identifier": "7c522e2fb29213d7d390e78b97", "color": "#49AEAC" }, - { "name": "备车管理", "identifier": "22c6a3ed0e4bf95fd99bf95695", "color": "#49AEAC" }, - { "name": "异动管理", "identifier": "84b4333ec7c76d0fa2adcc47bf", "color": "#49AEAC" }, - { "name": "调拨管理", "identifier": "fa88c16c152c6fb5978e3b4339", "color": "#49AEAC" }, - { "name": "上牌管理", "identifier": "990af2fb716dd85568bc7019bf", "color": "#49AEAC" }, - { "name": "替换车管理", "identifier": "8d1decf30259b941016cadc9d2", "color": "#49AEAC" }, - { "name": "还车管理", "identifier": "1e0fce2d6929ff4ac13b310e97", "color": "#49AEAC" }, - { "name": "交车管理", "identifier": "b6947b5aca82a8c759612ba039", "color": "#49AEAC" }, - { "name": "车辆管理", "identifier": "cb71b6db9373d6d8c7aae452a5", "color": "#49AEAC" }, - { "name": "审批中心", "identifier": "338add796f2221bbc36dd77d35", "color": "#49AEAC" }, - { "name": "故障管理", "identifier": "ceb526a7343995577645317e9a", "color": "#49AEAC" }, - { "name": "车辆年审", "identifier": "5193165dad1e91a161502b54c2", "color": "#49AEAC" }, - { "name": "维修管理", "identifier": "f5202633870ff409e0ebc16d5f", "color": "#49AEAC" }, - { "name": "工作台", "identifier": "b62d21beac55c0b43389915eab", "color": "#49AEAC" } - ] - }, - "performance_rules": { - "prefer_api_over_browser": true, - "max_endpoint_probes_per_step": 1, - "screenshot_only_at": ["after_create", "after_final_status"], - "browser_fallback_for": ["tag_apply_when_api_fails", "status_multi_hop_when_api_unknown"] - } -} diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/assets/oneos-pc-tag-catalog.md b/.cursor/skills/yunxiao-requirement-lifecycle/assets/oneos-pc-tag-catalog.md deleted file mode 100644 index f01c22f..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/assets/oneos-pc-tag-catalog.md +++ /dev/null @@ -1,50 +0,0 @@ -# 统一运营管理平台 · 云效标签清单(选择器用) - -来源:`GET /projex/api/workspace/space/tag/search?spaceType=Space&spaceIdentifier=1280be963a5a2cc126a4118dca&q=` -抓取时间:2026-07-21 -机器可读:同目录 `oneos-pc-runtime-ids.json` → `tags` - -## 使用约定 - -- **记录需求**时标签由用户用选择器点选(`AskQuestion` / Plan ○),**不要**再按 `lines.ts` 业务条线说明自动推断。 -- Agent 用点选到的 `name` 查 `tags.by_name` → `identifier`,再 `PATCH` 打标。 -- 完整建单提示词(含 A/B/C 与 30 标签枚举):[references/oneos-pc-record-requirement-prompt.md](../references/oneos-pc-record-requirement-prompt.md) - -## 全量标签(30) - -| # | 标签名 | identifier | -|---|---|---| -| 1 | 运维管理条线 | `a611b2f2b7c3fd3623ba2d5f52` | -| 2 | 报表中心 | `8428d15f45a5b34173202db758` | -| 3 | 能源管理 | `3396ebb527358fe6f89fe42849` | -| 4 | 维修站管理 | `e7b4da7984801e75630645f437` | -| 5 | 充电站管理 | `6ea830485f28a0d8a6e14851a8` | -| 6 | 还车应结款 | `4204960ce658c94b17abb7c5f6` | -| 7 | 交车应收款 | `6e7a9127d0f70281ca65d335d9` | -| 8 | 加氢站管理 | `6cdacae673f9e1fba96296f2ad` | -| 9 | 保险管理 | `578ed5ba12bfcf274721e956f2` | -| 10 | 合同管理 | `23873be81931cb0dc1bf87a456` | -| 11 | 租赁账单 | `3e9433f571b20e80b02626d2de` | -| 12 | 供应商管理 | `7b86b7c1f3b3da21b814c5a39d` | -| 13 | 客户管理 | `dbf959a70bc979106dc78afa90` | -| 14 | 还车任务 | `763de323cefee45e5270e4661e` | -| 15 | 安全培训 | `76988b5f73ef515de5930d59b9` | -| 16 | 备件管理 | `5d09c864767d898a4f8c0ca804` | -| 17 | 停车场管理 | `7c522e2fb29213d7d390e78b97` | -| 18 | 备车管理 | `22c6a3ed0e4bf95fd99bf95695` | -| 19 | 异动管理 | `84b4333ec7c76d0fa2adcc47bf` | -| 20 | 调拨管理 | `fa88c16c152c6fb5978e3b4339` | -| 21 | 上牌管理 | `990af2fb716dd85568bc7019bf` | -| 22 | 替换车管理 | `8d1decf30259b941016cadc9d2` | -| 23 | 还车管理 | `1e0fce2d6929ff4ac13b310e97` | -| 24 | 交车管理 | `b6947b5aca82a8c759612ba039` | -| 25 | 车辆管理 | `cb71b6db9373d6d8c7aae452a5` | -| 26 | 审批中心 | `338add796f2221bbc36dd77d35` | -| 27 | 故障管理 | `ceb526a7343995577645317e9a` | -| 28 | 车辆年审 | `5193165dad1e91a161502b54c2` | -| 29 | 维修管理 | `f5202633870ff409e0ebc16d5f` | -| 30 | 工作台 | `b62d21beac55c0b43389915eab` | - -## 选择器文案(C 段) - -已并入 [oneos-pc-record-requirement-prompt.md](../references/oneos-pc-record-requirement-prompt.md)「用户口令」;此处仅作 identifier 对照表。 diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/assets/oneos-project-profile.template.json b/.cursor/skills/yunxiao-requirement-lifecycle/assets/oneos-project-profile.template.json deleted file mode 100644 index 633af24..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/assets/oneos-project-profile.template.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "schema_version": 1, - "flow_model": "oneos_delivery", - "organization": "请填写组织名称", - "work_item_type": "产品类需求", - "target_lifecycle": [ - "待处理", "已确认", "分析中", "分析完成", "设计中", "设计完成", - "待开发", "开发中", "开发完成", "待测试", "测试中", "测试完成", - "发布中", "发布完成", "发布失败", "已关闭" - ], - "task_labels": ["交付", "开发", "测试", "发版"], - "policies": { - "require_formal_relations": true, - "require_current_state_condition": true, - "allow_automatic_final_close_without_acceptance": false, - "require_nonzero_development_tasks": true, - "test_success_is_production_release": false, - "bridge_idempotency_key_components": ["项目ID", "需求ID", "动作类型"], - "require_signed_timestamped_replay_protected_callbacks": true - }, - "projects": [ - { - "name": "请填写项目名称", - "migration_phase": "P0", - "requirement_statuses_observed": ["请填写当前全部需求状态"], - "task_workflow_mode": "B", - "task_identity_mode": "task_label", - "native_rule_can_filter_related_task_identity": false, - "main_task_prefix": "【交付】", - "development_task_prefix": "【开发】", - "test_task_prefix": "【测试】", - "release_task_prefix": "【发版】", - "repositories": [ - { - "name": "请填写仓库名称", - "role": "frontend", - "integration_branch": "develop", - "codeup_integrated": true, - "required_for_mr_gate": true, - "evidence": "请填写观察证据" - } - ], - "test_plan": { - "name": "请填写测试计划名称", - "iteration_formally_related": true, - "cases_partitioned_by_requirement": true, - "fixed_defect_is_terminal": false, - "tester_retest_required": true, - "evidence": "请填写观察证据" - }, - "release": { - "release_task_formally_related_to_iteration": true, - "production_evidence_separate_from_test": true, - "acceptance_required_after_release": true, - "production_pipeline_unchanged": true, - "evidence": "请填写观察证据" - }, - "legacy_rules": [ - { - "name": "请填写旧规则显示名", - "disposition": "keep_until_ready", - "enabled": true, - "reason": "请填写保留、停用或替换原因", - "evidence": "请填写观察证据" - } - ], - "controls": [ - {"id":"Y01","mode":"not_supported","enabled":false,"owner":"请填写负责人","fallback":"桥接创建并关联唯一主任务","evidence":"请填写观察证据"}, - {"id":"Y02","mode":"not_supported","enabled":false,"owner":"请填写负责人","fallback":"桥接或人工同步分析完成","evidence":"请填写观察证据"}, - {"id":"Y03","mode":"not_supported","enabled":false,"owner":"请填写负责人","fallback":"桥接或人工同步设计中","evidence":"请填写观察证据"}, - {"id":"Y04","mode":"not_supported","enabled":false,"owner":"请填写负责人","fallback":"桥接或人工同步设计完成","evidence":"请填写观察证据"}, - {"id":"Y05","mode":"not_supported","enabled":false,"owner":"请填写负责人","fallback":"桥接或人工同步待开发","evidence":"请填写观察证据"}, - {"id":"Y06","mode":"not_supported","enabled":false,"owner":"请填写负责人","fallback":"桥接或人工同步开发中","evidence":"请填写观察证据"}, - {"id":"Y07","mode":"not_supported","enabled":false,"owner":"请填写负责人","fallback":"桥接或人工同步开发完成","evidence":"请填写观察证据"}, - {"id":"Y08","mode":"not_supported","enabled":false,"owner":"请填写负责人","fallback":"桥接或人工同步待测试","evidence":"请填写观察证据"}, - {"id":"Y09","mode":"not_supported","enabled":false,"owner":"请填写负责人","fallback":"桥接或人工同步测试中","evidence":"请填写观察证据"}, - {"id":"Y10","mode":"not_supported","enabled":false,"owner":"请填写测试负责人","fallback":"测试负责人按用例和缺陷闭环同步测试完成","evidence":"请填写观察证据"}, - {"id":"Y11","mode":"not_supported","enabled":false,"owner":"请填写发布负责人","fallback":"发版任务提交后人工同步发布中","evidence":"请填写观察证据"}, - {"id":"Y12","mode":"not_supported","enabled":false,"owner":"请填写发布负责人","fallback":"核对生产发布证据后人工同步发布完成","evidence":"请填写观察证据"}, - {"id":"Y13","mode":"not_supported","enabled":false,"owner":"请填写发布负责人","fallback":"人工记录发布失败及原因","evidence":"请填写观察证据"}, - {"id":"Y14","mode":"manual","enabled":true,"owner":"请填写产品负责人","fallback":"产品验收后人工关闭","evidence":"请填写观察证据"}, - {"id":"Y15","mode":"not_supported","enabled":false,"owner":"请填写项目管理员","fallback":"只保留一个权威镜像方向","evidence":"请填写观察证据"}, - {"id":"Y16","mode":"not_supported","enabled":false,"owner":"请填写产品负责人","fallback":"快轨时桥接同步主任务","evidence":"请填写观察证据"}, - {"id":"Y20","mode":"native_rule","enabled":true,"owner":"请填写研发负责人","fallback":"开发负责人人工开始任务和需求","evidence":"请填写观察证据"}, - {"id":"Y21","mode":"integration_bridge","enabled":false,"owner":"请填写研发负责人","fallback":"人工核对全部开发任务和MR","evidence":"请填写观察证据"}, - {"id":"Y22","mode":"integration_bridge","enabled":false,"owner":"请填写项目管理员","fallback":"人工创建并关联唯一测试任务","evidence":"请填写观察证据"}, - {"id":"Y30","mode":"integration_bridge","enabled":false,"owner":"请填写测试负责人","fallback":"人工同步待测试","evidence":"请填写观察证据"}, - {"id":"Y31","mode":"manual","enabled":true,"owner":"请填写测试负责人","fallback":"测试负责人开始测试","evidence":"请填写观察证据"}, - {"id":"Y32","mode":"integration_bridge","enabled":false,"owner":"请填写测试负责人","fallback":"按需求用例包人工验收","evidence":"请填写观察证据"}, - {"id":"Y33","mode":"integration_bridge","enabled":false,"owner":"请填写发布负责人","fallback":"人工提交发版任务并同步范围","evidence":"请填写观察证据"}, - {"id":"Y34","mode":"integration_bridge","enabled":false,"owner":"请填写流水线负责人","fallback":"人工核对生产发布成功","evidence":"请填写观察证据"}, - {"id":"Y35","mode":"integration_bridge","enabled":false,"owner":"请填写流水线负责人","fallback":"人工记录生产发布失败","evidence":"请填写观察证据"}, - {"id":"Y40","mode":"manual","enabled":true,"owner":"请填写产品负责人","fallback":"人工关联迭代","evidence":"请填写观察证据"}, - {"id":"A01","mode":"not_supported","enabled":false,"owner":"请填写产品负责人","fallback":"人工上传并回写链接","evidence":"请填写观察证据"}, - {"id":"A02","mode":"not_supported","enabled":false,"owner":"请填写产品负责人","fallback":"人工维护更新内容历史","evidence":"请填写观察证据"}, - {"id":"A02B","mode":"not_supported","enabled":false,"owner":"请填写产品负责人","fallback":"人工维护需求说明","evidence":"请填写观察证据"}, - {"id":"A03","mode":"integration_bridge","enabled":false,"owner":"请填写项目管理员","fallback":"人工建需求和唯一主任务","evidence":"请填写观察证据"}, - {"id":"A04","mode":"integration_bridge","enabled":false,"owner":"请填写研发负责人","fallback":"人工拆分开发任务","evidence":"请填写观察证据"}, - {"id":"A05","mode":"integration_bridge","enabled":false,"owner":"请填写研发负责人","fallback":"人工核对MR并完成开发任务","evidence":"请填写观察证据"}, - {"id":"A06","mode":"integration_bridge","enabled":false,"owner":"请填写项目管理员","fallback":"人工建唯一测试任务","evidence":"请填写观察证据"}, - {"id":"A07","mode":"integration_bridge","enabled":false,"owner":"请填写测试负责人","fallback":"人工核对用例与缺陷闭环","evidence":"请填写观察证据"}, - {"id":"A08","mode":"integration_bridge","enabled":false,"owner":"请填写发布负责人","fallback":"人工建发版任务和更新说明","evidence":"请填写观察证据"}, - {"id":"A09","mode":"integration_bridge","enabled":false,"owner":"请填写流水线负责人","fallback":"人工回写发布成败","evidence":"请填写观察证据"}, - {"id":"A10","mode":"manual","enabled":true,"owner":"请填写产品负责人","fallback":"产品验收后人工关闭","evidence":"请填写观察证据"}, - {"id":"TC01","mode":"manual","enabled":true,"owner":"请填写测试负责人","fallback":"逐条执行并记录用例结果","evidence":"请填写观察证据"}, - {"id":"TC02","mode":"manual","enabled":true,"owner":"请填写测试负责人","fallback":"人工正式关联失败用例和缺陷","evidence":"请填写观察证据"}, - {"id":"TC03","mode":"manual","enabled":true,"owner":"请填写测试负责人","fallback":"复测后更新原用例状态","evidence":"请填写观察证据"}, - {"id":"DF01","mode":"manual","enabled":true,"owner":"请填写测试负责人","fallback":"已修复继续等待复测","evidence":"请填写观察证据"}, - {"id":"DF02","mode":"manual","enabled":true,"owner":"请填写测试负责人","fallback":"复测通过后关闭缺陷并通过用例","evidence":"请填写观察证据"}, - {"id":"DF03","mode":"manual","enabled":true,"owner":"请填写测试负责人","fallback":"复测失败后重开缺陷并保持用例失败","evidence":"请填写观察证据"}, - {"id":"CI01","mode":"manual","enabled":true,"owner":"请填写研发负责人","fallback":"人工核对全部前后端MR","evidence":"请填写观察证据"}, - {"id":"CI02","mode":"not_supported","enabled":false,"owner":"请填写流水线负责人","fallback":"使用test副本评审回调POC","evidence":"请填写观察证据"}, - {"id":"CI03","mode":"manual","enabled":true,"owner":"请填写发布负责人","fallback":"人工区分test、生产发布和验收","evidence":"请填写观察证据"}, - {"id":"L01","mode":"disabled_legacy","enabled":false,"owner":"请填写项目管理员","fallback":"保持发布完成等待验收","evidence":"请填写观察证据"}, - {"id":"L02","mode":"disabled_legacy","enabled":false,"owner":"请填写项目管理员","fallback":"保持发布完成等待验收","evidence":"请填写观察证据"} - ] - } - ] -} diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/assets/project-profile.template.json b/.cursor/skills/yunxiao-requirement-lifecycle/assets/project-profile.template.json deleted file mode 100644 index 3f6c25a..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/assets/project-profile.template.json +++ /dev/null @@ -1,148 +0,0 @@ -{ - "schema_version": 3, - "organization": "请填写组织名称", - "work_item_type": "产品类需求", - "lifecycle": [ - "待处理", - "已确认", - "分析中", - "分析完成", - "设计中", - "设计完成", - "待开发", - "开发中", - "开发完成", - "测试中", - "测试完成", - "完成发布", - "已完成" - ], - "requirement_labels": ["分析", "设计", "开发", "测试"], - "automation_policy": { - "require_current_state_condition": true, - "inspect_rule_order_and_cascades": true, - "allow_automatic_final_close_without_acceptance": false, - "zero_related_items_are_complete": false, - "development_start_event_candidates": ["添加关联分支", "正式关联代码资产"], - "require_code_assets_linked_to_requirement_and_development_task": true, - "stage_task_creation_idempotency_key_components": ["项目ID", "需求ID", "阶段类型"] - }, - "test_closure_policy": { - "accepted_case_states": ["已通过"], - "rejected_case_states": ["未执行", "待测试", "阻塞", "未通过"], - "defect_fixed_is_terminal": false, - "require_tester_retest": true - }, - "projects": [ - { - "name": "请填写项目名称", - "work_item_type": "产品类需求", - "release_status": "完成发布", - "final_status": "已完成", - "labels_observed": ["分析", "设计", "开发", "测试"], - "repositories": [ - { - "name": "请填写仓库名称", - "role": "frontend", - "integration_branch": "dev", - "codeup_integrated": true, - "required_for_mr_gate": true, - "evidence": "请填写仓库与需求正式关联的观察证据" - } - ], - "test_pipeline": { - "name": "请填写test流水线名称", - "environment": "test", - "is_clone_for_callback_poc": true, - "original_pipeline_unchanged": true, - "docker_success_callback_enabled": false, - "callback_security": { - "signed": false, - "timestamp_checked": false, - "replay_protected": false, - "idempotent_by_execution_id": false, - "failure_retry_tested": false - }, - "evidence": "请填写流水线观察证据" - }, - "release_evidence": { - "mode": "native_release_change", - "target_environment": "请填写目标发布环境", - "test_success_is_production_release": false, - "evidence": "请填写发布证据" - }, - "test_plan": { - "name": "请填写测试计划名称", - "requirement_formally_related": true, - "case_states_observed": ["未执行", "已通过", "未通过", "阻塞"], - "evidence": "请填写测试计划与用例状态证据" - }, - "defect_workflow": { - "fixed_status": "已修复", - "fixed_is_terminal": false, - "tester_retest_required": true, - "closed_status": "请填写缺陷真实关闭状态", - "reopen_status": "请填写缺陷复测失败后的状态", - "evidence": "请填写缺陷工作流证据" - }, - "rule_instances": [ - {"id": "R01", "mode": "manual", "actual_rule_name": "人工门禁:需求确认", "trigger": "负责人确认", "source_status": "待处理", "target_status": "已确认", "conditions": ["范围、负责人和验收口径明确"], "action": "人工变更状态为已确认", "order": 1, "enabled": true, "has_current_state_condition": true, "evidence": "请填写观察证据"}, - {"id": "R02", "mode": "native_rule", "actual_rule_name": "请填写云效规则显示名", "trigger": "添加关联任务工作项", "source_status": "已确认", "target_status": "分析中", "conditions": ["当前状态=已确认", "需求自身标签包含分析"], "action": "变更状态为分析中", "order": 2, "enabled": true, "has_current_state_condition": true, "evidence": "请填写观察证据"}, - {"id": "R03", "mode": "native_rule", "actual_rule_name": "请填写云效规则显示名", "trigger": "关联任务工作项状态变化", "source_status": "分析中", "target_status": "分析完成", "conditions": ["当前状态=分析中", "本阶段分析任务全部完成;若平台不能过滤阶段则记录替代策略"], "action": "变更状态为分析完成", "order": 3, "enabled": true, "has_current_state_condition": true, "evidence": "请填写观察证据"}, - {"id": "R04", "mode": "native_rule", "actual_rule_name": "请填写云效规则显示名", "trigger": "添加关联任务工作项", "source_status": "分析完成", "target_status": "设计中", "conditions": ["当前状态=分析完成", "需求自身标签包含设计"], "action": "变更状态为设计中", "order": 4, "enabled": true, "has_current_state_condition": true, "evidence": "请填写观察证据"}, - {"id": "R05", "mode": "native_rule", "actual_rule_name": "请填写云效规则显示名", "trigger": "关联任务工作项状态变化", "source_status": "设计中", "target_status": "设计完成", "conditions": ["当前状态=设计中", "本阶段设计任务全部完成;若平台不能过滤阶段则记录替代策略"], "action": "变更状态为设计完成", "order": 5, "enabled": true, "has_current_state_condition": true, "evidence": "请填写观察证据"}, - {"id": "R06", "mode": "native_rule", "actual_rule_name": "请填写云效规则显示名", "trigger": "添加关联任务工作项", "source_status": "设计完成", "target_status": "待开发", "conditions": ["当前状态=设计完成", "需求自身标签包含开发"], "action": "变更状态为待开发", "order": 6, "enabled": true, "has_current_state_condition": true, "evidence": "请填写观察证据"}, - {"id": "R07", "mode": "native_rule", "actual_rule_name": "请填写云效规则显示名", "trigger": "添加关联分支或正式关联代码资产", "source_status": "待开发", "target_status": "开发中", "conditions": ["当前状态=待开发", "仓库已集成", "代码资产正式关联需求"], "action": "变更状态为开发中", "order": 7, "enabled": true, "has_current_state_condition": true, "evidence": "请填写观察证据"}, - {"id": "R08", "mode": "native_rule", "actual_rule_name": "请填写云效规则显示名", "trigger": "关联合并请求状态变化", "source_status": "开发中", "target_status": "开发完成", "conditions": ["当前状态=开发中", "全部相关前后端合并请求已合并"], "action": "变更状态为开发完成", "order": 8, "enabled": true, "has_current_state_condition": true, "evidence": "请填写观察证据"}, - {"id": "R09", "mode": "native_rule", "actual_rule_name": "请填写云效规则显示名", "trigger": "添加关联任务工作项", "source_status": "开发完成", "target_status": "测试中", "conditions": ["当前状态=开发完成", "需求自身标签包含测试"], "action": "变更状态为测试中", "order": 9, "enabled": true, "has_current_state_condition": true, "evidence": "请填写观察证据"}, - {"id": "R10", "mode": "manual", "actual_rule_name": "人工门禁:测试验收", "trigger": "测试验收任务完成或获批回调", "source_status": "测试中", "target_status": "测试完成", "conditions": ["当前状态=测试中", "范围内用例全部通过", "失败用例已复测通过", "范围内缺陷已由测试复测并关闭"], "action": "变更状态为测试完成", "order": 10, "enabled": true, "has_current_state_condition": true, "evidence": "请填写观察证据"}, - {"id": "R11", "mode": "native_rule", "actual_rule_name": "请填写云效规则显示名", "trigger": "关联发布变更完成或获批回调", "source_status": "测试完成", "target_status": "完成发布", "conditions": ["当前状态=测试完成", "目标环境发布证据满足组织口径"], "action": "变更状态为完成发布", "order": 11, "enabled": false, "has_current_state_condition": true, "evidence": "请填写观察证据"}, - {"id": "R12", "mode": "manual", "actual_rule_name": "人工门禁:业务验收", "trigger": "业务验收", "source_status": "完成发布", "target_status": "已完成", "conditions": ["当前状态=完成发布", "正式验收证据有效"], "action": "人工变更状态为已完成", "order": 12, "enabled": true, "has_current_state_condition": true, "evidence": "请填写观察证据"} - ], - "task_rule_instances": [ - {"id": "TK01", "mode": "manual", "actual_rule_name": "人工门禁:设计任务真实开工", "trigger": "负责人开始处理设计任务", "scope": "设计任务", "source_state": "待处理", "conditions": ["任务标签=设计", "任务正式关联需求"], "actions": ["设计任务变为处理中"], "order": 101, "enabled": true, "evidence": "请填写观察证据"}, - {"id": "TK02", "mode": "native_rule", "actual_rule_name": "请填写云效规则显示名", "trigger": "设计任务状态变为已完成", "scope": "产品需求", "source_state": "设计中", "conditions": ["本阶段设计任务非零且全部完成"], "actions": ["需求变为设计完成"], "order": 102, "enabled": true, "evidence": "请填写观察证据"}, - {"id": "TK03", "mode": "integration_bridge", "actual_rule_name": "桥接:设计完成后创建开发任务", "trigger": "需求状态变为设计完成", "scope": "产品需求", "source_state": "设计完成", "conditions": ["不存在同需求同阶段未取消开发任务", "幂等键=项目ID+需求ID+开发"], "actions": ["创建并关联开发任务", "开发任务标签=开发", "开发任务状态=待处理"], "order": 103, "enabled": false, "evidence": "请填写观察证据"}, - {"id": "TK04", "mode": "native_rule", "actual_rule_name": "请填写云效规则显示名", "trigger": "开发任务创建并正式关联需求", "scope": "产品需求", "source_state": "设计完成", "conditions": ["需求标签包含开发", "开发任务关系可见"], "actions": ["需求变为待开发"], "order": 104, "enabled": true, "evidence": "请填写观察证据"}, - {"id": "TK05", "mode": "native_rule", "actual_rule_name": "请填写云效规则显示名", "trigger": "添加正式关联分支或代码资产", "scope": "产品需求和开发任务", "source_state": "需求=待开发;开发任务=待处理", "conditions": ["代码资产同时关联需求和开发任务", "仓库已接入项目"], "actions": ["需求变为开发中", "开发任务变为处理中"], "order": 105, "enabled": true, "evidence": "请填写观察证据"}, - {"id": "TK06", "mode": "native_rule", "actual_rule_name": "请填写云效规则显示名", "trigger": "关联合并请求状态变化", "scope": "产品需求和开发任务", "source_state": "需求=开发中;开发任务=处理中", "conditions": ["全部相关前后端MR已合并到真实dev/develop", "MR同时关联需求和开发任务"], "actions": ["需求变为开发完成", "开发任务变为已完成"], "order": 106, "enabled": true, "evidence": "请填写观察证据"}, - {"id": "TK07", "mode": "integration_bridge", "actual_rule_name": "桥接:test部署成功后创建测试任务", "trigger": "test流水线部署成功", "scope": "产品需求", "source_state": "开发完成", "conditions": ["流水线执行ID未处理", "不存在同需求同阶段未取消测试任务", "回调签名和防重放验证通过"], "actions": ["创建并关联测试任务", "测试任务变为处理中", "关联测试计划和用例", "需求变为测试中"], "order": 107, "enabled": false, "evidence": "请填写观察证据"}, - {"id": "TK08", "mode": "manual", "actual_rule_name": "人工门禁:测试任务验收闭环", "trigger": "测试验收完成", "scope": "产品需求和测试任务", "source_state": "需求=测试中;测试任务=处理中", "conditions": ["用例全部通过", "失败用例已复测", "范围内缺陷已由测试关闭"], "actions": ["测试任务变为已完成", "需求变为测试完成"], "order": 108, "enabled": true, "evidence": "请填写观察证据"} - ], - "control_coverage": [ - {"id": "R01", "mode": "manual", "enabled": true, "evidence": "请填写观察证据", "owner": "请填写负责人", "fallback": "人工确认需求范围与验收口径"}, - {"id": "R02", "mode": "native_rule", "enabled": true, "evidence": "请填写观察证据", "owner": "请填写负责人", "fallback": "人工进入分析中"}, - {"id": "R03", "mode": "native_rule", "enabled": true, "evidence": "请填写观察证据", "owner": "请填写负责人", "fallback": "人工核对本阶段分析任务"}, - {"id": "R04", "mode": "native_rule", "enabled": true, "evidence": "请填写观察证据", "owner": "请填写负责人", "fallback": "人工进入设计中"}, - {"id": "R05", "mode": "native_rule", "enabled": true, "evidence": "请填写观察证据", "owner": "请填写负责人", "fallback": "人工核对本阶段设计任务"}, - {"id": "R06", "mode": "native_rule", "enabled": true, "evidence": "请填写观察证据", "owner": "请填写负责人", "fallback": "人工进入待开发"}, - {"id": "R07", "mode": "native_rule", "enabled": true, "evidence": "请填写观察证据", "owner": "请填写负责人", "fallback": "开发人员真实开工时人工开始开发"}, - {"id": "R08", "mode": "native_rule", "enabled": true, "evidence": "请填写观察证据", "owner": "请填写负责人", "fallback": "人工核对全部相关MR"}, - {"id": "R09", "mode": "native_rule", "enabled": true, "evidence": "请填写观察证据", "owner": "请填写负责人", "fallback": "人工进入测试中"}, - {"id": "R10", "mode": "manual", "enabled": true, "evidence": "请填写观察证据", "owner": "请填写测试负责人", "fallback": "测试负责人验收任务"}, - {"id": "R11", "mode": "native_rule", "enabled": false, "evidence": "请填写观察证据", "owner": "请填写发布负责人", "fallback": "人工核对发布变更"}, - {"id": "R12", "mode": "manual", "enabled": true, "evidence": "请填写观察证据", "owner": "请填写产品负责人", "fallback": "产品或业务负责人验收"}, - {"id": "TK01", "mode": "manual", "enabled": true, "evidence": "请填写设计任务开工证据", "owner": "请填写设计负责人", "fallback": "负责人真实开工时人工改为处理中"}, - {"id": "TK02", "mode": "native_rule", "enabled": true, "evidence": "请填写设计任务完成汇总证据", "owner": "请填写产品负责人", "fallback": "人工核对非零设计任务全部完成"}, - {"id": "TK03", "mode": "integration_bridge", "enabled": false, "evidence": "请填写自动创建开发任务能力证据", "owner": "请填写项目管理员", "fallback": "从需求中人工新建并关联唯一开发任务"}, - {"id": "TK04", "mode": "native_rule", "enabled": true, "evidence": "请填写开发任务关联证据", "owner": "请填写项目管理员", "fallback": "人工核对关系后将需求改为待开发"}, - {"id": "TK05", "mode": "native_rule", "enabled": true, "evidence": "请填写需求和开发任务双对象开工证据", "owner": "请填写研发负责人", "fallback": "开发人员真实开工时人工更新任务和需求"}, - {"id": "TK06", "mode": "native_rule", "enabled": true, "evidence": "请填写需求和开发任务双对象完成证据", "owner": "请填写研发负责人", "fallback": "人工核对全部相关MR后完成任务和需求"}, - {"id": "TK07", "mode": "integration_bridge", "enabled": false, "evidence": "请填写test部署回调和自动创建测试任务证据", "owner": "请填写流水线负责人", "fallback": "test部署成功后人工创建并关联测试任务"}, - {"id": "TK08", "mode": "manual", "enabled": true, "evidence": "请填写测试任务与需求双闭环证据", "owner": "请填写测试负责人", "fallback": "测试负责人验收后同步完成测试任务和需求"}, - {"id": "TC01", "mode": "manual", "enabled": true, "evidence": "请填写用例执行状态证据", "owner": "请填写测试负责人", "fallback": "逐条执行并记录用例结果"}, - {"id": "TC02", "mode": "manual", "enabled": true, "evidence": "请填写失败用例关联缺陷证据", "owner": "请填写测试负责人", "fallback": "人工核对失败用例与缺陷正式关系"}, - {"id": "TC03", "mode": "manual", "enabled": true, "evidence": "请填写用例复测更新证据", "owner": "请填写测试负责人", "fallback": "复测后更新原用例状态"}, - {"id": "DF01", "mode": "manual", "enabled": true, "evidence": "请填写已修复交接证据", "owner": "请填写测试负责人", "fallback": "已修复继续等待测试复测"}, - {"id": "DF02", "mode": "manual", "enabled": true, "evidence": "请填写复测通过关闭证据", "owner": "请填写测试负责人", "fallback": "测试复测通过后关闭缺陷"}, - {"id": "DF03", "mode": "manual", "enabled": true, "evidence": "请填写复测失败重开证据", "owner": "请填写测试负责人", "fallback": "测试复测失败后重开缺陷"}, - {"id": "CI01", "mode": "native_rule", "enabled": true, "evidence": "请填写多仓库关联证据", "owner": "请填写研发负责人", "fallback": "人工核对全部前后端MR"}, - {"id": "CI02", "mode": "not_supported", "enabled": false, "evidence": "请填写流水线能力证据", "owner": "请填写流水线负责人", "fallback": "复制test流水线后评审回调POC"}, - {"id": "CI03", "mode": "manual", "enabled": true, "evidence": "请填写环境与发布证据", "owner": "请填写发布负责人", "fallback": "人工区分test部署、生产发布与业务验收"}, - {"id": "L01", "mode": "disabled_legacy", "enabled": false, "evidence": "请填写旧规则盘点证据", "owner": "请填写项目管理员", "fallback": "使用独立业务验收任务"}, - {"id": "L02", "mode": "disabled_legacy", "enabled": false, "evidence": "请填写旧规则盘点证据", "owner": "请填写项目管理员", "fallback": "保持发布完成状态等待业务验收"} - ], - "adjunct_automations": [] - } - ] -} diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/references/auto-stage-task.md b/.cursor/skills/yunxiao-requirement-lifecycle/references/auto-stage-task.md deleted file mode 100644 index 82abe72..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/references/auto-stage-task.md +++ /dev/null @@ -1,62 +0,0 @@ -# 需求进阶段自动建同名任务(强制 · AI 桥接) - -当**产品类需求**推进到下列状态时,AI(`apply`)必须查重后创建/复用**与需求同名**的任务,并**正式关联**该需求。不得只改需求状态却不建任务。 - -## 触发状态与字段规则 - -| 需求进入状态 | 任务标题 | 任务标签(右侧基础字段) | 负责人 | 时间字段 | -|---|---|---|---|---| -| **分析中** | = 需求标题(同名) | `分析` | **需求创建人**姓名 | 沿用需求(见下) | -| **设计中** | = 需求标题(同名) | `设计` | **需求创建人**姓名 | 沿用需求(见下) | -| **待开发** | = 需求标题(同名) | `交付`(OneOS 主任务);若项目仍用阶段开发标签则用 `开发` | 固定 **何斐** | 沿用需求(见下) | - -### 时间沿用(「标签创建时间沿用需求的」) - -按平台能力尽量对齐,优先级: - -1. 任务 **计划开始时间** = 需求的计划开始时间;若需求无计划开始,则用需求 **创建时间** 的日期。 -2. 若平台允许写入任务创建时间:任务创建时间 = 需求创建时间。 -3. 若都不能改创建时间:仍写计划开始;并在任务描述首行注明:`创建时间沿用需求:YYYY-MM-DD HH:mm`。 - -### 正式关联(强制) - -- 使用云效**父子项或关联项**把任务挂到**该产品需求**。 -- 仅标题写需求编号、或只关联其他任务而不关联需求 → **不合格**,必须补关联后再回报成功。 -- 标题前缀(如 `【分析】`)**不是**关联;本规则默认标题与需求**完全同名**;若项目强制标题前缀,允许 `【分析】{需求标题}`,但仍须正式关联 + 正确标签。 - -## 幂等与查重 - -幂等键: - -```text -项目ID + 需求ID + 阶段标签(分析|设计|交付/开发) -``` - -执行顺序: - -1. 查询该需求下是否已有**未取消**、同阶段标签、同名(或同名前缀)任务。 -2. **已存在一条** → 复用:核对正式关联、标签、负责人(待开发须为何斐)、时间字段;缺则补齐;**不新建第二条**。 -3. **存在多条冲突** → 停止创建,列出任务编号,请人合并后再继续。 -4. **不存在** → 创建并正式关联,再改需求状态(若口令是「推进到某状态」且任务尚未建好,先建任务再改状态,避免有状态无任务)。 - -快轨直接到 **待开发**(跳过分析/设计):只建/复用 **待开发** 那一条同名任务(负责人何斐),不要为跳过的阶段补建分析/设计任务,除非用户明确要求。 - -## 与 OneOS 终态模型对齐 - -- `oneos_delivery`:待开发阶段的同名任务即唯一 **【交付】主任务**(标题优先与需求同名;标签=`交付`)。分析中/设计中另建同名阶段任务(标签分析/设计),**不**再额外建第二条交付主任务。 -- `stage_tasks`:分析/设计/开发阶段任务按上表;待开发对应标签=`开发` 的同名任务,负责人何斐(若项目配置了其他默认开发负责人,以用户当次口令为准,口令未写则何斐)。 - -## AI 回报模板(短) - -```text -需求:ONEOSP-xxx「标题」→ 状态:分析中/设计中/待开发 -任务:TASK-xxx「同名」;标签:…;负责人:…;计划开始:…;已正式关联需求 -查重:新建 / 复用 -``` - -## 负向(不得误做) - -- 需求仅到 `已确认` / `待处理`:不自动建分析/设计/待开发任务。 -- 不得把样式类口头变更当成建任务触发。 -- 不得在未查重时重复创建同阶段同名任务。 -- 不得把负责人设错:分析中/设计中≠何斐(除非创建人就是何斐);待开发必须何斐(除非用户当次明确改派)。 diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/references/codeup-integration.md b/.cursor/skills/yunxiao-requirement-lifecycle/references/codeup-integration.md deleted file mode 100644 index ac12a75..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/references/codeup-integration.md +++ /dev/null @@ -1,60 +0,0 @@ -# Codeup研发资产集成 - -## 目录 - -1. 仓库覆盖 -2. 分支策略 -3. 正式关联 -4. 提交与合并请求 -5. CI01多仓库门禁 -6. 验证场景 - -## 1. 仓库覆盖 - -前端、后端均可触发研发自动化,前提是仓库接入正确项目,分支/提交/MR正式关联需求,规则没有错误限定到单一仓库。 - -逐仓库记录:仓库名、角色、项目集成状态、Webhook、实际集成分支、是否纳入全部MR门禁和观察证据。 - -## 2. 分支策略 - -- 检查真实集成分支:存在`dev`则优先`dev`,否则使用存在的`develop`。 -- 从集成分支创建`feature/`或`fix/`。 -- 一个需求涉及多个仓库时,每个仓库可有一个同名活动分支。 -- 快速迭代仍使用短生命周期分支;合并后是否删除按仓库策略和用户授权处理。 -- 不直接推送保护分支,不为自动化方便创建不存在的`dev`/`develop`。 - -## 3. 正式关联 - -关联优先级: - -1. 从需求“代码”区域创建分支并回到需求确认资产数量。 -2. 在Codeup创建分支/MR时显式选择工作项;存在开发任务时同时关联需求和开发任务。 -3. 使用以工作项ID结尾的分支名,并在需求页确认自动关联。 -4. 提交说明关键字仅作兜底,必须回需求页验证。 - -需求编号、分支名或MR标题有关键字,不等同已正式关联。 - -## 4. 提交与合并请求 - -- 提交可带工作项ID增强追溯,但不判定开发完成。 -- MR目标必须是仓库真实集成分支。 -- MR显式关联需求和对应开发任务并完成评审。 -- 只有全部相关MR合并后才观察R08。 -- 首次提交时间不作为真实开发开始时间;人工“开始开发”为效能主口径。 - -## 5. CI01多仓库门禁 - -CI01要求所有相关前后端仓库的分支、提交和MR都正式关联需求与对应开发任务。只遗漏一个仓库或只关联需求,平台的“全部MR已合并”就可能只看到部分资产、提前完成需求,或完全无法完成开发任务。 - -如果无法可靠聚合所有仓库,保留人工核对门禁,不能声称R08已完全自动化。 - -## 6. 验证场景 - -| 场景 | 操作 | 期望 | -|---|---|---| -| 正式关联分支 | 从需求代码区创建真实仓库分支 | 异步进入开发中 | -| 非关联分支 | 仅本地创建/推送无关系分支 | 不触发R07 | -| 仅有提交 | 提交代码但不合并MR | 不进入开发完成 | -| 单仓库MR | 所有相关仓库只有一个MR合并 | 仍保持开发中 | -| 多仓库全部合并 | 所有相关MR正式关联并合并 | 进入开发完成 | -| 错误集成分支 | MR指向非真实集成分支 | 阻塞并修正,不伪造通过 | diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/references/lifecycle-rules.md b/.cursor/skills/yunxiao-requirement-lifecycle/references/lifecycle-rules.md deleted file mode 100644 index 71904a5..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/references/lifecycle-rules.md +++ /dev/null @@ -1,103 +0,0 @@ -# 需求生命周期与项目规则 - -## 目录 - -1. 状态参数化 -2. R01–R12规则矩阵 -3. 阶段入口和阶段完成 -4. 开发开始与开发完成 -5. 标签、任务和关键字 -6. 规则实例与限制 - -## 1. 状态参数化 - -典型流程为: - -```text -待处理 → 已确认 → 分析中 → 分析完成 → 设计中 → 设计完成 → 待开发 -→ 开发中 → 开发完成 → 测试中 → 测试完成 → 完成发布/发布完成 → 已完成/已关闭 -``` - -逐项目读取真实状态。`完成发布`与`发布完成`、`已完成`与`已关闭`是候选映射,不能静默互换或创建近义重复状态。 - -## 2. R01–R12规则矩阵 - -| ID | 流转 | 触发事件 | 必要条件 | 动作 | 方式 | -|---|---|---|---|---|---| -| R01 | 待处理 → 已确认 | 负责人确认 | 范围、负责人、优先级、验收口径明确 | 改为已确认 | 人工 | -| R02 | 已确认 → 分析中 | 添加关联任务工作项 | 当前状态;需求自身标签包含`分析` | 改为分析中 | 原生规则 | -| R03 | 分析中 → 分析完成 | 关联任务状态变化 | 当前状态;本阶段分析任务全部完成 | 改为分析完成 | 原生/条件化 | -| R04 | 分析完成 → 设计中 | 添加关联任务工作项 | 当前状态;需求自身标签包含`设计` | 改为设计中 | 原生规则 | -| R05 | 设计中 → 设计完成 | 关联任务状态变化 | 当前状态;本阶段设计任务全部完成 | 改为设计完成 | 原生/条件化 | -| R06 | 设计完成 → 待开发 | 添加关联任务工作项 | 当前状态;需求自身标签包含`开发` | 改为待开发 | 原生规则 | -| R07 | 待开发 → 开发中 | 添加关联分支或正式关联代码资产 | 当前状态;仓库已集成;资产正式关联 | 改为开发中 | 自动兜底 | -| R08 | 开发中 → 开发完成 | 关联合并请求状态变化 | 当前状态;全部相关MR已合并 | 改为开发完成 | 原生规则 | -| R09 | 开发完成 → 测试中 | 添加关联任务工作项 | 当前状态;需求自身标签包含`测试` | 改为测试中 | 原生规则 | -| R10 | 测试中 → 测试完成 | 测试验收任务或获批回调 | 当前状态;用例、失败复测、缺陷均闭环 | 改为测试完成 | 人工/桥接 | -| R11 | 测试完成 → 发布完成状态 | 发布变更完成或获批回调 | 当前状态;目标环境发布证据有效 | 改为真实发布状态 | 原生/桥接 | -| R12 | 发布完成状态 → 项目终态 | 业务验收 | 当前状态;验收证据有效 | 改为真实终态 | 默认人工 | - -R05–R10与阶段任务自身状态的双向联动见`task-lifecycle.md`;R10的用例和缺陷口径见`test-defect-closure.md`;R11、R12见`pipeline-release.md`;R07、R08的代码资产口径见`codeup-integration.md`。 - -## 3. 阶段入口和阶段完成 - -R02、R04、R06、R09同时要求: - -1. 从需求中“新建并关联”或“关联已有”任务工作项。 -2. 当前状态等于前一阶段完成状态。 -3. 需求自身包含目标阶段标签。 - -如果规则编辑器不能读取需求自身标签,标签只能用于筛选和报表;保留人工入口或使用当前状态+显式关联事件,不能声称标签已成为技术门槛。 - -R03、R05首选“本阶段任务全部完成”。若平台只能判断“全部关联任务工作项”,选择一种明确策略: - -- 阶段开始时才创建/关联本阶段任务。 -- 使用不同工作项类型,并确认规则能按类型过滤。 -- 使用父子需求拆分阶段。 -- 保留人工完成动作。 - -不得把零个关联任务当作全部完成。使用关联项状态变化触发,并执行零关联项负向测试。 - -## 4. 开发开始与开发完成 - -R07采用双轨口径: - -- 效能主口径:开发人员真实开工时执行“开始开发”。 -- 自动兜底:创建或正式关联需求分支/代码资产后进入`开发中`。 - -首次提交可能晚于真实开工,不能作为权威开始时间。自动规则必须要求当前状态=`待开发`。 - -R08推荐定义: - -```text -事件 = 关联合并请求状态变化 -且当前状态 = 开发中 -且全部相关合并请求 = 已合并 -则状态 = 开发完成 -``` - -提交活动不能证明开发完成。全部相关前端、后端MR都必须正式关联并合并到实际集成分支。 - -## 5. 标签、任务和关键字 - -`分析`、`设计`、`开发`、`测试`标签加在产品类需求自身。 - -“添加关联任务工作项”依赖云效正式关系,不依赖标题关键字。有效方式: - -- 在需求详情新建并关联任务。 -- 在需求详情关联已有任务。 -- 在任务详情选择该需求为父项/关联项,并在页面确认关系。 - -仅写需求编号、粘贴链接或添加同名标签都不能替代正式关联。编号仍建议出现在标题中,方便检索和审计。 - -## 6. 规则实例与限制 - -每个项目分别记录R01–R12的实际规则显示名/人工门禁、精确触发、源状态、目标状态、全部条件、动作、顺序、启用状态、执行账号和证据。 - -已知限制: - -- 规则通常异步执行,5–30秒内不要过早判失败。 -- “全部关联项”只能看到已集成并正式关联的资产。 -- 提前创建未来阶段任务可能阻塞“全部任务完成”。 -- 需求状态规则不会自动更新阶段任务状态;必须同时配置TK01–TK08。 -- 平台能力因模板、版本和权限不同,以当前UI和执行日志为准。 diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/references/live-operations.md b/.cursor/skills/yunxiao-requirement-lifecycle/references/live-operations.md deleted file mode 100644 index 0c8271d..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/references/live-operations.md +++ /dev/null @@ -1,133 +0,0 @@ -# 线上实施、验证与治理 - -## 目录 - -1. 安全边界 -2. 项目盘点 -3. 规则应用 -4. 隔离验证 -5. 故障诊断 -6. 回滚 -7. 运行治理 - -## 1. 安全边界 - -- 只操作明确授权的组织、项目、工作项类型和测试资产。 -- 复用已登录会话,不记录账号、密码、Cookie、Token、OTP、私钥或签名材料。 -- 不修改现有prod流水线;回调实验只改test副本。 -- 不删除测试需求、分支、MR、流水线或规则,除非另有授权。 -- 保留无关规则和用户修改。 - -## 2. 项目盘点 - -先记录`flow_model=stage_tasks`或`flow_model=oneos_delivery`。发现两套规则同时启用时判定为迁移中,不按规则名称猜测主模型。 - -对每个项目独立记录: - -| 类别 | 必查内容 | -|---|---| -| 项目 | 精确项目名、项目ID、工作项类型 | -| 工作流 | 全部状态、顺序、发布状态、终态 | -| 阶段任务 | 设计/开发/测试任务类型、状态、标签、父子/关联关系、自动创建能力 | -| 标签 | `分析`、`设计`、`开发`、`测试` | -| 规则 | 名称、事件、条件、动作、顺序、启用、执行账号、后继规则 | -| Codeup | 前后端仓库、集成、Webhook、真实`dev/develop` | -| 流水线 | test/prod名称、环境、部署阶段、副本能力 | -| 发布 | 发布变更关系、目标环境、完成状态 | -| 测试 | 计划范围、用例状态枚举、需求关系 | -| 缺陷 | 已修复、复测、关闭态、重开态、权限 | - -两个项目只复制逻辑,不复制内部ID、仓库、分支、流水线、规则ID或观察证据。 - -OneOS终态迁移还要盘点:`待测试/发布中/发布失败`状态缺口、主任务身份方式、任务工作流方案A/B、原生规则能否过滤关联任务身份、Y/A控制负责人和幂等键。门禁未齐时保留可用旧规则,只停用会绕过验收或造成已确认误触发的规则。 - -## 3. 规则应用 - -1. 导出或截图现有规则和启用状态。 -2. 识别后继规则和可能的连锁路径。 -3. 创建缺失阶段标签。 -4. 一次只创建或修改一条规则。 -5. 设置精确触发、当前状态、需求标签、其他条件和目标动作。 -6. 保存后重新打开,核对全部字段、顺序、启用状态和执行账号。 -7. 把真实字段写回该项目`rule_instances`。 -8. 触发最小事件,等待5–30秒。 -9. 查看状态、研发资产、执行日志和二次流转。 -10. 记录证据后再处理下一条。 - -手工推进只能标记为“测试准备动作”,不能伪造规则通过。 - -## 4. 隔离验证 - -测试需求命名示例: - -```text -[自动化测试] 产品类需求生命周期验证-YYYYMMDD -``` - -| 用例 | 操作 | 期望 | 负向检查 | -|---|---|---|---| -| T01 | 已确认+分析标签+关联任务 | 分析中 | 无标签不流转 | -| T02 | 完成分析任务 | 分析完成 | 零任务不误触发 | -| T03 | 设计标签+关联任务 | 设计中 | 仅标题关键字不触发 | -| T04 | 完成设计任务 | 设计完成 | 未完成任务阻塞 | -| T04a | 设计完成后触发TK03 | 创建且只创建一个开发任务 | 重复事件不得重复创建 | -| T04b | 开发任务正式关联需求 | 需求待开发、任务待处理 | 仅标题编号不触发 | -| T05 | 开发标签+关联任务 | 待开发 | 错误当前状态不触发 | -| T06 | 正式关联真实仓库分支 | 开发中 | 非关联分支不触发 | -| T06a | 同一代码资产同时关联需求和开发任务 | 需求开发中、开发任务处理中 | 只关联需求不得声称任务已联动 | -| T07 | 前后端各关联MR,只合并一个 | 保持开发中 | 不得提前完成 | -| T08 | 全部相关MR合并 | 开发完成 | 仅提交不得完成 | -| T08a | 全部相关MR同时关联开发任务并合并 | 开发任务已完成 | 部分仓库MR未合并时阻塞 | -| T09 | 测试标签+关联测试任务 | 测试中 | 未来任务不应误阻塞 | -| T09a | test流水线部署成功触发TK07 | 创建且只创建一个测试任务并进入处理中 | 流水线失败或重复回调不得创建 | -| T10a | 执行全部用例并记录结果 | 每条有明确状态 | 未执行/阻塞/失败阻塞 | -| T10b | 失败用例关联缺陷,改为已修复 | 保持测试中 | 未复测不得完成 | -| T10c | 复测通过/失败 | 关闭并通过/重开并失败 | 用例缺陷状态一致 | -| T11 | 发布变更完成/获批回调 | 发布完成状态 | 重复回调不重复推进 | -| T12a | 进入发布完成后等待异步窗口 | 停留发布完成 | L01/L02不得误关闭 | -| T12b | 完成正式业务验收 | 项目终态 | test或发布不能替代验收 | - -结果只使用:`通过`、`异步通过`、`未触发`、`误触发`、`阻塞`。 - -## 5. 故障诊断 - -按顺序检查: - -1. 事件是否真实发生,而不是只有相似标题/标签。 -2. 当前状态是否精确匹配。 -3. 标签是否加在需求自身。 -4. 任务、分支、MR、发布变更是否正式关联。 -5. 仓库是否接入正确项目,Webhook是否有效。 -6. 全部相关MR是否合并到真实集成分支。 -7. 规则是否启用,执行账号是否有权限。 -8. 执行日志是未匹配、失败还是排队。 -9. 用例是否仍有未执行、阻塞或失败。 -10. 缺陷是否仅到已修复,尚未复测关闭。 -11. 目标状态是否触发后继/重复规则。 -12. 是否发生`测试完成 → 发布完成 → 终态`瞬时连锁。 -13. 是否等待合理异步窗口。 -14. 是否误用另一个项目的内部ID或证据。 -15. 缺陷与原用例在复测后的状态是否一致。 -16. 需求状态变化是否遗漏对应阶段任务状态,或任务变化是否遗漏需求汇总。 -17. 自动创建下一阶段任务是否具有需求ID+阶段类型幂等门禁。 -18. 项目是否混用R/TK阶段模型与Y/A终态模型。 -19. OneOS主任务是否唯一且可被规则可靠识别。 -20. 是否缺少`待测试`、`发布中`、`发布失败`或重试流转边。 - -## 6. 回滚 - -- 先禁用本次新增规则,再恢复修改前条件和动作。 -- 保留变更前截图/导出和规则ID。 -- 回调POC通过禁用副本回滚;删除需单独授权。 -- 分支/MR等测试资产按仓库策略清理;删除需单独授权。 -- 不覆盖与本次变更无关的规则。 - -## 7. 运行治理 - -- 每季度复核规则账号、权限、启用状态和失败日志。 -- 新增仓库时同步验证项目集成、Webhook和多仓库门禁。 -- 状态、标签或工作项类型重命名后逐条复核依赖规则。 -- 保护`master/main`和真实`dev/develop`。 -- 监控超过7天无活动的需求分支,未经授权不删除。 -- 分开统计自动流转时间与真实业务开始时间。 -- 持续检查回调签名、时间戳、防重放、幂等和失败重试。 diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/references/oneos-pc-fast-path.md b/.cursor/skills/yunxiao-requirement-lifecycle/references/oneos-pc-fast-path.md deleted file mode 100644 index be9a309..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/references/oneos-pc-fast-path.md +++ /dev/null @@ -1,117 +0,0 @@ -# 统一运营管理平台 · 记录需求快路径 - -真相源运行时 ID:[assets/oneos-pc-runtime-ids.json](../assets/oneos-pc-runtime-ids.json) -验证样例:ONEOS-84「测试需求」(2026-07-21)。 - -**用户可复制完整口令(A/B/C 门禁 + 30 标签):** [oneos-pc-record-requirement-prompt.md](oneos-pc-record-requirement-prompt.md) - -本文件只服务**日常 `记录需求` apply**。全量审计/规则配置仍读 `live-operations.md` 等模块。 - -## 0. 执行门禁(强制) - -用户口令要求 Plan / 单选「优先级」「推进至」「标签」时: - -1. **必须**先拿到明确的 A(紧急/高/中/低)、B(分析中/设计中/设计完成/开发中)、**C(标签,可多选)**。 -2. 标签 **C** 只能从 [assets/oneos-pc-tag-catalog.md](../assets/oneos-pc-tag-catalog.md) / `tags.catalog` 点选;**禁止**按 `lines.ts` 或业务条线说明自动推断。 -3. 来源优先:`AskQuestion` 点选 → Plan 批准时写明的 A+B+C → 用户一句话点明。 -4. **禁止**:未选时默认「中 + 分析中」并继续建单;**禁止**未选标签时自作主张打标。 -5. 「批准/Implement 计划」≠ 已选 A+B+C;计划正文若仍是空 ○,必须先问清再执行。 - -多条需求名称时:每条或整批共用的优先级/推进至/标签仍须用户点选后再写云效。 - -## 1. 加载配方(禁止重探) - -对项目别名含「统一运营管理平台」时: - -1. 读取 `assets/oneos-pc-runtime-ids.json`。 -2. **禁止**再探测 create URL(不要用 `/workitem/create`)。 -3. **禁止**对优先级 ID 做猜测重试超过配方表。 - -## 2. 标准执行顺序(目标 1~3 分钟) - -```text -AutoPRD → API 建需求(含描述) → API 建阶段任务 → API 打标签(COVER) → 状态连跳 → 一次校验回报 -``` - -### 2.1 AutoPRD - -- 先跑 `$oneos-autoprd`(模块/原型来自口令或对话页)。 -- 描述 HTML 最小块:`【原型】` + `【目标】` + `【非目标】` + `【更新内容】`(无变更记录则「首版定稿」)。 - -### 2.2 API 建产品类需求 - -`POST`(失败再试一次 `PUT`) -`https://devops.aliyun.com/projex/api/workitem/workitem?_input_charset=utf-8` - -要点: - -- `workitemTypeIdentifier` **与** `workitemType` 都设为产品类需求 ID。 -- `document: { content: html, formatType: 'RICHTEXT' }` 创建时写入。 -- `fieldValueList` 带 `priority`、`assignedTo`、可选 `79`(计划开始,中国时区中午 epoch 字符串)。 -- 开发中:需求负责人用何斐 ID;分析中/设计中/设计完成:创建人 ID。 - -### 2.3 API 建同名阶段任务 - -同一 create 接口,`category=Task`,`parentIdentifier=需求ID`,标题与需求**完全同名**。 - -| 推进至 | 任务标签目标 | 负责人 | -|---|---|---| -| 分析中 | 分析 | 创建人 | -| 设计中 | 设计 | 创建人 | -| 开发中 / 待开发 | 开发或交付 | 何斐 | - -查重:同需求 + 同阶段标签 + 未取消 → 复用,不建第二条。详见 [auto-stage-task.md](auto-stage-task.md)。 - -### 2.4 状态连跳(开发中) - -从「待处理」到「开发中」UI 固定路径: - -```text -待处理 → 设计完成 → 待开发 → 开发中 -``` - -每跳点开状态按钮选目标;不要在「待处理」下拉里找「开发中」(没有)。 - -其它推进至: - -- 分析中 / 设计中 / 设计完成:待处理下拉通常可直达。 - -### 2.5 标签(选择器点选 + API) - -1. 用户已从 `tags.catalog` 点选标签名(见门禁 C);用 `tags.by_name` 得到 identifier。 -2. **优先 API**(已验证): - -```http -PATCH https://devops.aliyun.com/projex/api/workitem/workitem/{identifier}?_input_charset=utf-8 -Content-Type: application/json - -{ - "workitemIdentifier": "{identifier}", - "propertyKey": "tag", - "propertyValue": "{id1,id2}", - "operateType": "COVER" -} -``` - -3. API 失败 1 次 → UI 标签选择器勾选 → **确定**;仍失败 → **停止并请用户回复标签名**,禁止报「已打上」。 -4. **禁止**对照 `lines.ts` / 业务条线说明自动映射;刷新全量标签:`GET` `api.list_tags`(`spaceType=Space`)。 - -### 2.6 校验与回报(只做一次) - -核对:编号、链接、状态、优先级、负责人、计划开始、描述含原型、任务父项、标签。 -截图最多:建单后、终态后各一次。 - -## 3. 性能红线 - -| 允许 | 禁止 | -|---|---| -| 配方内 ID / URL | 每步 3+ 端点盲探 | -| create 带描述 | 默认先开富文本再粘贴 | -| 状态固定三跳 | React fiber 乱调 onChange(易白屏) | -| 标签失败问用户 | 标签失败仍声称成功 | -| API 失败 1 次改 UI | 同失败 payload 循环重试 | - -## 4. 浏览器会话 - -- 复用已登录 `devops.aliyun.com`;不记录 Cookie/Token。 -- 优先已打开的「统一运营管理平台」项目页;锁 tab 后执行,结束解锁。 diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/references/oneos-pc-record-requirement-prompt.md b/.cursor/skills/yunxiao-requirement-lifecycle/references/oneos-pc-record-requirement-prompt.md deleted file mode 100644 index 0ee6812..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/references/oneos-pc-record-requirement-prompt.md +++ /dev/null @@ -1,139 +0,0 @@ -# 统一运营管理平台 · 记录需求到云效(完整提示词) - -复制下方「用户口令」整段发给 Agent;Agent 须先弹出 Plan / 单选卡让你点选 **A 优先级 + B 推进至 + C 标签**,**未点选前不得建单**。 - ---- - -## 用户口令(复制整段) - -```text -使用 $yunxiao-requirement-lifecycle + $oneos-autoprd -记录需求到云效 · 统一运营管理平台 - -【需求名】 -(填写 1 条或多条,例:车辆资产功能优化) - -【描述来源】 -$oneos-autoprd,原型/模块:(填写,例:oneos-h5-vehicle-assets) - -【已锁定】 -- 项目:统一运营管理平台 -- 计划开始:今日 -- 负责人:分析中 / 设计中 / 设计完成 → 创建人;开发中 → 何斐 - -【请你先 Plan 点选,未选完禁止写云效】 - -A. 优先级(单选) -○ 紧急 -○ 高 -○ 中 -○ 低 - -B. 推进至(单选) -○ 分析中(建同名「分析」任务,负责人=创建人) -○ 设计中(建同名「设计」任务,负责人=创建人) -○ 设计完成(只改状态,负责人=创建人;不强制建阶段任务) -○ 开发中(建同名开发/交付任务,负责人=何斐;状态连跳:待处理→设计完成→待开发→开发中) - -C. 标签(可多选;只能从下列 30 个云效标签中选,禁止自造、禁止按业务条线/lines.ts 自动推断) -○ 运维管理条线 -○ 报表中心 -○ 能源管理 -○ 维修站管理 -○ 充电站管理 -○ 还车应结款 -○ 交车应收款 -○ 加氢站管理 -○ 保险管理 -○ 合同管理 -○ 租赁账单 -○ 供应商管理 -○ 客户管理 -○ 还车任务 -○ 安全培训 -○ 备件管理 -○ 停车场管理 -○ 备车管理 -○ 异动管理 -○ 调拨管理 -○ 上牌管理 -○ 替换车管理 -○ 还车管理 -○ 交车管理 -○ 车辆管理 -○ 审批中心 -○ 故障管理 -○ 车辆年审 -○ 维修管理 -○ 工作台 - -【我确认 A+B+C 后你再执行】 -批准 / Implement 计划时,我会写明所选值,例如: -优先级:高;推进至:设计完成;标签:运维管理条线 - -【执行要求(Agent)】 -1. 读快路径:references/oneos-pc-fast-path.md + assets/oneos-pc-runtime-ids.json -2. 先 $oneos-autoprd 生成描述(【原型】【目标】【非目标】【更新内容】;无增量则「首版定稿」) -3. API 建产品类需求(POST /workitem/workitem,创建时写入 document) -4. 计划开始=今日;按 B 推进状态;按规则建/复用同名阶段任务 -5. 标签:用 tags.by_name 查 identifier,PATCH propertyKey=tag operateType=COVER;API 失败 1 次再 UI 兜底 -6. 一次校验回报:编号、链接、状态、优先级、负责人、计划开始、标签、描述、阶段任务 -7. 禁止默认「中+分析中」;禁止未选标签时自作主张打标;标签失败不得报「已成功」 -``` - ---- - -## 单条需求示例(填好即用) - -```text -使用 $yunxiao-requirement-lifecycle + $oneos-autoprd -记录需求到云效 · 统一运营管理平台 - -【需求名】 -测试需求3 - -【描述来源】 -$oneos-autoprd,原型/模块:oneos-h5-vehicle-assets - -【已锁定】 -- 项目:统一运营管理平台 -- 计划开始:今日 -- 负责人:分析中/设计中/设计完成=创建人;开发中=何斐 - -请先 Plan 让我点选 A 优先级、B 推进至、C 标签(30 项 catalog,禁止 lines.ts 推断)。 -我确认后再执行快路径建单。 -``` - -确认回复示例: - -```text -优先级:高;推进至:设计完成;标签:运维管理条线 -``` - ---- - -## Agent 执行清单(内部,勿省略) - -| 步 | 动作 | -|---|---| -| 0 | 门禁:A+B+C 已明确;Implement ≠ 已选 | -| 1 | `$oneos-autoprd` → HTML 描述 | -| 2 | `POST …/workitem/workitem` 建需求 + document | -| 3 | 字段:priority、assignedTo、79=今日 | -| 4 | 若 B∈{分析中,设计中,开发中} → 同名阶段任务(查重复用) | -| 5 | 状态推进至 B(开发中三跳) | -| 6 | `PATCH …/workitem/{id}` 打标签 COVER | -| 7 | 回报 ONEOS-xxx + 链接 + 核对表 | - -标签 API 形态: - -```json -{ - "workitemIdentifier": "{id}", - "propertyKey": "tag", - "propertyValue": "{identifier1},{identifier2}", - "operateType": "COVER" -} -``` - -参考:`assets/oneos-pc-tag-catalog.md`、`assets/oneos-pc-runtime-ids.json` → `tags.by_name` diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/references/oneos-terminal-flow.md b/.cursor/skills/yunxiao-requirement-lifecycle/references/oneos-terminal-flow.md deleted file mode 100644 index 269f36a..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/references/oneos-terminal-flow.md +++ /dev/null @@ -1,192 +0,0 @@ -# OneOS终态交付模型与迁移规则 - -## 目录 - -1. 模型选择与边界 -2. 工作项和状态 -3. 原生Y规则 -4. AI与Webhook桥接 -5. 迁移就绪门禁 -6. 分阶段实施 -7. 验收与负向测试 -8. 已知风险和回滚 - -## 1. 模型选择与边界 - -OneOS终态模型以需求为阶段看板唯一真相: - -```text -迭代 -├─ 多个产品类需求 -├─ 按需求分包的测试计划 -└─ 一条【发版】任务 - -产品类需求 -└─ 一条【交付】主任务 - ├─ 多条【开发】任务 - └─ 一条【测试】任务 -``` - -每个项目只能选择一个主模型: - -- `stage_tasks`:分析、设计、开发、测试各建阶段任务,使用R/TK控制。 -- `oneos_delivery`:一条交付主任务镜像需求,多开发任务汇总,使用Y/A控制。 - -不得同时启用两套进场规则。若项目仍有分析/设计标签驱动的R规则,先记录为`legacy_active`,完成迁移就绪检查后再停用。 - -## 2. 工作项和状态 - -### 2.1 任务身份 - -| 任务 | 数量 | 必须关系 | 识别优先级 | -|---|---:|---|---| -| `【交付】`主任务 | 每需求1条 | 正式关联需求 | 独立工作项类型 > 任务标签`交付` > 标题前缀流程约束 | -| `【开发】`任务 | 每需求1..N条 | 正式关联需求和主任务 | 独立类型 > 标签`开发` > 标题前缀 | -| `【测试】`任务 | 每需求1条 | 正式关联需求和主任务 | 独立类型 > 标签`测试` > 标题前缀 | -| `【发版】`任务 | 每迭代1条 | 正式关联迭代和本批需求 | 独立类型 > 标签`发版` > 标题前缀 | - -标题关键字本身不构成正式关系。规则编辑器不能读取关联任务的类型、标签或标题时,不得声称原生规则能区分主任务和开发任务;使用桥接或人工门禁。 - -### 2.2 需求状态 - -目标状态顺序: - -```text -待处理 → 已确认 → 分析中 → 分析完成 → 设计中 → 设计完成 -→ 待开发 → 开发中 → 开发完成 → 待测试 → 测试中 → 测试完成 -→ 发布中 → 发布完成 / 发布失败 → 已关闭 -``` - -允许的快轨至少包括`已确认 → 待开发`和`设计完成 → 待开发`。`发布失败 → 发布中`用于重试。已有近义状态先映射,禁止静默创建`完成发布/发布完成`、`已完成/已关闭`等重复状态。 - -### 2.3 任务状态 - -- 主任务:首选与需求同名镜像状态。 -- 开发任务:`待处理 → 处理中 → 已完成`,另有取消态。 -- 测试任务:`待处理/待测试 → 测试中 → 已完成`。 -- 发版任务:`待处理 → 发布中 → 发布完成/发布失败`。 - -若任务工作流不能承载主任务全量状态,不配置Y02–Y16原生镜像;由桥接更新需求并记录主任务显示口径。 - -## 3. 原生Y规则 - -统一规则前缀:`[OneOS终态] Yxx ...`。保存后重新打开核对事件、条件、动作、顺序、启用状态和执行账号。 - -### 3.1 主任务镜像 - -| ID | 触发 | 条件 | 动作 | 默认方式 | -|---|---|---|---|---| -| Y01 | 新增正式关联主任务 | 主任务可可靠识别;需求为待处理/已确认 | 需求与主任务进入分析中或获批快轨 | 原生/桥接 | -| Y02 | 主任务到分析完成 | 正式关联唯一需求 | 需求到分析完成 | 原生 | -| Y03 | 主任务到设计中 | 正式关联唯一需求 | 需求到设计中 | 原生 | -| Y04 | 主任务到设计完成 | 正式关联唯一需求 | 需求到设计完成 | 原生 | -| Y05 | 主任务到待开发 | 正式关联唯一需求 | 需求到待开发;负责人按项目配置 | 原生/桥接 | -| Y06 | 主任务到开发中 | 正式关联唯一需求 | 需求到开发中 | 原生 | -| Y07 | 主任务到开发完成 | 正式关联唯一需求 | 需求到开发完成 | 原生 | -| Y08 | 主任务到待测试 | 正式关联唯一需求 | 需求到待测试 | 原生 | -| Y09 | 主任务到测试中 | 正式关联唯一需求 | 需求到测试中 | 原生 | -| Y10 | 主任务到测试完成 | Testhub门禁通过 | 需求到测试完成 | 桥接优先 | -| Y11 | 主任务到发布中 | 发版任务和迭代范围有效 | 需求到发布中 | 原生/桥接 | -| Y12 | 主任务到发布完成 | 生产发布证据有效 | 需求到发布完成 | 桥接 | -| Y13 | 主任务到发布失败 | 生产发布失败证据有效 | 需求到发布失败并记录原因 | 桥接 | -| Y14 | 主任务到已关闭 | 产品验收证据有效 | 需求到已关闭 | 默认人工 | -| Y15 | 需求状态变化 | 存在唯一主任务且无循环风险 | 主任务同名镜像 | 可选原生 | -| Y16 | 需求快轨到待开发 | 存在唯一主任务 | 主任务到待开发 | 原生/桥接 | - -每条双向镜像规则必须有防循环条件。平台无法区分“由本规则写入”的事件时,只保留一个权威方向。 - -### 3.2 开发任务汇总 - -| ID | 触发 | 条件 | 动作 | 默认方式 | -|---|---|---|---|---| -| Y20 | 新增第一条开发任务或正式关联开发分支 | 需求=待开发;开发任务数量≥1;任务已指派 | 需求和主任务到开发中;对应开发任务处理中 | 原生/桥接 | -| Y21 | 开发任务完成 | 非取消开发任务数量≥1且全部完成;全部相关MR已合并 | 需求和主任务到开发完成 | 桥接优先 | -| Y22 | 开发完成 | 同需求无未取消测试任务;幂等键未处理 | 创建并关联唯一测试任务,进入待测试 | 桥接 | - -现有的“任务添加分支且标签=开发,待处理→处理中”和“全部MR已合并且标签=开发,处理中→已完成”可作为Y20/Y21的任务侧子规则保留。需求侧“全部MR已合并”只能看到正式关联资产,必须执行多仓库负向测试。 - -### 3.3 测试、发布和迭代 - -| ID | 触发 | 条件 | 动作 | 默认方式 | -|---|---|---|---|---| -| Y30 | 创建测试任务 | 正式关联需求和主任务 | 需求/主任务到待测试 | 桥接 | -| Y31 | 首条用例执行或测试负责人开工 | 测试任务=待处理/待测试 | 测试任务、需求、主任务到测试中 | 原生/人工 | -| Y32 | 测试验收完成 | 本需求用例包全部通过且缺陷闭环 | 三个对象完成测试阶段 | 桥接/人工 | -| Y33 | 发版任务提交 | 发版任务正式关联迭代与本批需求 | 发版任务和本批需求到发布中 | 原生/桥接 | -| Y34 | 生产发布成功 | 流水线执行、环境、范围、签名和幂等均有效 | 到发布完成 | 桥接 | -| Y35 | 生产发布失败 | 同上;失败证据有效 | 到发布失败并记录原因 | 桥接 | -| Y40 | 需求到待开发 | 未关联迭代 | 通知产品负责人,不自动写入未知迭代 | 通知/人工 | - -test部署只能允许测试开始,不能触发Y34。发布完成后必须停留,等待产品/业务验收;禁止自动进入已关闭。 - -## 4. AI与Webhook桥接 - -| ID | 能力 | 最小门禁 | -|---|---|---| -| A01 | 发布原型/附件并回写需求描述 | 外部存储授权;链接可审计 | -| A02 | 生成并保留更新内容历史 | 不覆盖历史段;**OneOS 默认调用 `$oneos-autoprd` 第 10 章「功能变更记录」(自上次定稿以来);旧段挪到「更新内容·历史」** | -| A02B | 生成产品可读需求说明 | 不写实现机密;保留来源;**OneOS 默认调用 `$oneos-autoprd` 全文/交付口径写入「需求说明」** | -| A03 | 建需求和唯一主任务并快轨待开发 | 幂等查询;正式关系可见;**建单写描述前先完成 A02+A02B(AutoPRD)**;进入待开发时按 [auto-stage-task.md](auto-stage-task.md) 建/复用与需求同名任务,负责人何斐,时间沿用需求 | -| A03B | 需求进入分析中/设计中自动建同名阶段任务 | 标签分析/设计;负责人=需求创建人;正式关联;时间沿用需求;与 A03 同一套查重 | -| A04 | 拆分多条开发任务 | 负责人、范围和父子关系明确 | -| A05 | 按MR证据关闭开发任务 | 所有仓库、目标分支和关联关系完整 | -| A06 | 全部开发完成后建唯一测试任务 | 幂等键=`项目ID+需求ID+create_test_task` | -| A07 | 按Testhub结果完成测试 | 用例与缺陷口径满足TC/DF控制 | -| A08 | 按迭代创建发版任务和更新说明 | 正式关联迭代和本批需求 | -| A09 | 发布成败回写 | 验证环境、签名、时间戳、执行ID和范围 | -| A10 | 产品验收后关闭 | 明确验收人和证据;不得由发布成功替代 | - -所有桥接必须具备:最小权限服务账号、签名、时间戳、防重放、幂等、失败重试、审计日志和人工回退。不得把未部署的AI能力写成已自动化。 - -## 5. 迁移就绪门禁 - -只有全部满足时才停用旧R/TK进场规则: - -1. 需求状态已包含`待测试`、`发布中`、`发布失败`,并配置必要流转边。 -2. 主任务能被可靠识别;若只能靠标题前缀,已明确由桥接处理,不使用无过滤原生镜像。 -3. 主任务和开发/测试/发版任务的工作流已选定并验证。 -4. Y/A控制逐项指定`native_rule`、`integration_bridge`、`manual`或`not_supported`。 -5. A03、A06、A08、A09具备幂等键、负责人和回退方案。 -6. Codeup前后端仓库、真实`dev/develop`、正式关联和多仓库MR门禁已验证。 -7. Testhub按需求分包;未执行、阻塞、失败用例和仅“已修复”缺陷会阻塞测试完成。 -8. test、生产发布、发布变更和产品验收证据已分离。 -9. 已保存旧规则名称、条件、顺序、启用状态和执行账号证据。 -10. `L01`、`L02`已停用,发布完成不会瞬时关闭。 - -未通过门禁时:保留当前可用规则,只停用会绕过验收或造成误触发的规则,并输出迁移计划。 - -## 6. 分阶段实施 - -- P0:补齐工作流、唯一主任务、快轨待开发、负责人和旧规则备份。 -- P1:开发任务拆分、分支/MR双关联、多仓库完成门禁、唯一测试任务。 -- P2:迭代、Testhub按需求分包、用例和缺陷复测闭环。 -- P3:发版任务、生产流水线成败回写、发布失败重试、产品验收关闭。 - -每阶段先在隔离测试需求验证,再启用下一阶段。新规则不补触发历史事件;历史数据修正必须单独记录。 - -## 7. 验收与负向测试 - -| ID | 操作 | 期望 | 负向检查 | -|---|---|---|---| -| V01 | 创建需求和唯一主任务 | 正式关联且不重复 | 重放不得建第二条主任务 | -| V02 | 未建开发任务 | 保持待开发 | 主任务本身不得被当作开发任务 | -| V03 | 建两条开发任务 | 进入开发中 | 无正式关系或无负责人不触发 | -| V04 | 只完成一条开发任务/MR | 保持开发中 | 不得提前完成 | -| V05 | 所有开发任务和MR完成 | 开发完成并只建一条测试任务 | 重复回调不重复创建 | -| V06 | 同迭代其他需求未测完 | 本需求仍可独立完成测试 | 不按整个计划全绿判断 | -| V07 | 用例失败且缺陷仅已修复 | 保持测试中 | 未复测不得完成 | -| V08 | 复测通过/失败 | 用例通过+缺陷关闭 / 用例失败+缺陷重开 | 双方状态必须一致 | -| V09 | test部署成功 | 允许测试开始 | 不得发布完成 | -| V10 | 生产发布成功 | 发布完成并停留 | 不得自动已关闭 | -| V11 | 生产发布失败后重试 | 发布失败→发布中 | 原因和执行ID可审计 | -| V12 | 产品验收 | 已关闭 | 无验收证据不关闭 | - -## 8. 已知风险和回滚 - -- “全部关联任务完成”若无法按任务身份过滤,会把主任务或测试任务纳入,默认交桥接。 -- 仅标题前缀不能替代正式关系,也不能保证原生规则可过滤。 -- 需求和主任务双向镜像可能循环,只保留一个权威方向或增加来源防重。 -- 分支/MR只关联需求而未关联开发任务,会导致任务状态不更新。 -- 发布完成自动关闭会绕过产品验收;`L01`、`L02`默认停用。 - -回滚顺序:先禁用本次新增Y/A入口,恢复旧规则启用状态和顺序,再恢复状态流转边;保留已创建任务、关系和执行日志,删除任何资产需单独授权。 diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/references/pipeline-release.md b/.cursor/skills/yunxiao-requirement-lifecycle/references/pipeline-release.md deleted file mode 100644 index 4290aca..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/references/pipeline-release.md +++ /dev/null @@ -1,66 +0,0 @@ -# 流水线、发布与最终验收 - -## 目录 - -1. 环境证据边界 -2. R11发布完成 -3. CI02回调POC -4. CI03证据分离 -5. R12最终验收 -6. L01–L02历史规则 -7. 连锁触发验证 - -## 1. 环境证据边界 - -分别记录以下事件,未经批准不得互相替代: - -- test环境部署成功。 -- 生产环境流水线发布成功。 -- 云效关联发布变更达到完成状态。 -- 产品/业务验收完成。 - -流水线成功不等于测试用例通过,test成功也不默认等于生产发布完成。 - -## 2. R11发布完成 - -优先使用“关联发布变更达到项目真实完成状态”作为R11证据。规则要求当前状态=`测试完成`,目标环境和发布证据满足组织口径。 - -如果组织明确把test部署定义为“完成发布”,必须记录该口径的批准证据;否则test回调只记录测试部署成功,不推进发布完成。 - -## 3. CI02回调POC - -1. 复制现有test流水线并使用明显POC名称。 -2. 保留原test与prod流水线不变。 -3. 只在副本Docker部署成功路径末尾添加回调。 -4. 普通变量传工作项ID、环境、流水线执行ID。 -5. 密钥变量保存认证材料,不写入仓库、命令输出或文档。 -6. 请求携带时间戳、签名和执行ID幂等键。 -7. 服务端校验需求当前状态,只允许预期流转执行一次。 -8. 测试成功、重复回调、错误签名、超时、部署失败和安全重试。 -9. POC通过后走推广评审,不直接修改prod。 - -## 4. CI03证据分离 - -CI03要求项目配置分别保存test、prod、发布变更和验收证据。任何桥接都必须记录实现方式、启用状态、负责人、证据和人工回退。 - -回调失败保留失败日志并可安全重试,不得伪造成功。同一执行ID重试不得多次推进需求。 - -## 5. R12最终验收 - -发布成功不等于业务验收。默认由产品/业务负责人确认后人工进入`已完成`或`已关闭`。只有可靠、可审计的验收任务才允许条件化自动关闭。 - -## 6. L01–L02历史规则 - -- `L01`:`发布完成 + 全部关联任务完成/关闭 → 项目终态`。默认停用或收紧为独立验收任务,避免早期阶段任务误满足。 -- `L02`:监听`测试完成 → 发布完成`状态路径并立即进入项目终态。默认停用,防止发布完成成为瞬时状态。 - -逐项目记录两条规则是否存在、是否启用、真实条件、顺序和证据;不存在也要有盘点证据。 - -## 7. 连锁触发验证 - -- 不按规则名称推断行为,逐条读取触发、条件、动作、顺序和执行账号。 -- 每个目标状态都检查后继规则和重复规则。 -- R11后至少观察两个时点:刚进入发布完成时,以及等待5–30秒后。 -- 未经验收自动进入终态时,记录实际后继规则并判定`误触发`。 -- 跨两级以上流转必须有独立负向测试,不能只验证最终状态。 - diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/references/report-template.md b/.cursor/skills/yunxiao-requirement-lifecycle/references/report-template.md deleted file mode 100644 index 3bc1210..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/references/report-template.md +++ /dev/null @@ -1,97 +0,0 @@ -# 云效自动化执行报告模板 - -```markdown -# 云效产品需求全生命周期自动化执行报告 - -## 1. 基本信息 - -- 组织: -- 项目: -- 工作项类型: -- 流程模型:stage_tasks / oneos_delivery -- 迁移阶段(仅oneos_delivery):P0 / P1 / P2 / P3 -- 执行模式:audit / plan / apply / test / document -- 执行时间: -- 执行账号:仅记录显示名,不记录凭据 - -## 2. 观察到的当前配置 - -| 项目 | 生命周期 | 标签 | 仓库/集成分支 | test流水线 | 发布证据 | -|---|---|---|---|---|---| - -## 3. 规则变更 - -| 项目 | 规则 | 变更前 | 变更后 | 启用状态 | 复开核验 | -|---|---|---|---|---|---| - -### 3.0 项目级规则实例 - -| 项目 | 规则ID | 云效规则显示名/人工门禁 | 精确触发事件 | 源状态 | 必要条件 | 动作/目标状态 | 顺序 | 启用 | 观察证据 | -|---|---|---|---|---|---|---|---:|---|---| - -每个项目必须分别覆盖`R01`–`R12`和`TK01`–`TK08`。不得把一个项目的规则ID、显示名、顺序或证据复制为另一个项目的观察结果。 - -### 3.1 完整性台账 - -| 控制ID | 对象/流转 | 实现方式 | 启用状态 | 观察证据 | 负责人 | 回退方案 | 结论 | -|---|---|---|---|---|---|---|---| - -每个项目必须分别覆盖全部31项:`R01`–`R12`、`TK01`–`TK08`、`TC01`–`TC03`、`DF01`–`DF03`、`CI01`–`CI03`、`L01`–`L02`。不得把“规则编辑器不支持”写成“已完成”;应记录为`not_supported`并给出人工门禁或集成桥接方案。 - -`oneos_delivery`模型改用48项台账:`Y01`–`Y16`、`Y20`–`Y22`、`Y30`–`Y35`、`Y40`、`A01`、`A02`、`A02B`、`A03`–`A10`、`TC01`–`TC03`、`DF01`–`DF03`、`CI01`–`CI03`、`L01`–`L02`。 - -### 3.1a OneOS迁移就绪度 - -| 项目 | 目标状态缺口 | 主任务身份方式 | 原生规则能否过滤 | 任务工作流方案 | 桥接幂等/负责人 | 旧规则处置 | 结论 | -|---|---|---|---|---|---|---|---| - -旧R/TK规则只有在目标状态、任务身份、任务工作流、桥接和回滚证据全部就绪后才能停用。`L01`、`L02`不受此等待条件影响,应保持停用以保护产品验收。 - -### 3.2 规则顺序与连锁触发 - -| 前置规则 | 目标状态 | 后继规则 | 是否自动二次流转 | 是否绕过验收 | 处理结论 | -|---|---|---|---|---|---| - -## 4. 验证结果 - -| 用例 | 触发事件 | 期望状态 | 实际状态 | 结果分类 | 执行日志/证据 | -|---|---|---|---|---|---| - -## 5. 研发资产覆盖 - -| 仓库 | dev/develop | 分支关联 | 提交关联 | MR关联 | 全部合并判断 | -|---|---|---|---|---|---| - -## 6. 流水线与发布 - -| 流水线 | 环境 | 是否副本 | Docker部署 | 回调 | 是否影响prod | -|---|---|---|---|---|---| - -## 7. 测试计划与缺陷闭环 - -| 测试计划 | 用例总数 | 已通过 | 未执行/待测试 | 阻塞 | 未通过 | 结论 | -|---|---:|---:|---:|---:|---:|---| - -| 缺陷 | 关联用例/需求 | 修复状态 | 复测结果 | 最终状态 | 证据 | -|---|---|---|---|---|---| - -| 失败用例 | 关联缺陷 | 缺陷已修复后用例状态 | 复测通过后用例状态 | 复测失败后用例状态 | 证据 | -|---|---|---|---|---|---| - -## 8. 人工节点与剩余风险 - -- 真实开发开始时间: -- 业务验收: -- 发布完成是否被后继规则立即关闭: -- 全部关联任务范围: -- 多仓库漏关联风险: -- 其他: -- 范围外通用自动化(通知、指派、SLA、字段同步等): - -## 9. 回滚 - -- 禁用新增规则: -- 恢复旧规则: -- 停用回调POC: -- 测试资产清理(需授权): -``` diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/references/role-trigger-prompts.md b/.cursor/skills/yunxiao-requirement-lifecycle/references/role-trigger-prompts.md deleted file mode 100644 index 71815ff..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/references/role-trigger-prompts.md +++ /dev/null @@ -1,499 +0,0 @@ -# 云效全流程角色与触发节点沟通话术 - -> 本文是异常处理和精细交接使用的详细版。日常操作优先读取`simple-role-commands.md`,使用“记录需求、开始分析、开始开发、提交测试、测试完成、发布成功、验收通过”等短口令。 - -## 目录 - -1. 使用规则 -2. 通用话术骨架 -3. 项目管理员与流程负责人 -4. 产品负责人 -5. 需求分析人员 -6. 设计人员 -7. 交付负责人 -8. 技术负责人 -9. 开发人员 -10. 代码评审人员 -11. 测试负责人和测试人员 -12. 缺陷修复人员 -13. 运维与发布负责人 -14. 业务验收人员 -15. 集成桥接负责人 -16. 异常和催办话术 -17. 节点交接清单 - -## 1. 使用规则 - -这套话术用于让不同角色把真实业务事件准确告诉Codex,再由Codex审计、规划、执行获批修改、验证或形成报告。默认使用`yunxiao-requirement-lifecycle`。 - -每次沟通至少提供: - -- 精确项目名和需求编号。 -- 当前角色、当前状态和刚刚发生的真实事件。 -- 关联任务、仓库、分支、MR、测试计划、缺陷或流水线执行ID;没有就明确写“无”。 -- 希望Codex采用的权限:`audit`、`plan`、`apply`、`test`或`document`。 -- 期望停在哪个状态,不要只说“继续流转”。 - -权限含义: - -- `audit`:只核查和报告,不修改云效。 -- `plan`:只输出修改方案和影响,不执行。 -- `apply`:只修改话术中明确列出的项目、工作项或规则。 -- `test`:使用隔离测试资产触发并收集证据。 -- `document`:只形成说明、记录或报告。 - -必须遵守: - -- 标题、需求编号、分支名或标签不能代替云效正式关联。 -- 首次提交不代表真实开始开发;人工“开始处理”是效能主口径,分支关联只是自动兜底。 -- `已修复`只表示交给测试复测,不是缺陷关闭。 -- test部署成功不等于测试通过,更不等于生产发布完成。 -- 发布成功后停留`发布完成`,必须有产品或业务验收才能进入`已关闭`。 -- 未明确授权时,不改生产流水线、不直接推送保护分支、不删除规则或测试资产。 -- 项目仍为`stage_tasks`时使用R/TK规则;只有迁移门禁通过后才使用完整`oneos_delivery`链路。 - -## 2. 通用话术骨架 - -### 2.1 请求执行 - -```text -使用 yunxiao-requirement-lifecycle。 -权限:【audit/plan/apply/test/document】 -项目:【精确项目名】 -流程模型:【未知/stage_tasks/oneos_delivery】 -我的角色:【角色】 -需求:【需求编号+标题】 -当前状态:【状态】 -刚发生的事件:【真实事件】 -相关资产:【任务/MR/分支/测试计划/缺陷/流水线执行ID;没有写无】 -期望结果:【目标状态或需要生成的资产】 -请先核查当前状态和正式关联,再执行授权范围内动作;输出实际动作、触发结果、证据、未完成项和下一责任人。不要用手工改状态冒充自动化通过。 -``` - -### 2.2 只核查为什么没有流转 - -```text -使用 yunxiao-requirement-lifecycle,权限=audit。 -项目:【项目名】,需求:【编号】,当前状态:【状态】。 -我已执行:【事件】,相关对象:【对象编号或链接】;等待了【秒数】仍未流转。 -请按事件、当前状态、标签位置、正式关联、规则启用、执行账号、执行日志、后继规则和异步窗口逐项诊断。不要直接替我改状态。 -``` - -### 2.3 请求修改规则 - -```text -使用 yunxiao-requirement-lifecycle,权限=apply。 -只允许修改项目【项目名】中的规则【精确规则名/控制ID】,目标是【目标】。 -修改前备份触发、条件、动作、顺序、启用状态和执行账号;一次只改一条,保存后重新打开核对,并用隔离需求做正向和负向测试。禁止修改生产流水线及无关规则。 -``` - -## 3. 项目管理员与流程负责人 - -### ADM01 项目首次审计或接管 - -触发:新项目接入、规则无人维护或准备复制到另一个项目。 - -```text -使用 yunxiao-requirement-lifecycle,权限=audit。 -请审计项目【项目名】,先判断是stage_tasks、oneos_delivery还是迁移中。盘点工作流状态、标签、工作项类型、全部自动化规则、执行账号、前后端仓库、dev/develop、Testhub、缺陷状态、test/prod流水线、发布证据和验收门禁。 -输出当前—目标差距、规则控制ID映射、风险、P0-P3实施顺序和不能自动化的人工门禁。不要修改线上配置。 -``` - -完成证据:项目画像、规则清单、模型判断、缺口和迁移阶段齐全。 - -### ADM02 迁移到OneOS终态模型 - -触发:准备从阶段任务切换到交付主任务模型。 - -```text -使用 yunxiao-requirement-lifecycle,权限=plan。 -项目【项目名】准备迁移到oneos_delivery。请检查待测试、发布中、发布失败状态,交付主任务识别方式,开发/测试/发版任务工作流,A03/A06/A08/A09幂等与负责人,多仓库MR门禁、Testhub分包、生产证据和L01/L02状态。 -只有十项迁移门禁全部通过才给出停用旧R/TK规则清单;否则保留现网主链,只列安全可做项和回滚方案。 -``` - -完成证据:迁移门禁逐项结论,而不是笼统“可以迁移”。 - -### ADM03 规则变更后回归 - -触发:新建、修改、启停任何生命周期规则。 - -```text -使用 yunxiao-requirement-lifecycle,权限=test。 -项目【项目名】刚修改规则【规则名/控制ID】。请创建或复用隔离测试需求,验证事件【事件】、源状态【状态】、目标状态【状态】,并增加错误状态、无正式关联、部分MR、重复回调或后继连锁等负向场景。 -结果只能记录为通过、异步通过、未触发、误触发或阻塞,并附执行日志和回滚入口。 -``` - -### ADM04 定期治理 - -触发:季度检查、状态/标签重命名、新增仓库或人员离职。 - -```text -使用 yunxiao-requirement-lifecycle,权限=audit。 -请对项目【项目名】执行运行治理检查:规则执行账号和权限、失败日志、状态/标签重命名影响、新增仓库Webhook、多仓库MR覆盖、超过7天无活动分支、回调签名/幂等/重试、L01/L02以及生产和验收证据分离。 -输出需要立即修复、计划修复和仅观察三类清单。 -``` - -## 4. 产品负责人 - -### PO01 新建并确认需求 - -触发:需求范围、负责人、优先级和验收标准已明确。 - -```text -使用 yunxiao-requirement-lifecycle,权限=apply。 -项目【项目名】需要建立需求【标题】。范围:【范围】;不包含:【排除项】;负责人:【姓名】;优先级:【级别】;目标迭代:【迭代】;验收标准:【标准】。 -请先查重,再创建或完善产品类需求。若项目为oneos_delivery,校验唯一【交付】主任务;若仍为stage_tasks,不要擅自创建主任务。完成后告诉我需求编号、正式关系、当前状态和下一责任人。 -``` - -### PO02 批准进入分析或快轨待开发 - -触发:需求确认后决定正常分析,或低风险小改走快轨。 - -正常路径: - -```text -项目【项目名】需求【编号】已确认,批准进入分析。使用 yunxiao-requirement-lifecycle,权限=apply。请核查范围、负责人和验收标准齐全,再按当前流程模型触发分析入口;不要跳过缺失信息。 -``` - -快轨路径: - -```text -项目【项目名】需求【编号】批准快轨到待开发,批准人【姓名】,原因【原因】,验收标准【标准】。使用 yunxiao-requirement-lifecycle,权限=apply。请确认快轨边存在、唯一交付主任务已同步且没有循环风险;缺少任一条件就停止并报告。 -``` - -### PO03 需求范围变更 - -触发:开发或测试中修改范围。 - -```text -使用 yunxiao-requirement-lifecycle,权限=plan。 -项目【项目名】需求【编号】当前【状态】,拟变更:【新增/删除内容】;原因:【原因】;受影响资产:【开发任务/MR/用例/缺陷/发布范围】。 -请先做影响分析,列出需要重开或新增的任务、MR、用例和验收项,以及是否应回退需求状态。未经我确认不要直接改状态或删除已有证据。 -``` - -### PO04 产品验收通过并关闭 - -触发:生产发布完成,业务验收通过。 - -```text -使用 yunxiao-requirement-lifecycle,权限=apply。 -项目【项目名】需求【编号】当前为发布完成。验收人【姓名】,验收时间【时间】,验收环境【生产】,验收结果【通过】,证据【链接/附件/记录】。 -请核查生产发布证据与需求范围一致,并确认不存在未关闭验收项;满足后执行已关闭并回报审计证据。不得用流水线成功替代本次验收。 -``` - -### PO05 验收不通过 - -```text -使用 yunxiao-requirement-lifecycle,权限=plan。 -项目【项目名】需求【编号】生产验收不通过。问题:【问题】;证据:【证据】;影响:【影响】。 -请创建或关联可追踪缺陷/改进任务,建议应回到的真实状态和责任人;未经批准不要直接关闭需求或覆盖原发布记录。 -``` - -## 5. 需求分析人员 - -### BA01 开始分析 - -```text -项目【项目名】需求【编号】由我开始分析,实际开始时间【时间】。使用 yunxiao-requirement-lifecycle,权限=apply。请核查当前状态和我的关联任务,将分析任务改为处理中并确认需求进入分析中;如果项目为oneos_delivery,则按主任务权威方向同步,避免双向循环。 -``` - -### BA02 分析完成 - -```text -项目【项目名】需求【编号】分析已完成。产物:【需求说明/流程/字段/边界链接】;未决项:【无或列表】;验收口径:【口径】。 -使用 yunxiao-requirement-lifecycle,权限=apply。请先检查分析任务非零且全部完成、产物可访问、未决项已处理,再推进分析完成;回报触发的是原生规则、桥接还是人工门禁。 -``` - -## 6. 设计人员 - -### UX01 开始设计 - -```text -项目【项目名】需求【编号】开始设计,设计负责人【姓名】,原型地址【地址】,实际开始时间【时间】。使用 yunxiao-requirement-lifecycle,权限=apply。请核查正式关联设计任务和当前状态,再更新设计任务为处理中并确认需求进入设计中。 -``` - -### UX02 设计完成并交接开发 - -```text -项目【项目名】需求【编号】设计完成。原型版本【版本】;设计稿【链接】;交互说明【链接】;响应式范围【范围】;已确认人【姓名】;遗留项【无或列表】。 -使用 yunxiao-requirement-lifecycle,权限=apply。请核查设计任务和交付物后完成设计阶段,并检查是否只创建一组正确的开发任务。不要因为标题含“开发”就视为已正式关联。 -``` - -完成证据:设计任务已完成、需求到设计完成;后继开发任务创建成功后才到待开发。 - -## 7. 交付负责人 - -### DL01 创建或核查唯一交付主任务 - -```text -使用 yunxiao-requirement-lifecycle,权限=apply。 -项目【项目名】需求【编号】需要建立【交付】主任务,负责人【姓名】。请先按正式关系和交付标签查重;不存在才创建,存在一条则复用,存在多条则停止并列出冲突。主任务必须正式关联需求,不能只靠标题前缀。 -``` - -### DL02 阶段同步异常 - -```text -使用 yunxiao-requirement-lifecycle,权限=audit。 -项目【项目名】需求【编号】状态【需求状态】,交付主任务【任务编号】状态【任务状态】,两者不一致。 -请判断权威方向、检查Y01-Y16、来源防重和执行日志,说明应补偿哪个对象。不要同时双向写入造成循环。 -``` - -## 8. 技术负责人 - -### TL01 拆分开发任务 - -```text -使用 yunxiao-requirement-lifecycle,权限=apply。 -项目【项目名】需求【编号】已到待开发。请按以下范围拆分开发任务:前端【范围/负责人/仓库】,后端【范围/负责人/仓库】,其他【范围】。每条任务必须正式关联需求和交付主任务、标签=开发、状态=待处理,并记录目标dev/develop分支。查重后创建,禁止重复任务。 -``` - -### TL02 仅前端或仅后端需求 - -```text -项目【项目名】需求【编号】只涉及【前端/后端】,不涉及【另一端】。使用 yunxiao-requirement-lifecycle,权限=apply。请只创建实际需要的开发任务,并把不涉及的仓库明确记为不适用;不要用缺少另一端MR阻塞,也不要把未盘点仓库默认为已完成。 -``` - -### TL03 开发完成门禁核查 - -```text -使用 yunxiao-requirement-lifecycle,权限=audit。 -项目【项目名】需求【编号】准备判定开发完成。相关仓库:【列表】;开发任务:【列表】;MR:【列表及目标分支】。 -请确认所有非取消开发任务完成、所有相关MR正式双关联并合并到真实dev/develop;任一仓库未完成都保持开发中。输出缺口,不要手工改成开发完成。 -``` - -## 9. 开发人员 - -### DEV01 真实开始开发并创建分支 - -```text -使用 yunxiao-requirement-lifecycle,权限=apply。 -我开始处理项目【项目名】需求【编号】的开发任务【任务编号】,真实开始时间【时间】,仓库【仓库名】,基线【dev/develop】,拟建分支【feature/编号或fix/编号】。 -请先确认基线真实存在且任务为待处理,再从基线创建或指导创建分支,并确保分支同时正式关联需求和开发任务。完成后核查需求=开发中、任务=处理中;不要用首次提交时间覆盖真实开始时间。 -``` - -### DEV02 提交代码并创建MR - -```text -使用 yunxiao-requirement-lifecycle,权限=apply。 -项目【项目名】需求【编号】,开发任务【任务编号】,仓库【仓库】,分支【分支】,目标分支【dev/develop】。改动摘要:【摘要】;本地验证:【命令和结果】。 -请先拉取并检查冲突,再按仓库规范提交、推送并创建MR;MR必须正式关联需求和开发任务。不要直接推保护分支,不要因为提交成功就把开发标为完成。 -``` - -### DEV03 多仓库部分完成 - -```text -项目【项目名】需求【编号】目前仅仓库【仓库A】的MR【编号】已合并,仓库【仓库B】仍在开发。使用 yunxiao-requirement-lifecycle,权限=audit。请确认需求和交付主任务仍保持开发中,并检查是否存在“部分MR导致提前完成”的误触发规则。 -``` - -## 10. 代码评审人员 - -### REV01 评审要求修改 - -```text -项目【项目名】MR【编号】评审不通过,需要修改:【问题列表】。使用 yunxiao-requirement-lifecycle,权限=document。请把评审结论关联到需求【编号】和开发任务【编号】,确认MR保持未合并、开发任务保持处理中、需求保持开发中。 -``` - -### REV02 MR合并后核查 - -```text -使用 yunxiao-requirement-lifecycle,权限=audit。 -项目【项目名】MR【编号】已合并到【dev/develop】,关联需求【编号】、开发任务【编号】。请等待异步窗口后核查任务状态;再汇总同需求所有仓库和MR。只有全部相关任务和MR完成才允许需求进入开发完成,并确认测试任务只创建一条。 -``` - -## 11. 测试负责人和测试人员 - -### QA01 建立测试任务和用例范围 - -```text -使用 yunxiao-requirement-lifecycle,权限=apply。 -项目【项目名】需求【编号】开发已完成,目标迭代【迭代】,测试负责人【姓名】。请查重后创建或复用唯一【测试】任务,并将测试计划按需求编号建立独立用例包。范围:【功能/接口/权限/兼容/回归】;不适用:【列表】。正式关联需求、主任务和测试计划后进入待测试。 -``` - -### QA02 test部署成功,开始测试 - -```text -项目【项目名】需求【编号】的test部署成功,流水线【名称】,执行ID【ID】,环境【test】,部署时间【时间】。使用 yunxiao-requirement-lifecycle,权限=apply。请校验执行ID、范围和重复回调,再允许测试任务和需求进入测试中。明确记录:本事件不能推进发布完成。 -``` - -### QA03 用例失败并提交缺陷 - -```text -使用 yunxiao-requirement-lifecycle,权限=apply。 -项目【项目名】需求【编号】,测试计划【计划】,用例【用例ID】执行失败。环境【环境】;前置条件【条件】;步骤【步骤】;实际结果【实际】;期望结果【期望】;证据【截图/日志】;严重程度【级别】。 -请查重后创建或关联缺陷,并把缺陷与原用例、需求正式关联。保持用例未通过、测试任务和需求测试中;不要把创建缺陷当作测试完成。 -``` - -### QA04 缺陷复测通过 - -```text -项目【项目名】缺陷【编号】已在环境【环境】复测,原用例【用例ID】重新执行通过,证据【证据】,复测人【姓名】,时间【时间】。 -使用 yunxiao-requirement-lifecycle,权限=apply。请将原用例更新为通过,并把缺陷进入项目真实关闭态;随后重新核查本需求是否仍有未执行、阻塞、失败用例或未关闭缺陷。 -``` - -### QA05 缺陷复测失败 - -```text -项目【项目名】缺陷【编号】复测失败,原用例【用例ID】仍未通过。实际结果【结果】;证据【证据】。 -使用 yunxiao-requirement-lifecycle,权限=apply。请重开缺陷或转回项目真实处理中状态,保持原用例未通过、需求测试中,并通知原修复负责人。不要保留“已修复/已关闭”。 -``` - -### QA06 测试验收完成 - -```text -使用 yunxiao-requirement-lifecycle,权限=apply。 -项目【项目名】需求【编号】申请完成测试。测试计划【计划】;用例包【范围】;执行统计【通过/失败/阻塞/未执行】;缺陷清单【编号+状态】;测试负责人【姓名】。 -请按TC01-TC03和DF01-DF03核查。只有全部范围用例已执行并通过、失败用例已复测、缺陷由测试关闭时,才完成测试任务并推进需求测试完成;否则列出阻塞项。 -``` - -## 12. 缺陷修复人员 - -### BUG01 接收缺陷并开始修复 - -```text -使用 yunxiao-requirement-lifecycle,权限=apply。 -我接收项目【项目名】缺陷【编号】,关联需求【编号】、原用例【ID】,开始修复时间【时间】,仓库【仓库】,拟用分支【fix/缺陷编号】。 -请核查缺陷、需求和用例的正式关联,再把缺陷转入真实处理中状态;分支和MR同时关联缺陷、需求及对应开发任务。 -``` - -### BUG02 修复完成交给测试 - -```text -项目【项目名】缺陷【编号】已修复。分支【分支】;MR【编号】;合并目标【dev/develop】;验证结果【结果】;部署环境【test/尚未部署】。 -使用 yunxiao-requirement-lifecycle,权限=apply。请核查代码和部署证据后把缺陷改为已修复并交给测试复测。不要关闭缺陷,不要把原失败用例改为通过。 -``` - -## 13. 运维与发布负责人 - -### OPS01 test部署回报 - -```text -使用 yunxiao-requirement-lifecycle,权限=audit。 -项目【项目名】test流水线【名称】执行【成功/失败】,执行ID【ID】,提交/MR范围【范围】,时间【时间】。 -请核查本次执行关联的需求和幂等记录。成功只允许测试开始;失败保持原状态并记录原因。不要触发生产发布完成。 -``` - -### OPS02 创建迭代发版任务 - -```text -使用 yunxiao-requirement-lifecycle,权限=apply。 -项目【项目名】迭代【迭代名/ID】准备发版,计划窗口【时间】,负责人【姓名】,本批需求【编号列表】,生产流水线【名称】。 -请查重后创建或复用唯一【发版】任务,正式关联迭代和本批需求,并生成发布范围与回滚项。需求未测试完成或范围不一致时停止,不进入发布中。 -``` - -### OPS03 开始生产发布 - -```text -项目【项目名】发版任务【编号】批准开始生产发布。审批人【姓名】;窗口【时间】;生产流水线【名称】;本批需求【列表】;回滚方案【链接】。 -使用 yunxiao-requirement-lifecycle,权限=apply。请核查全部需求测试完成、范围一致和审批证据,再进入发布中。只操作明确的生产发布,不修改流水线定义。 -``` - -### OPS04 生产发布成功 - -```text -使用 yunxiao-requirement-lifecycle,权限=apply。 -项目【项目名】生产流水线【名称】执行成功,执行ID【ID】,环境【prod】,完成时间【时间】,发布范围【需求/MR/版本】,证据【链接】。 -请验证签名、时间戳、执行ID幂等和范围,推进发版任务及本批需求到发布完成并停留,通知产品验收。禁止自动进入已关闭。 -``` - -### OPS05 生产发布失败或重试 - -失败: - -```text -项目【项目名】生产流水线【名称】执行失败,执行ID【ID】,失败阶段【阶段】,原因【原因】,影响需求【列表】,回滚结果【结果】。 -使用 yunxiao-requirement-lifecycle,权限=apply。请记录失败证据并推进到发布失败;不得伪造成功或关闭需求。 -``` - -重试: - -```text -项目【项目名】发版任务【编号】批准从发布失败重试,批准人【姓名】,新执行ID【ID】,已修正原因【说明】。 -使用 yunxiao-requirement-lifecycle,权限=apply。请核查发布失败→发布中流转边、幂等键和范围后开始重试,保留原失败记录。 -``` - -## 14. 业务验收人员 - -### ACC01 验收通过 - -```text -我是业务验收人【姓名】。项目【项目名】需求【编号】已在生产环境按验收项【列表】验证通过,时间【时间】,证据【链接/附件】。 -使用 yunxiao-requirement-lifecycle,权限=apply。请核查身份、发布完成状态和证据范围后关闭需求,并保留验收记录。 -``` - -### ACC02 验收不通过 - -```text -我是业务验收人【姓名】。项目【项目名】需求【编号】生产验收不通过,失败项【列表】,实际结果【结果】,证据【证据】。 -使用 yunxiao-requirement-lifecycle,权限=plan。请建立可追踪问题清单,分析应回到测试、开发还是重新发布,并指定下一责任人;不要关闭需求。 -``` - -## 15. 集成桥接负责人 - -### INT01 回调未生效或重复 - -```text -使用 yunxiao-requirement-lifecycle,权限=audit。 -桥接控制【A03/A06/A08/A09/其他】,项目【项目名】,需求【编号】,事件ID【ID】,时间戳【时间】,回调结果【未生效/重复/失败】,日志摘要【摘要】。 -请核查签名、时间窗、防重放、幂等键、当前状态、权限、重试和审计日志。不要绕过校验直接补写状态;给出安全重试或人工回退方案。 -``` - -### INT02 幂等补偿 - -```text -使用 yunxiao-requirement-lifecycle,权限=apply。 -项目【项目名】控制【控制ID】需要补偿,原事件ID【ID】,期望对象【任务/状态】,当前对象【实际】。已确认原操作未成功且不存在重复对象。 -请再次查询幂等键和正式关系,只补偿缺失动作;完成后输出新旧事件关联、对象ID和重复检查证据。 -``` - -## 16. 异常和催办话术 - -### 状态长时间不动 - -```text -项目【项目名】需求/任务【编号】在【状态】停留【时长】,最后真实事件【事件】,负责人【姓名】,关联资产【列表】。使用 yunxiao-requirement-lifecycle,权限=audit。请判断是业务未完成、自动化未触发还是异步失败,并给出下一责任人和最小恢复动作;不要直接跳状态。 -``` - -### 状态被提前推进 - -```text -项目【项目名】需求【编号】从【源状态】意外进入【目标状态】,时间【时间】。使用 yunxiao-requirement-lifecycle,权限=audit。请检查后继规则、全部关联项口径、部分MR、未复测缺陷、test/prod混用和L01/L02,定位真实触发规则并给出回滚建议。未经确认不要回退状态。 -``` - -### 任务重复创建 - -```text -项目【项目名】需求【编号】出现重复【交付/开发/测试】任务:【任务列表】。使用 yunxiao-requirement-lifecycle,权限=audit。请检查幂等键、正式关系和重复回调,确定保留项及合并/取消方案。未经授权不要删除任务。 -``` - -## 17. 节点交接清单 - -每次角色交接都让Codex确认以下内容: - -1. 项目、需求、任务和流程模型准确。 -2. 源状态与目标状态明确。 -3. 事件真实发生,且正式关联可见。 -4. 当前责任人和下一责任人明确。 -5. 交付物、代码、用例、缺陷或发布证据可访问。 -6. 正向条件全部满足,负向阻塞项为零。 -7. 自动化结果与人工准备动作分开记录。 -8. 异步等待后重新核查状态和执行日志。 -9. 没有误触发后继规则或重复创建。 -10. 回滚入口、人工回退和未完成事项已记录。 - -Codex的节点回报统一使用: - -```text -项目: -流程模型: -工作项: -源状态 → 实际状态: -本次真实事件: -执行动作: -触发方式:原生规则/桥接/人工门禁/未触发 -验证结果:通过/异步通过/未触发/误触发/阻塞 -证据: -未完成项: -下一责任角色: -允许的下一动作: -回滚入口: -``` diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/references/simple-role-commands.md b/.cursor/skills/yunxiao-requirement-lifecycle/references/simple-role-commands.md deleted file mode 100644 index b94c7b6..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/references/simple-role-commands.md +++ /dev/null @@ -1,417 +0,0 @@ -# 云效日常口语化操作口令 - -## 1. 先说一次项目 - -每次新会话先输入: - -```text -项目:统一运营管理平台PC端 -``` - -之后直接说口令即可。项目没有明确、同一编号可能属于多个项目或用户切换项目时,Codex必须先问清项目,不能猜。 - -口令可以自然表达,不要求标点完全一致。例如: - -- `记录需求:增加车辆批量导出` -- `帮我记录一个需求,增加车辆批量导出` - -两句话含义相同。 - -## 2. 产品和需求 - -### 记录需求 - -产品输入: - -```text -记录需求:增加车辆批量导出 -``` - -Codex执行:查重后在当前项目创建产品类需求,初始状态为`待处理`,返回需求编号。只有标题也可以先记录;云效必填字段缺失时只追问缺少的字段。 - -**带模块/原型的记录(OneOS):** 若口令含模块名或原型(如「记录需求:保险采购」),创建前**先调用 `$oneos-autoprd`**,把「需求说明」写入描述;若用户同时定稿,一并写入「更新内容」。 - -**统一运营管理平台 · 快路径(强制):** 项目为「统一运营管理平台 / PC 端」时,先读 [oneos-pc-fast-path.md](oneos-pc-fast-path.md) 与 [oneos-pc-runtime-ids.json](../assets/oneos-pc-runtime-ids.json)。用已验证 `POST /workitem/workitem` 建单(创建时带 `document`),不要探测 `/workitem/create`。推进至「开发中」时状态连跳:`待处理 → 设计完成 → 待开发 → 开发中`。 - -**优先级 / 推进至 Plan 单选门禁(强制):** 用户要求 Plan 模式单选,或口令含「优先级」「推进至」需点选时: - -1. 先用 `AskQuestion`(可用时)或 Plan 确认拿到 A(紧急/高/中/低)与 B(分析中/设计中/设计完成/开发中)。 -2. **禁止**未选时默认「中 + 分析中」并继续建单。 -3. 「批准 / Implement 计划」本身不等于已选 A+B;计划里仍是空选项时必须先问清。 -4. 标签须用户从云效标签 catalog(`oneos-pc-tag-catalog.md`)点选;禁止按业务条线说明/`lines.ts` 自动推断。点选后 API 打标,失败则停并请用户回复标签名,禁止假装成功。 - -**完整可复制提示词(推荐):** 统一运营管理平台建单优先用 [oneos-pc-record-requirement-prompt.md](oneos-pc-record-requirement-prompt.md)——含 `$yunxiao-requirement-lifecycle` + `$oneos-autoprd`、A/B/C 三门禁与 30 项标签枚举。复制该文件「用户口令」整段发给 Agent;确认后再执行快路径。 - -简短版(填需求名与模块后复制): - -```text -使用 $yunxiao-requirement-lifecycle + $oneos-autoprd -记录需求到云效 · 统一运营管理平台 - -【需求名】(填写) -【描述来源】$oneos-autoprd,原型/模块:(填写) - -请先 Plan 让我点选 A 优先级、B 推进至、C 标签(30 项 catalog,见 oneos-pc-record-requirement-prompt.md;禁止 lines.ts 推断)。 -我确认后再执行快路径建单。 -``` - -确认回复示例:`优先级:高;推进至:设计完成;标签:运维管理条线` - -### 确认需求 - -产品输入: - -```text -确认需求:ONEOSP-123 -``` - -Codex执行:检查范围、负责人、优先级和验收标准,齐全后把需求改为`已确认`。 - -`确认需求`默认不创建分析任务。确认和真实开始分析是两个事件,防止开始时间失真。由分析人员下一步输入`开始分析`。 - -### 需求定稿(原型 + AutoPRD) - -产品输入: - -```text -保险采购需求定稿 -``` - -或: - -```text -需求定稿:ONEOSP-123;模块=保险采购 -``` - -Codex执行: - -1. 调用 `$oneos-autoprd` 定稿流程:更新原型 `.spec/requirements-prd.md` 第 10 章「功能变更记录」与 `autoprd-baseline.json`(只记功能/逻辑,不记样式/UI/表结构)。 -2. 若已有云效需求编号:刷新描述中的「需求说明」与「更新内容」;旧更新内容 →「更新内容·历史」。 -3. 若口令要求进待开发/发对象存储:继续走下方「快速进入开发」或 OneOS 快轨口令,不得跳过 AutoPRD。 - -### 快速进入开发 - -产品输入: - -```text -快速开发:ONEOSP-123;原因=小改动;负责人=张三;范围=只改后端 -``` - -Codex执行:检查项目是否允许快轨、验收标准是否明确,再跳过不需要的分析/设计阶段并建立必要开发任务。条件不齐时不跳状态,只提示缺少什么。 - -**OneOS 模块已定型进待开发(推荐):** - -```text -「保险采购」需求已经确定:先 AutoPRD 写需求说明与更新内容;发布对象存储并写入描述; -推进到待开发;自动建与需求同名的【交付】任务并正式关联;负责人何斐。 -``` - -执行前必须先跑 `$oneos-autoprd`(含定稿变更记录若用户刚定稿)。进入待开发时**必须**执行 [auto-stage-task.md](auto-stage-task.md)(同名任务、标签交付、负责人何斐、时间沿用需求);已有唯一交付任务则复用并改负责人为何斐,禁止建第二条。 - -### 需求变更 - -产品输入: - -```text -变更需求:ONEOSP-123;增加导出时间筛选 -``` - -Codex执行:先分析受影响的任务、MR、用例、缺陷和发布范围,给出应回到哪个阶段;产品确认后再修改,不直接覆盖历史说明。涉及原型功能变更时,同步 `$oneos-autoprd` 更新 PRD;用户再说「定稿」时写入第 10 章。 - -## 3. 分析和设计 - -### 开始分析 - -分析人员输入: - -```text -开始分析:ONEOSP-123;负责人=李四 -``` - -Codex执行: - -1. 将需求推进到`分析中`(或确认已在分析中)。 -2. **按 [auto-stage-task.md](auto-stage-task.md)**:查重后创建/复用与需求**同名**任务;标签=`分析`;正式关联需求;负责人默认=需求**创建人**(口令写了负责人则用口令);计划开始/创建时间沿用需求。 -3. 任务进入`处理中`(或项目约定的开工态)。 -4. `stage_tasks`:需求标签含`分析`。`oneos_delivery`:不因此再建第二条【交付】主任务。 - -### 分析完成 - -分析人员输入: - -```text -分析完成:ONEOSP-123;说明=https://... -``` - -Codex执行:检查分析产物和未决项;完成对应任务或主任务阶段,核查需求进入`分析完成`。 - -### 开始设计 - -设计人员输入: - -```text -开始设计:ONEOSP-123;负责人=王五 -``` - -Codex执行: - -1. 将需求推进到`设计中`。 -2. **按 auto-stage-task**:同名任务;标签=`设计`;正式关联;负责人默认=需求**创建人**(口令可覆盖);时间沿用需求。 -3. 任务开工态同上。 -4. `oneos_delivery`:不同时新建交付主任务(交付主任务在待开发时建/复用)。 - -### 设计完成 - -设计人员输入: - -```text -设计完成:ONEOSP-123;原型=https://... -``` - -Codex执行:检查原型或设计说明,完成设计任务/主任务阶段,核查需求进入`设计完成`。 - -如果项目已经部署并验证自动建开发任务的桥接,而且负责人、仓库和范围明确,Codex继续查重并创建开发任务;否则停在`设计完成`,提示技术负责人输入`安排开发`。不得创建无负责人或重复任务。 - -**进入待开发时(含快轨):** 必须再跑 auto-stage-task:同名任务、标签`交付`(或`开发`)、负责人**何斐**、正式关联、时间沿用需求。 - -## 4. 开发 - -### 安排开发 - -技术负责人输入: - -```text -安排开发:ONEOSP-123;前端=张三/ln-one-os-web;后端=李四/ln-cloud -``` - -只改一端时可以说: - -```text -安排开发:ONEOSP-123;只改后端=李四/ln-cloud -``` - -Codex执行:按实际范围创建一条或多条`【开发】`任务,正式关联需求;`oneos_delivery`还要关联交付主任务。任务初始为`待处理`,需求进入`待开发`。明确“不涉及”的仓库不会成为完成门禁。 - -### 开始开发 - -开发人员输入: - -```text -开始开发:ONEOSP-123;任务=TASK-456;仓库=ln-cloud -``` - -Codex执行:记录真实开始时间,确认真实基线`dev`或`develop`,创建`feature/ONEOSP-123`并同时正式关联需求和开发任务。任务进入`处理中`,需求进入`开发中`。 - -首次提交只作兜底,不覆盖这次真实开始时间。 - -### 提交代码 - -开发人员输入: - -```text -提交代码:ONEOSP-123;任务=TASK-456;仓库=ln-cloud -``` - -Codex执行:检查改动和测试,先拉取并处理冲突,再提交、推送并创建合并到真实`dev/develop`的MR;MR同时关联需求和开发任务。提交成功不等于开发完成。 - -### 开发完成 - -开发人员或技术负责人输入: - -```text -开发完成:ONEOSP-123 -``` - -Codex执行:汇总该需求所有非取消开发任务、前后端仓库和正式关联MR。只有全部任务完成、全部相关MR已合并,才确认需求进入`开发完成`;少一个仓库也不提前完成。 - -条件满足后,如果项目已配置并验证唯一测试任务桥接,则查重创建测试任务;否则提示测试负责人输入`提交测试`。 - -## 5. 测试和缺陷 - -### 提交测试 - -测试负责人或开发负责人输入: - -```text -提交测试:ONEOSP-123;test流水线=oneos-web-test -``` - -Codex执行:确认开发完成,触发或核查明确的test流水线。部署成功后,查重创建并正式关联唯一测试任务和需求用例包,进入`待测试/测试中`的项目真实状态。 - -test成功只允许开始测试,不能变成发布完成。 - -### 开始测试 - -测试人员输入: - -```text -开始测试:ONEOSP-123;负责人=赵六 -``` - -Codex执行:核查test部署和测试任务,把测试任务改为`测试中/处理中`,需求进入`测试中`。 - -### 发现Bug - -测试人员输入: - -```text -发现Bug:ONEOSP-123;用例=CASE-12;现象=车辆来源显示0;期望=显示中文来源 -``` - -Codex执行:查重后创建缺陷,正式关联需求、原失败用例和必要开发任务;用例保持未通过,需求保持`测试中`。 - -### Bug修复完成 - -开发人员输入: - -```text -Bug修复完成:BUG-123;MR=456;已部署test -``` - -Codex执行:核查代码、MR和test部署后,把缺陷改为`已修复`并交给测试复测。不会关闭缺陷,也不会把原用例直接改成通过。 - -### 复测通过 - -测试人员输入: - -```text -复测通过:BUG-123;用例=CASE-12 -``` - -Codex执行:重新记录原用例为通过,把缺陷改为项目真实关闭态,再检查该需求是否还有失败、阻塞、未执行用例或未关闭缺陷。 - -### 复测失败 - -测试人员输入: - -```text -复测失败:BUG-123;用例=CASE-12;现象=仍显示0 -``` - -Codex执行:原用例保持未通过,缺陷重开或回到处理中,并通知修复负责人;需求保持`测试中`。 - -### 测试完成 - -测试负责人输入: - -```text -测试完成:ONEOSP-123 -``` - -Codex执行:检查本需求用例包,不按整个迭代一刀切。只有没有未执行、阻塞、失败用例,且所有缺陷都经过测试复测关闭,才完成测试任务并推进需求到`测试完成`。 - -## 6. 发布和验收 - -### 准备发布 - -运维或项目负责人输入: - -```text -准备发布:迭代V1.3;需求=ONEOSP-123,ONEOSP-124;窗口=今晚22:00 -``` - -Codex执行:检查本批需求均测试完成,查重创建或复用唯一`【发版】`任务,正式关联迭代和需求,生成发布范围及回滚清单。 - -### 开始发布 - -运维输入: - -```text -开始发布:发版任务=TASK-900;prod流水线=oneos-prod;审批人=王经理 -``` - -Codex执行:核查审批、本批范围、生产流水线和回滚方案后进入`发布中`。这句话授权执行明确的本次生产发布,但不授权修改生产流水线定义。 - -### 发布成功 - -运维输入: - -```text -发布成功:执行ID=PIPE-789;发版任务=TASK-900 -``` - -Codex执行:到生产流水线核查环境、执行ID、范围和成功证据,确认后推进本批需求到`发布完成`并停留,等待验收。只说“成功”但没有生产证据时不改状态。 - -### 发布失败 - -运维输入: - -```text -发布失败:执行ID=PIPE-789;原因=镜像拉取失败 -``` - -Codex执行:核查失败证据,记录原因并进入项目真实`发布失败`状态;项目尚无该状态时记录阻塞并给出人工处置,不创建近义重复状态。 - -### 验收通过 - -产品或业务输入: - -```text -验收通过:ONEOSP-123;验收人=王经理;证据=https://... -``` - -Codex执行:核查生产发布完成和验收证据后,把需求改为`已关闭`。发布成功不能代替本句验收。 - -### 验收不通过 - -产品或业务输入: - -```text -验收不通过:ONEOSP-123;问题=导出字段缺失 -``` - -Codex执行:创建或关联可追踪问题,判断应回到开发、测试还是重新发布,先给出处理建议;不关闭需求,不覆盖原发布记录。 - -## 7. 随时可用的查询口令 - -```text -查状态:ONEOSP-123 -下一步:ONEOSP-123 -为什么没流转:ONEOSP-123 -查重复任务:ONEOSP-123 -查关联代码:ONEOSP-123 -查测试闭环:ONEOSP-123 -给我回滚方案:刚才那条规则 -``` - -这些口令默认只查询或出方案,不修改云效。 - -## 8. Codex的简短回报格式 - -处理成功: - -```text -已处理:ONEOSP-123 -状态:已确认 → 分析中 -任务:已创建并关联分析任务 TASK-456 -下一步:分析人员输入“分析完成:ONEOSP-123;说明=链接” -``` - -条件不足: - -```text -暂未处理:ONEOSP-123 -缺少:开发负责人 -请补充:安排开发:ONEOSP-123;只改后端=负责人/仓库 -``` - -自动化未触发: - -```text -未触发:ONEOSP-123 仍为待开发 -原因:分支只写了需求编号,没有在云效正式关联开发任务 -下一步:我可以补正式关联,然后再核查一次 -``` - -## 9. Codex执行口令的规则 - -1. `记录、确认、开始、完成、提交、修复、复测、准备发布、发布、验收`属于当前项目和明确工作项的执行授权。 -2. `查、看看、为什么、下一步、给方案`只允许读取或规划。 -3. 写操作前必须确认当前项目;项目不清楚只问项目名。 -4. 其他必要字段缺失时只追问最少信息,不发送长表单。 -5. 先查重、再创建;任务幂等键使用项目ID、需求ID和阶段类型。 -6. 项目使用`stage_tasks`还是`oneos_delivery`由Codex识别,同事不需要记规则编号。 -7. 当前平台或桥接不支持的动作要明确说“需要人工一步”,不能伪装成已自动化。 -8. 自动规则执行后等待5–30秒,再回看状态、正式关联和执行日志。 -9. 不用手工改最终状态冒充自动化通过。 -10. 不修改生产流水线定义,不删除规则、分支、MR、任务或测试资产,除非用户单独明确授权。 diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/references/task-lifecycle.md b/.cursor/skills/yunxiao-requirement-lifecycle/references/task-lifecycle.md deleted file mode 100644 index 7432685..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/references/task-lifecycle.md +++ /dev/null @@ -1,87 +0,0 @@ -# 阶段任务生命周期与自动创建 - -## 目录 - -1. 目标链路 -2. TK01–TK08规则 -3. 关系与代码资产 -4. 原生规则和桥接边界 -5. 验证与回滚 - -## 1. 目标链路 - -```text -需求设计中 + 设计任务处理中 -→ 设计任务已完成 -→ 需求设计完成 -→ 创建并关联开发任务 -→ 需求待开发 + 开发任务待处理 -→ 正式关联需求分支 -→ 需求开发中 + 开发任务处理中 -→ 全部相关MR合并到真实dev/develop -→ 需求开发完成 + 开发任务已完成 -→ test流水线部署成功 -→ 创建并关联测试任务 -→ 需求测试中 + 测试任务处理中 -→ 用例全部通过且缺陷复测关闭 -→ 需求测试完成 + 测试任务已完成 -``` - -分支只能合并到集成分支,不能“合并到测试环境”。test环境由流水线部署;部署成功用于允许测试开始,不用于证明生产发布。 - -## 2. TK01–TK08规则 - -| ID | 触发 | 必要条件 | 动作 | 推荐实现 | -|---|---|---|---|---| -| TK01 | 需求进入设计中 | 已正式关联设计任务;任务标签=`设计` | 设计任务保持待处理,负责人真实开工后人工改为处理中 | 默认+人工 | -| TK02 | 设计任务变为已完成 | 需求当前状态=设计中;本阶段设计任务非零且全部完成 | 需求改为设计完成 | 原生规则 | -| TK03 | 需求变为设计完成 | 不存在同需求、同阶段、未取消的开发任务 | 创建并正式关联开发任务,标签=`开发`,状态=待处理 | API/Webhook桥接 | -| TK04 | 开发任务创建并关联成功 | 需求当前状态=设计完成;开发任务关系可见 | 需求改为待开发 | 原生规则 | -| TK05 | 正式关联需求分支/代码资产 | 需求=待开发;开发任务=待处理;资产同时关联需求和开发任务 | 需求改为开发中;开发任务改为处理中 | 原生规则 | -| TK06 | 关联合并请求状态变化 | 需求=开发中;开发任务=处理中;全部相关前后端MR已合并到真实集成分支 | 需求改为开发完成;开发任务改为已完成 | 原生规则 | -| TK07 | test流水线部署成功 | 需求=开发完成;执行ID未处理;不存在同需求、同阶段、未取消的测试任务 | 创建并关联测试任务,关联测试计划/用例,任务改为处理中,需求改为测试中 | API/Webhook桥接 | -| TK08 | 测试验收完成 | 用例全部通过;失败用例已复测;范围内缺陷均由测试关闭 | 测试任务改为已完成;需求改为测试完成 | 桥接/人工门禁 | - -## 3. 关系与代码资产 - -- 设计、开发、测试任务必须使用云效正式父子项或关联项关系,不依赖标题关键字。 -- 开发分支和MR必须同时关联产品需求与对应开发任务;只关联需求不能可靠更新开发任务。 -- 一个需求涉及多个仓库时,全部相关前端、后端MR都纳入TK06门禁。 -- 首次提交可能晚于真实开工。任务`处理中`的权威时间是负责人执行“开始处理”;分支关联仅作自动兜底。 - -## 4. 原生规则和桥接边界 - -优先使用云效原生规则处理状态变化、父子项、关联项和Codeup对象。若当前规则编辑器没有“创建工作项”动作,TK03和TK07必须标记为`integration_bridge`或`not_supported`,不得声称已经自动创建。 - -桥接创建任务时使用幂等键: - -```text -项目ID + 需求ID + 阶段类型 -``` - -重复回调先查询是否存在未取消的同阶段任务;存在则复用并校验正式关系,不重复创建。流水线回调还要校验签名、时间戳、执行ID、防重放和失败重试。 - -## 5. 验证与回滚 - -- 正向验证每个TK控制,并检查需求和任务两个对象的状态。 -- 负向验证零任务、错误标签、错误当前状态、未正式关联分支、只合并部分MR、流水线失败和重复回调。 -- 新规则不会补触发历史事件。历史任务仅在核对MR/流水线证据后人工修正,并标记为测试准备或数据修复。 -- 回滚时先禁用新增规则或桥接入口,不删除已有任务和关系;恢复前一状态需要单独业务确认。 - -## 6. 进阶段自动建同名任务(与 OneOS / 口令共用) - -完整规则见 [auto-stage-task.md](auto-stage-task.md)。摘要: - -| 需求状态 | 同名任务标签 | 负责人 | -|---|---|---| -| 分析中 | `分析` | 需求创建人 | -| 设计中 | `设计` | 需求创建人 | -| 待开发 | `交付` 或 `开发` | 何斐 | - -必须正式关联需求;计划开始/创建时间沿用需求;同阶段幂等查重。 - -当创建或修复**分析 / 设计 / 交付(开发)**任务时: - -1. **任务标签(右侧基础字段)**:分析 → `分析`;设计 → `设计`;待开发主任务 → `交付`(或 `开发`)。Title 同名或前缀 alone 不够。 -2. **正式关联需求**:每条阶段任务必须链接到**产品需求**(父子或关联项)。只关联其他任务不算完成。 -3. 创建/修复后先核对标签与可见需求关系,再报告成功。 \ No newline at end of file diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/references/test-defect-closure.md b/.cursor/skills/yunxiao-requirement-lifecycle/references/test-defect-closure.md deleted file mode 100644 index dcf7b3c..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/references/test-defect-closure.md +++ /dev/null @@ -1,62 +0,0 @@ -# 测试用例与缺陷复测闭环 - -## 目录 - -1. 测试完成门槛 -2. TC01–TC03用例控制 -3. DF01–DF03缺陷控制 -4. 实现方式 -5. 验证场景 - -## 1. 测试完成门槛 - -R10只有同时满足以下条件才能完成: - -1. 范围内用例均已执行并有明确结果。 -2. 不存在`未执行/待测试`、`阻塞`或`未通过`用例;获批暂缓/不适用项记录批准人与原因。 -3. 每个未通过用例均正式创建或关联缺陷。 -4. 修复后重新执行原用例并通过。 -5. 范围内缺陷均由测试复测并进入项目真实关闭态。 -6. 测试计划或测试验收任务由测试负责人确认。 - -流水线成功只能证明部署/自动检查成功,不能替代用例结果和缺陷复测。 - -## 2. TC01–TC03用例控制 - -| ID | 控制 | 完成口径 | -|---|---|---| -| TC01 | 用例执行 | 每条范围内用例有真实执行结果;未执行、待测试、阻塞、未通过均阻塞测试完成 | -| TC02 | 失败用例关联缺陷 | 每条未通过用例正式创建或关联缺陷;仅写编号或链接不算关联 | -| TC03 | 用例复测更新 | 修复后重新执行原用例;通过才改为已通过,失败保持未通过 | - -## 3. DF01–DF03缺陷控制 - -| ID | 控制 | 完成口径 | -|---|---|---| -| DF01 | 已修复交接 | `已修复`只表示开发交给测试复测,不是终态,不解除测试门禁 | -| DF02 | 复测通过 | 缺陷进入项目真实关闭态,原失败用例同时更新为已通过 | -| DF03 | 复测失败 | 缺陷进入项目真实重开/处理中状态,原用例保持未通过 | - -不得关闭缺陷却不更新原用例,也不得在缺陷仅为`已修复`时把用例标为通过。 - -## 4. 实现方式 - -先检查当前规则编辑器是否真的暴露Testhub执行用例和缺陷聚合条件: - -- 支持时:使用原生聚合规则,并保存条件和执行日志证据。 -- 不支持时:使用测试验收任务/人工门禁。 -- 需要全自动时:单独评审Testhub OpenAPI/Webhook桥接,要求签名、幂等、失败重试和审计记录。 - -不能把`not_supported`写成“已自动化”;必须指定负责人和回退方案。 - -## 5. 验证场景 - -| 场景 | 操作 | 期望 | 负向检查 | -|---|---|---|---| -| 用例未执行 | 保留一条未执行用例 | 保持测试中 | 不得完成测试 | -| 用例失败 | 记录未通过并正式关联缺陷 | 保持测试中 | 仅建缺陷不得完成 | -| 缺陷已修复 | 开发改为已修复 | 等待测试复测 | 不得关闭或通过用例 | -| 复测通过 | 重跑原用例并通过 | 用例通过、缺陷关闭 | 两者状态必须一致 | -| 复测失败 | 重跑仍失败 | 用例未通过、缺陷重开 | 不得保持已修复/关闭 | -| 全部闭环 | 全部用例通过且缺陷关闭 | 允许R10推进 | 检查是否误触发后继规则 | - diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/scripts/oneos_profile_tool.py b/.cursor/skills/yunxiao-requirement-lifecycle/scripts/oneos_profile_tool.py deleted file mode 100644 index c1849b6..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/scripts/oneos_profile_tool.py +++ /dev/null @@ -1,314 +0,0 @@ -#!/usr/bin/env python3 -"""Validate and render a OneOS delivery-model Yunxiao project profile.""" - -from __future__ import annotations - -import argparse -import json -import sys -from pathlib import Path -from typing import Any - - -TARGET_LIFECYCLE = [ - "待处理", "已确认", "分析中", "分析完成", "设计中", "设计完成", - "待开发", "开发中", "开发完成", "待测试", "测试中", "测试完成", - "发布中", "发布完成", "发布失败", "已关闭", -] -REQUIRED_TASK_LABELS = {"交付", "开发", "测试", "发版"} -REQUIRED_CONTROL_IDS = { - *(f"Y{index:02d}" for index in range(1, 17)), - "Y20", "Y21", "Y22", - *(f"Y{index:02d}" for index in range(30, 36)), - "Y40", - "A01", "A02", "A02B", *(f"A{index:02d}" for index in range(3, 11)), - *(f"TC{index:02d}" for index in range(1, 4)), - *(f"DF{index:02d}" for index in range(1, 4)), - *(f"CI{index:02d}" for index in range(1, 4)), - "L01", "L02", -} -VALID_MODES = { - "manual", "native_rule", "integration_bridge", "disabled_legacy", "not_supported" -} -VALID_PHASES = {"P0", "P1", "P2", "P3"} -VALID_TASK_WORKFLOW_MODES = {"A", "B"} -VALID_TASK_IDENTITY_MODES = {"work_item_type", "task_label", "title_prefix_process_control"} -VALID_LEGACY_DISPOSITIONS = {"keep_until_ready", "disable", "replace", "already_disabled"} -SENSITIVE_FRAGMENTS = ( - "password", "passwd", "secret", "token", "cookie", "credential", - "private_key", "otp", "密码", "密钥", -) - - -def is_text(value: Any) -> bool: - return isinstance(value, str) and bool(value.strip()) - - -def require_text(container: dict[str, Any], key: str, label: str, errors: list[str]) -> None: - if not is_text(container.get(key)): - errors.append(f"{label}.{key} 不能为空") - - -def walk_sensitive_keys(value: Any, prefix: str = "") -> list[str]: - found: list[str] = [] - if isinstance(value, dict): - for key, nested in value.items(): - current = f"{prefix}.{key}" if prefix else str(key) - if any(fragment in str(key).lower() for fragment in SENSITIVE_FRAGMENTS): - found.append(current) - found.extend(walk_sensitive_keys(nested, current)) - elif isinstance(value, list): - for index, nested in enumerate(value): - found.extend(walk_sensitive_keys(nested, f"{prefix}[{index}]")) - return found - - -def validate_controls(controls: Any, label: str, errors: list[str]) -> None: - if not isinstance(controls, list): - errors.append(f"{label} 必须是数组") - return - seen: set[str] = set() - for index, control in enumerate(controls): - item_label = f"{label}[{index}]" - if not isinstance(control, dict): - errors.append(f"{item_label} 必须是对象") - continue - control_id = control.get("id") - if not is_text(control_id): - errors.append(f"{item_label}.id 不能为空") - continue - if control_id in seen: - errors.append(f"{label} 控制项重复:{control_id}") - seen.add(control_id) - if control_id not in REQUIRED_CONTROL_IDS: - errors.append(f"{label} 未知控制项:{control_id}") - mode = control.get("mode") - if mode not in VALID_MODES: - errors.append(f"{item_label}.mode 不合法") - if not isinstance(control.get("enabled"), bool): - errors.append(f"{item_label}.enabled 必须是布尔值") - if mode in {"disabled_legacy", "not_supported"} and control.get("enabled") is not False: - errors.append(f"{item_label} 的 {mode} 模式要求 enabled=false") - if control_id in {"L01", "L02"}: - if mode != "disabled_legacy" or control.get("enabled") is not False: - errors.append(f"{item_label} 必须停用,防止发布完成绕过验收") - for key in ("owner", "fallback", "evidence"): - require_text(control, key, item_label, errors) - missing = sorted(REQUIRED_CONTROL_IDS - seen) - if missing: - errors.append(f"{label} 缺少控制项:" + "、".join(missing)) - - -def validate_project(project: Any, index: int, errors: list[str]) -> None: - label = f"projects[{index}]" - if not isinstance(project, dict): - errors.append(f"{label} 必须是对象") - return - require_text(project, "name", label, errors) - if project.get("migration_phase") not in VALID_PHASES: - errors.append(f"{label}.migration_phase 必须是 P0/P1/P2/P3") - statuses = project.get("requirement_statuses_observed") - if not isinstance(statuses, list) or not statuses or not all(is_text(v) for v in statuses): - errors.append(f"{label}.requirement_statuses_observed 必须是非空文本数组") - if project.get("task_workflow_mode") not in VALID_TASK_WORKFLOW_MODES: - errors.append(f"{label}.task_workflow_mode 必须是 A 或 B") - if project.get("task_identity_mode") not in VALID_TASK_IDENTITY_MODES: - errors.append(f"{label}.task_identity_mode 不合法") - if not isinstance(project.get("native_rule_can_filter_related_task_identity"), bool): - errors.append(f"{label}.native_rule_can_filter_related_task_identity 必须是布尔值") - for key in ( - "main_task_prefix", "development_task_prefix", "test_task_prefix", "release_task_prefix" - ): - require_text(project, key, label, errors) - - repositories = project.get("repositories") - if not isinstance(repositories, list) or not repositories: - errors.append(f"{label}.repositories 至少包含一个仓库") - else: - for repo_index, repo in enumerate(repositories): - repo_label = f"{label}.repositories[{repo_index}]" - if not isinstance(repo, dict): - errors.append(f"{repo_label} 必须是对象") - continue - for key in ("name", "role", "integration_branch", "evidence"): - require_text(repo, key, repo_label, errors) - if repo.get("integration_branch") not in {"dev", "develop"}: - errors.append(f"{repo_label}.integration_branch 只能是实际存在的 dev/develop") - for key in ("codeup_integrated", "required_for_mr_gate"): - if not isinstance(repo.get(key), bool): - errors.append(f"{repo_label}.{key} 必须是布尔值") - - test_plan = project.get("test_plan") - if not isinstance(test_plan, dict): - errors.append(f"{label}.test_plan 必须是对象") - else: - require_text(test_plan, "name", f"{label}.test_plan", errors) - require_text(test_plan, "evidence", f"{label}.test_plan", errors) - for key, required in ( - ("iteration_formally_related", True), - ("cases_partitioned_by_requirement", True), - ("fixed_defect_is_terminal", False), - ("tester_retest_required", True), - ): - if test_plan.get(key) is not required: - errors.append(f"{label}.test_plan.{key} 必须为 {str(required).lower()}") - - release = project.get("release") - if not isinstance(release, dict): - errors.append(f"{label}.release 必须是对象") - else: - require_text(release, "evidence", f"{label}.release", errors) - for key in ( - "release_task_formally_related_to_iteration", - "production_evidence_separate_from_test", - "acceptance_required_after_release", - "production_pipeline_unchanged", - ): - if release.get(key) is not True: - errors.append(f"{label}.release.{key} 必须为 true") - - legacy_rules = project.get("legacy_rules") - if not isinstance(legacy_rules, list): - errors.append(f"{label}.legacy_rules 必须是数组") - else: - for legacy_index, rule in enumerate(legacy_rules): - item_label = f"{label}.legacy_rules[{legacy_index}]" - if not isinstance(rule, dict): - errors.append(f"{item_label} 必须是对象") - continue - for key in ("name", "reason", "evidence"): - require_text(rule, key, item_label, errors) - if rule.get("disposition") not in VALID_LEGACY_DISPOSITIONS: - errors.append(f"{item_label}.disposition 不合法") - if not isinstance(rule.get("enabled"), bool): - errors.append(f"{item_label}.enabled 必须是布尔值") - - validate_controls(project.get("controls"), f"{label}.controls", errors) - - -def validate(data: dict[str, Any]) -> list[str]: - errors: list[str] = [] - if data.get("schema_version") != 1: - errors.append("schema_version 必须为 1") - if data.get("flow_model") != "oneos_delivery": - errors.append("flow_model 必须为 oneos_delivery") - for key in ("organization", "work_item_type"): - require_text(data, key, "profile", errors) - if data.get("target_lifecycle") != TARGET_LIFECYCLE: - errors.append("target_lifecycle 必须与OneOS终态16状态顺序一致") - labels = data.get("task_labels") - if not isinstance(labels, list) or not REQUIRED_TASK_LABELS.issubset(set(labels)): - errors.append("task_labels 必须包含:交付、开发、测试、发版") - policies = data.get("policies") - if not isinstance(policies, dict): - errors.append("policies 必须是对象") - else: - true_keys = ( - "require_formal_relations", "require_current_state_condition", - "require_nonzero_development_tasks", - "require_signed_timestamped_replay_protected_callbacks", - ) - false_keys = ("allow_automatic_final_close_without_acceptance", "test_success_is_production_release") - for key in true_keys: - if policies.get(key) is not True: - errors.append(f"policies.{key} 必须为 true") - for key in false_keys: - if policies.get(key) is not False: - errors.append(f"policies.{key} 必须为 false") - parts = policies.get("bridge_idempotency_key_components") - if not isinstance(parts, list) or not parts or not all(is_text(v) for v in parts): - errors.append("policies.bridge_idempotency_key_components 不能为空") - projects = data.get("projects") - if not isinstance(projects, list) or not projects: - errors.append("projects 至少包含一个项目") - else: - for index, project in enumerate(projects): - validate_project(project, index, errors) - sensitive = walk_sensitive_keys(data) - if sensitive: - errors.append("配置中禁止出现敏感字段:" + "、".join(sensitive)) - if "请填写" in json.dumps(data, ensure_ascii=False): - errors.append("仍存在“请填写”占位符") - return errors - - -def render(data: dict[str, Any]) -> str: - lines = [ - "# OneOS云效终态交付模型执行计划", "", - f"- 组织:{data['organization']}", - f"- 工作项类型:{data['work_item_type']}", - f"- 项目数:{len(data['projects'])}", - "- 完整性口径:每项目48项Y/A/测试/缺陷/集成/旧规则控制。", - "- 安全边界:不修改生产流水线;发布完成后保留产品验收门禁。", "", - ] - for project in data["projects"]: - missing_statuses = [s for s in TARGET_LIFECYCLE if s not in project["requirement_statuses_observed"]] - lines.extend([ - f"## 项目:{project['name']}", "", - f"- 迁移阶段:{project['migration_phase']}", - f"- 任务工作流方案:{project['task_workflow_mode']}", - f"- 任务身份方式:{project['task_identity_mode']}", - f"- 原生规则可过滤关联任务身份:{'是' if project['native_rule_can_filter_related_task_identity'] else '否'}", - f"- 缺少目标状态:{'、'.join(missing_statuses) if missing_statuses else '无'}", "", - "### 旧规则处置", "", - "| 规则 | 处置 | 启用 | 原因 | 证据 |", "|---|---|---|---|---|", - ]) - for rule in project["legacy_rules"]: - lines.append( - f"| {rule['name']} | {rule['disposition']} | {'是' if rule['enabled'] else '否'} | " - f"{rule['reason']} | {rule['evidence']} |" - ) - lines.extend([ - "", "### 48项控制台账", "", - "| ID | 方式 | 启用 | 负责人 | 回退方案 | 证据 |", "|---|---|---|---|---|---|", - ]) - for control in sorted(project["controls"], key=lambda item: item["id"]): - lines.append( - f"| {control['id']} | {control['mode']} | {'是' if control['enabled'] else '否'} | " - f"{control['owner']} | {control['fallback']} | {control['evidence']} |" - ) - lines.extend([ - "", "### 迁移门禁", "", - "- [ ] 目标状态与流转边已补齐。", - "- [ ] 主任务可可靠识别或已明确交桥接。", - "- [ ] 多仓库需求与开发任务均正式关联分支/MR。", - "- [ ] Testhub按需求分包且缺陷复测闭环。", - "- [ ] test、生产发布和产品验收证据分离。", - "- [ ] L01、L02已停用,旧规则已有备份和回滚。", "", - ]) - return "\n".join(lines) - - -def main() -> int: - parser = argparse.ArgumentParser(description=__doc__) - subparsers = parser.add_subparsers(dest="command", required=True) - validate_parser = subparsers.add_parser("validate") - validate_parser.add_argument("profile", type=Path) - render_parser = subparsers.add_parser("render") - render_parser.add_argument("profile", type=Path) - render_parser.add_argument("--output", required=True, type=Path) - args = parser.parse_args() - try: - with args.profile.open("r", encoding="utf-8-sig") as handle: - data = json.load(handle) - if not isinstance(data, dict): - raise ValueError("配置根节点必须是JSON对象") - errors = validate(data) - except (OSError, json.JSONDecodeError, ValueError) as error: - print(f"配置读取失败:{error}", file=sys.stderr) - return 2 - if errors: - for error in errors: - print(f"[错误] {error}", file=sys.stderr) - return 1 - if args.command == "validate": - print(f"[通过] OneOS配置有效:{len(data['projects'])}个项目,每项目48项控制。") - return 0 - args.output.parent.mkdir(parents=True, exist_ok=True) - args.output.write_text(render(data), encoding="utf-8") - print(f"[完成] 已生成执行计划:{args.output}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/.cursor/skills/yunxiao-requirement-lifecycle/scripts/profile_tool.py b/.cursor/skills/yunxiao-requirement-lifecycle/scripts/profile_tool.py deleted file mode 100644 index 5738bf3..0000000 --- a/.cursor/skills/yunxiao-requirement-lifecycle/scripts/profile_tool.py +++ /dev/null @@ -1,536 +0,0 @@ -#!/usr/bin/env python3 -"""Validate a project-scoped Yunxiao lifecycle profile and render an execution plan.""" - -from __future__ import annotations - -import argparse -import json -import sys -from pathlib import Path -from typing import Any - - -REQUIRED_LABELS = {"分析", "设计", "开发", "测试"} -REQUIRED_RULE_IDS = {f"R{index:02d}" for index in range(1, 13)} -REQUIRED_TASK_RULE_IDS = {f"TK{index:02d}" for index in range(1, 9)} -REQUIRED_CONTROL_IDS = { - *REQUIRED_RULE_IDS, - *REQUIRED_TASK_RULE_IDS, - *(f"TC{index:02d}" for index in range(1, 4)), - *(f"DF{index:02d}" for index in range(1, 4)), - *(f"CI{index:02d}" for index in range(1, 4)), - "L01", - "L02", -} -VALID_MODES = { - "manual", - "native_rule", - "integration_bridge", - "disabled_legacy", - "not_supported", -} -VALID_RELEASE_EVIDENCE_MODES = { - "native_release_change", - "pipeline_callback", - "manual_release_confirmation", -} -SENSITIVE_FRAGMENTS = ( - "password", - "passwd", - "secret", - "token", - "cookie", - "credential", - "private_key", - "otp", - "密码", - "密钥", -) - - -def load_profile(path: Path) -> dict[str, Any]: - with path.open("r", encoding="utf-8-sig") as handle: - data = json.load(handle) - if not isinstance(data, dict): - raise ValueError("配置根节点必须是 JSON 对象") - return data - - -def is_text(value: Any) -> bool: - return isinstance(value, str) and bool(value.strip()) - - -def require_text(container: dict[str, Any], key: str, label: str, errors: list[str]) -> None: - if not is_text(container.get(key)): - errors.append(f"{label}.{key} 不能为空") - - -def walk_sensitive_keys(value: Any, prefix: str = "") -> list[str]: - found: list[str] = [] - if isinstance(value, dict): - for key, nested in value.items(): - current = f"{prefix}.{key}" if prefix else str(key) - lower = str(key).lower() - if any(fragment in lower for fragment in SENSITIVE_FRAGMENTS): - found.append(current) - found.extend(walk_sensitive_keys(nested, current)) - elif isinstance(value, list): - for index, nested in enumerate(value): - found.extend(walk_sensitive_keys(nested, f"{prefix}[{index}]")) - return found - - -def validate_control_list( - controls: Any, label: str, errors: list[str] -) -> None: - if not isinstance(controls, list): - errors.append(f"{label} 必须是数组") - return - seen: set[str] = set() - for index, control in enumerate(controls): - item_label = f"{label}[{index}]" - if not isinstance(control, dict): - errors.append(f"{item_label} 必须是对象") - continue - control_id = control.get("id") - if not is_text(control_id): - errors.append(f"{item_label}.id 不能为空") - continue - if control_id in seen: - errors.append(f"{label} 控制项重复:{control_id}") - seen.add(control_id) - if control_id not in REQUIRED_CONTROL_IDS: - errors.append(f"{label} 未知控制项:{control_id}") - mode = control.get("mode") - if mode not in VALID_MODES: - errors.append(f"{item_label}.mode 不合法") - if not isinstance(control.get("enabled"), bool): - errors.append(f"{item_label}.enabled 必须是布尔值") - for key in ("evidence", "owner", "fallback"): - require_text(control, key, item_label, errors) - if mode in {"disabled_legacy", "not_supported"} and control.get("enabled") is not False: - errors.append(f"{item_label} 的 {mode} 模式要求 enabled=false") - if control_id in {"L01", "L02"} and mode not in { - "disabled_legacy", - "native_rule", - "manual", - }: - errors.append(f"{item_label} 必须明确为停用旧规则、获批原生规则或人工处置") - missing = sorted(REQUIRED_CONTROL_IDS - seen) - if missing: - errors.append(f"{label} 缺少控制项:" + "、".join(missing)) - - -def validate_rule_instances( - rules: Any, lifecycle: list[str], label: str, errors: list[str] -) -> None: - if not isinstance(rules, list): - errors.append(f"{label} 必须是数组") - return - seen: set[str] = set() - orders: set[int] = set() - expected = { - f"R{index + 1:02d}": (lifecycle[index], lifecycle[index + 1]) - for index in range(12) - } - for index, rule in enumerate(rules): - item_label = f"{label}[{index}]" - if not isinstance(rule, dict): - errors.append(f"{item_label} 必须是对象") - continue - rule_id = rule.get("id") - if not is_text(rule_id): - errors.append(f"{item_label}.id 不能为空") - continue - if rule_id in seen: - errors.append(f"{label} 规则实例重复:{rule_id}") - seen.add(rule_id) - if rule_id not in REQUIRED_RULE_IDS: - errors.append(f"{label} 未知规则实例:{rule_id}") - mode = rule.get("mode") - if mode not in {"manual", "native_rule", "integration_bridge", "not_supported"}: - errors.append(f"{item_label}.mode 不合法") - for key in ("actual_rule_name", "trigger", "source_status", "target_status", "action", "evidence"): - require_text(rule, key, item_label, errors) - conditions = rule.get("conditions") - if not isinstance(conditions, list) or not conditions or not all(is_text(v) for v in conditions): - errors.append(f"{item_label}.conditions 必须是非空文本数组") - order = rule.get("order") - if not isinstance(order, int) or order < 1: - errors.append(f"{item_label}.order 必须是正整数") - elif order in orders: - errors.append(f"{label} 规则顺序重复:{order}") - else: - orders.add(order) - if not isinstance(rule.get("enabled"), bool): - errors.append(f"{item_label}.enabled 必须是布尔值") - if rule.get("has_current_state_condition") is not True: - errors.append(f"{item_label}.has_current_state_condition 必须为 true") - if rule_id in expected: - expected_source, expected_target = expected[rule_id] - if rule.get("source_status") != expected_source: - errors.append(f"{item_label}.source_status 应为 {expected_source}") - if rule.get("target_status") != expected_target: - errors.append(f"{item_label}.target_status 应为 {expected_target}") - missing = sorted(REQUIRED_RULE_IDS - seen) - if missing: - errors.append(f"{label} 缺少规则实例:" + "、".join(missing)) - - -def validate_task_rule_instances(rules: Any, label: str, errors: list[str]) -> None: - if not isinstance(rules, list): - errors.append(f"{label} 必须是数组") - return - seen: set[str] = set() - orders: set[int] = set() - for index, rule in enumerate(rules): - item_label = f"{label}[{index}]" - if not isinstance(rule, dict): - errors.append(f"{item_label} 必须是对象") - continue - rule_id = rule.get("id") - if not is_text(rule_id): - errors.append(f"{item_label}.id 不能为空") - continue - if rule_id in seen: - errors.append(f"{label} 规则实例重复:{rule_id}") - seen.add(rule_id) - if rule_id not in REQUIRED_TASK_RULE_IDS: - errors.append(f"{label} 未知任务规则实例:{rule_id}") - if rule.get("mode") not in {"manual", "native_rule", "integration_bridge", "not_supported"}: - errors.append(f"{item_label}.mode 不合法") - for key in ("actual_rule_name", "trigger", "scope", "source_state", "evidence"): - require_text(rule, key, item_label, errors) - for key in ("conditions", "actions"): - values = rule.get(key) - if not isinstance(values, list) or not values or not all(is_text(value) for value in values): - errors.append(f"{item_label}.{key} 必须是非空文本数组") - order = rule.get("order") - if not isinstance(order, int) or order < 1: - errors.append(f"{item_label}.order 必须是正整数") - elif order in orders: - errors.append(f"{label} 规则顺序重复:{order}") - else: - orders.add(order) - if not isinstance(rule.get("enabled"), bool): - errors.append(f"{item_label}.enabled 必须是布尔值") - missing = sorted(REQUIRED_TASK_RULE_IDS - seen) - if missing: - errors.append(f"{label} 缺少任务规则实例:" + "、".join(missing)) - - -def validate_project( - project: Any, index: int, lifecycle: list[str], errors: list[str] -) -> None: - label = f"projects[{index}]" - if not isinstance(project, dict): - errors.append(f"{label} 必须是对象") - return - for key in ("name", "work_item_type", "release_status", "final_status"): - require_text(project, key, label, errors) - if project.get("release_status") != lifecycle[11]: - errors.append(f"{label}.release_status 必须与 lifecycle[11] 一致") - if project.get("final_status") != lifecycle[12]: - errors.append(f"{label}.final_status 必须与 lifecycle[12] 一致") - - labels = project.get("labels_observed") - if not isinstance(labels, list) or not REQUIRED_LABELS.issubset(set(labels)): - errors.append(f"{label}.labels_observed 必须包含:分析、设计、开发、测试") - - repositories = project.get("repositories") - if not isinstance(repositories, list) or not repositories: - errors.append(f"{label}.repositories 至少包含一个仓库") - else: - for repo_index, repo in enumerate(repositories): - repo_label = f"{label}.repositories[{repo_index}]" - if not isinstance(repo, dict): - errors.append(f"{repo_label} 必须是对象") - continue - for key in ("name", "role", "integration_branch", "evidence"): - require_text(repo, key, repo_label, errors) - if repo.get("integration_branch") not in {"dev", "develop"}: - errors.append(f"{repo_label}.integration_branch 只能是实际存在的 dev/develop") - for key in ("codeup_integrated", "required_for_mr_gate"): - if not isinstance(repo.get(key), bool): - errors.append(f"{repo_label}.{key} 必须是布尔值") - - pipeline = project.get("test_pipeline") - if not isinstance(pipeline, dict): - errors.append(f"{label}.test_pipeline 必须是对象") - else: - for key in ("name", "environment", "evidence"): - require_text(pipeline, key, f"{label}.test_pipeline", errors) - for key in ( - "is_clone_for_callback_poc", - "original_pipeline_unchanged", - "docker_success_callback_enabled", - ): - if not isinstance(pipeline.get(key), bool): - errors.append(f"{label}.test_pipeline.{key} 必须是布尔值") - security = pipeline.get("callback_security") - if not isinstance(security, dict): - errors.append(f"{label}.test_pipeline.callback_security 必须是对象") - else: - security_keys = ( - "signed", - "timestamp_checked", - "replay_protected", - "idempotent_by_execution_id", - "failure_retry_tested", - ) - for key in security_keys: - if not isinstance(security.get(key), bool): - errors.append(f"{label}.test_pipeline.callback_security.{key} 必须是布尔值") - if pipeline.get("docker_success_callback_enabled") is True: - if pipeline.get("is_clone_for_callback_poc") is not True: - errors.append(f"{label} 启用回调时必须使用test流水线副本") - if pipeline.get("original_pipeline_unchanged") is not True: - errors.append(f"{label} 启用回调时必须保持原流水线不变") - if any(security.get(key) is not True for key in security_keys): - errors.append(f"{label} 启用回调时必须完成全部安全与重试验证") - - release = project.get("release_evidence") - if not isinstance(release, dict): - errors.append(f"{label}.release_evidence 必须是对象") - else: - if release.get("mode") not in VALID_RELEASE_EVIDENCE_MODES: - errors.append(f"{label}.release_evidence.mode 不合法") - for key in ("target_environment", "evidence"): - require_text(release, key, f"{label}.release_evidence", errors) - if release.get("test_success_is_production_release") is not False: - errors.append(f"{label}.release_evidence.test_success_is_production_release 必须为 false") - - test_plan = project.get("test_plan") - if not isinstance(test_plan, dict): - errors.append(f"{label}.test_plan 必须是对象") - else: - for key in ("name", "evidence"): - require_text(test_plan, key, f"{label}.test_plan", errors) - if not isinstance(test_plan.get("requirement_formally_related"), bool): - errors.append(f"{label}.test_plan.requirement_formally_related 必须是布尔值") - states = test_plan.get("case_states_observed") - if not isinstance(states, list) or not states or not all(is_text(v) for v in states): - errors.append(f"{label}.test_plan.case_states_observed 必须是非空文本数组") - - defect = project.get("defect_workflow") - if not isinstance(defect, dict): - errors.append(f"{label}.defect_workflow 必须是对象") - else: - for key in ("fixed_status", "closed_status", "reopen_status", "evidence"): - require_text(defect, key, f"{label}.defect_workflow", errors) - if defect.get("fixed_is_terminal") is not False: - errors.append(f"{label}.defect_workflow.fixed_is_terminal 必须为 false") - if defect.get("tester_retest_required") is not True: - errors.append(f"{label}.defect_workflow.tester_retest_required 必须为 true") - - validate_rule_instances(project.get("rule_instances"), lifecycle, f"{label}.rule_instances", errors) - validate_task_rule_instances( - project.get("task_rule_instances"), f"{label}.task_rule_instances", errors - ) - validate_control_list(project.get("control_coverage"), f"{label}.control_coverage", errors) - if not isinstance(project.get("adjunct_automations"), list): - errors.append(f"{label}.adjunct_automations 必须是数组,可为空") - - -def validate(data: dict[str, Any]) -> list[str]: - errors: list[str] = [] - if data.get("schema_version") != 3: - errors.append("schema_version 必须为 3;旧版台账不包含阶段任务闭环") - for key in ("organization", "work_item_type"): - require_text(data, key, "profile", errors) - - lifecycle = data.get("lifecycle") - if not isinstance(lifecycle, list) or len(lifecycle) != 13 or not all(is_text(v) for v in lifecycle): - errors.append("lifecycle 必须包含 13 个非空有序状态") - lifecycle = [str(index) for index in range(13)] - elif len(set(lifecycle)) != len(lifecycle): - errors.append("lifecycle 状态名称不能重复") - - labels = data.get("requirement_labels") - if not isinstance(labels, list) or not REQUIRED_LABELS.issubset(set(labels)): - errors.append("requirement_labels 必须包含:分析、设计、开发、测试") - - policy = data.get("automation_policy") - if not isinstance(policy, dict): - errors.append("automation_policy 必须是对象") - else: - for key in ("require_current_state_condition", "inspect_rule_order_and_cascades"): - if policy.get(key) is not True: - errors.append(f"automation_policy.{key} 必须为 true") - for key in ("allow_automatic_final_close_without_acceptance", "zero_related_items_are_complete"): - if policy.get(key) is not False: - errors.append(f"automation_policy.{key} 必须为 false") - events = policy.get("development_start_event_candidates") - if not isinstance(events, list) or not events or not all(is_text(v) for v in events): - errors.append("automation_policy.development_start_event_candidates 不能为空") - if policy.get("require_code_assets_linked_to_requirement_and_development_task") is not True: - errors.append( - "automation_policy.require_code_assets_linked_to_requirement_and_development_task 必须为 true" - ) - idempotency = policy.get("stage_task_creation_idempotency_key_components") - if not isinstance(idempotency, list) or not idempotency or not all(is_text(v) for v in idempotency): - errors.append("automation_policy.stage_task_creation_idempotency_key_components 不能为空") - - test_policy = data.get("test_closure_policy") - if not isinstance(test_policy, dict): - errors.append("test_closure_policy 必须是对象") - else: - if test_policy.get("defect_fixed_is_terminal") is not False: - errors.append("test_closure_policy.defect_fixed_is_terminal 必须为 false") - if test_policy.get("require_tester_retest") is not True: - errors.append("test_closure_policy.require_tester_retest 必须为 true") - for key in ("accepted_case_states", "rejected_case_states"): - values = test_policy.get(key) - if not isinstance(values, list) or not values or not all(is_text(v) for v in values): - errors.append(f"test_closure_policy.{key} 不能为空") - - projects = data.get("projects") - if not isinstance(projects, list) or not projects: - errors.append("projects 至少包含一个项目") - else: - names: set[str] = set() - for index, project in enumerate(projects): - validate_project(project, index, lifecycle, errors) - if isinstance(project, dict) and is_text(project.get("name")): - name = project["name"] - if name in names: - errors.append(f"项目名称重复:{name}") - names.add(name) - - sensitive = walk_sensitive_keys(data) - if sensitive: - errors.append("配置中禁止出现敏感字段:" + "、".join(sensitive)) - if "请填写" in json.dumps(data, ensure_ascii=False): - errors.append("仍存在“请填写”占位符") - return errors - - -def render(data: dict[str, Any]) -> str: - lines = [ - "# 云效需求生命周期自动化执行计划", - "", - f"- 组织:{data['organization']}", - f"- 工作项类型:{data['work_item_type']}", - f"- 项目数:{len(data['projects'])}", - "- 完整性口径:每项目12个需求规则实例、8个任务规则实例、31个闭环控制项。", - "- 安全边界:不修改现有生产流水线;不记录任何凭据或密钥。", - "", - ] - for project in data["projects"]: - lines.extend([ - f"## 项目:{project['name']}", - "", - f"- 发布状态:{project['release_status']}", - f"- 最终状态:{project['final_status']}", - f"- test流水线:{project['test_pipeline']['name']}", - f"- 发布证据方式:{project['release_evidence']['mode']}", - "", - "### 仓库覆盖", - "", - "| 仓库 | 角色 | 集成分支 | Codeup已集成 | 纳入全部MR门禁 | 证据 |", - "|---|---|---|---|---|---|", - ]) - for repo in project["repositories"]: - lines.append( - f"| {repo['name']} | {repo['role']} | {repo['integration_branch']} | " - f"{'是' if repo['codeup_integrated'] else '否'} | " - f"{'是' if repo['required_for_mr_gate'] else '否'} | {repo['evidence']} |" - ) - lines.extend([ - "", - "### R01–R12实际规则实例", - "", - "| ID | 实现 | 实际规则/门禁 | 触发 | 流转 | 条件 | 顺序 | 启用 | 证据 |", - "|---|---|---|---|---|---|---:|---|---|", - ]) - for rule in sorted(project["rule_instances"], key=lambda item: item["order"]): - conditions = ";".join(rule["conditions"]) - lines.append( - f"| {rule['id']} | {rule['mode']} | {rule['actual_rule_name']} | {rule['trigger']} | " - f"{rule['source_status']} → {rule['target_status']} | {conditions} | {rule['order']} | " - f"{'是' if rule['enabled'] else '否'} | {rule['evidence']} |" - ) - lines.extend([ - "", - "### TK01–TK08任务规则实例", - "", - "| ID | 实现 | 实际规则/门禁 | 触发 | 作用域 | 源状态 | 条件 | 动作 | 顺序 | 启用 | 证据 |", - "|---|---|---|---|---|---|---|---|---:|---|---|", - ]) - for rule in sorted(project["task_rule_instances"], key=lambda item: item["order"]): - conditions = ";".join(rule["conditions"]) - actions = ";".join(rule["actions"]) - lines.append( - f"| {rule['id']} | {rule['mode']} | {rule['actual_rule_name']} | {rule['trigger']} | " - f"{rule['scope']} | {rule['source_state']} | {conditions} | {actions} | {rule['order']} | " - f"{'是' if rule['enabled'] else '否'} | {rule['evidence']} |" - ) - lines.extend([ - "", - "### 31项完整性台账", - "", - "| 控制ID | 实现方式 | 启用 | 观察证据 | 负责人 | 回退方案 |", - "|---|---|---|---|---|---|", - ]) - for control in sorted(project["control_coverage"], key=lambda item: item["id"]): - lines.append( - f"| {control['id']} | {control['mode']} | {'是' if control['enabled'] else '否'} | " - f"{control['evidence']} | {control['owner']} | {control['fallback']} |" - ) - adjunct = project["adjunct_automations"] - lines.extend([ - "", - "### 范围外通用自动化盘点", - "", - "- " + (";".join(map(str, adjunct)) if adjunct else "未纳入;如需通知、自动指派、SLA、字段同步等,应单独授权和设计。"), - "", - ]) - lines.extend([ - "## 执行检查", - "", - "- [ ] 两个或多个项目的规则实例、控制台账和证据已分别填写。", - "- [ ] 已逐条读取真实触发配置、规则顺序、启用状态和执行账号。", - "- [ ] 已检查后继规则、重复规则和跨阶段连锁流转。", - "- [ ] 已验证失败用例、缺陷已修复、复测通过/失败时双方状态一致。", - "- [ ] 已逐仓库确认真实dev/develop分支、正式关联和全部MR门禁。", - "- [ ] 已区分test部署、生产发布、发布变更和业务验收。", - "- [ ] 已执行正向、负向、异步、多仓库、失败重试和旧规则连锁测试。", - "", - ]) - return "\n".join(lines) - - -def main() -> int: - parser = argparse.ArgumentParser(description=__doc__) - subparsers = parser.add_subparsers(dest="command", required=True) - validate_parser = subparsers.add_parser("validate", help="验证项目配置") - validate_parser.add_argument("profile", type=Path) - render_parser = subparsers.add_parser("render", help="生成 Markdown 执行计划") - render_parser.add_argument("profile", type=Path) - render_parser.add_argument("--output", required=True, type=Path) - args = parser.parse_args() - - try: - data = load_profile(args.profile) - errors = validate(data) - except (OSError, json.JSONDecodeError, ValueError) as error: - print(f"配置读取失败:{error}", file=sys.stderr) - return 2 - if errors: - for error in errors: - print(f"[错误] {error}", file=sys.stderr) - return 1 - if args.command == "validate": - print( - f"[通过] 配置有效:{len(data['projects'])} 个项目;" - "每项目12个需求规则实例、8个任务规则实例、31个闭环控制项。" - ) - return 0 - args.output.parent.mkdir(parents=True, exist_ok=True) - args.output.write_text(render(data), encoding="utf-8") - print(f"[完成] 已生成执行计划:{args.output}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/.gitignore b/.gitignore index 3c2d4f5..8f656b7 100644 --- a/.gitignore +++ b/.gitignore @@ -141,3 +141,5 @@ temp/ # Database files (keep directory structure and initial data) # assets/database/*.json # !assets/database/.gitkeep + +.superpowers/ diff --git a/.tmp-autorodo/16-drafts.md b/.tmp-autorodo/16-drafts.md new file mode 100644 index 0000000..9277e8f --- /dev/null +++ b/.tmp-autorodo/16-drafts.md @@ -0,0 +1,105 @@ +# AutoRDO 16 条清洗初稿(阶段 A) + +### 1 +**标题**:交车管理:交车导入展示交车明细 +**描述**:交、还车相关能力中,交车导入功能需展示交车明细 +**待确认**: +- 「交车明细」包含哪些字段 +- 还车导入是否同步调整 + +### 2 +**标题**:故障管理:选择车辆后展示车辆里程 +**描述**:故障管理中,选定车辆后展示该车里程信息 +**待确认**: +- 里程数据来源(车机/GPS/人工等) + +### 3 +**标题**:交还车管理:拍照前增加 3 秒提示 +**描述**:交还车管理中,调用相机拍照前增加 3 秒拍照提示 +**待确认**: +- 适用于交车、还车还是全部拍照场景 + +### 4 +**标题**:加氢站管理:氢费明细支持批量删除 +**描述**:加氢站管理支持批量删除氢费明细 +**待确认**: +- 删除权限与是否可恢复 +- 已对账/已付款明细是否允许删除 + +### 5 +**标题**:系统全局:右上角帮助中心(车辆状态说明) +**描述**:系统右上角增加「帮助中心」入口,内容为「车辆状态说明」,供用户自行查阅 +**待确认**: +- 是否仅车辆状态说明,还是帮助中心后续可扩展其他说明 + +### 6 +**标题**:还车应结款:违章处理违约金与保险上浮改由安全组录入 +**描述**:还车应结款中,「违章处理违约金」「保险上浮」由业务服务组录入,调整为在安全组内填写 +**待确认**: +- 「业务服务组」「安全组」是否分别映射为业务服务组/安全部,或单据内分区名称保持原文 + +### 7 +**标题**:还车应结款:安全组增加其他违规费用 +**描述**:还车应结款的安全组增加「其他违规费用」字段 +**待确认**: +- 字段类型、是否必填及是否计入应退/应补汇总 + +### 8 +**标题**:违章管理:列表审计字段、导入去重与全量更新 +**描述**: +违章管理优化如下: +1. 列表增加创建人、创建时间、更新人、更新时间 +2. 按「车牌号 + 违法时间」校验重复;导入遇重复时提示「车牌浙F0001违章数据重复,请清理后再导入」 +3. 支持违章数据全量更新 +**待确认**: +- 「全量更新」是整表覆盖还是按车牌+违法时间 upsert +- 重复校验是否同步作用于手工新增 + +### 9 +**标题**:车辆氢费明细:站名变更后自动更新已导入数据 +**描述**:加氢站主体信息变更时,车辆氢费明细中已导入数据自动更新为新加氢站名称 +**待确认**: +- 是否仅更新站名,还是同步其他站点主数据字段 + +### 10 +**标题**:还车应结款:增加按提交人查询 +**描述**:还车应结款增加「提交人」查询条件 +**待确认**: +- 「提交人」对应单据哪个角色字段(发起人/业管提交人等) + +### 11 +**标题**:还车管理:列表展示还车人信息 +**描述**:还车管理列表(待办与历史)增加还车人信息;点击「车辆到达」后,待办列表展示还车人 +**待确认**: +- 「还车人」与司机/客户经办人是否同一字段 + +### 12 +**标题**:加氢站管理:联系电话校验放宽支持座机 +**描述**:站点信息中「联系电话」正则校验放宽,允许录入座机号码 +**待确认**: +- 允许的座机格式范围(区号、分机等) + +### 13 +**标题**:系统全局:优化置灰文案可读性 +**描述**:全局优化置灰字体显示,使不可编辑/禁用态文案更清晰 +**待确认**: +- 优先覆盖的页面范围 + +### 14 +**标题**:还车应结款:导出增加四小组操作人及状态 +**描述**:还车应结款导出列表增加四个小组的操作人信息,含当前展示人员及操作状态 +**待确认**: +- 「四个小组」具体对应哪四个部门分区 +- 操作状态枚举值 + +### 15 +**标题**:车辆管理:多车牌逗号分隔批量查询 +**描述**:车辆管理允许多辆车牌搜索;可批量复制车牌,用英文逗号 `,` 隔开 +**待确认**: +- 是否兼容中文逗号、空格、换行等分隔符 + +### 16 +**标题**:租赁合同等:审批状态旁增加当前审批人 +**描述**:租赁合同、提车应收款、还车应结款、租赁业务台账(材料称「租赁账单」)在审批状态/状态右侧增加列「当前审批人」,显示工作流当前审批操作人姓名 +**待确认**: +- 多人并行审批时展示规则(全部列出 / 仅主处理人) diff --git a/.tmp-autorodo/19-confirmed-ledger.md b/.tmp-autorodo/19-confirmed-ledger.md new file mode 100644 index 0000000..4f32672 --- /dev/null +++ b/.tmp-autorodo/19-confirmed-ledger.md @@ -0,0 +1,223 @@ +# AutoRDO 已确认定稿(台账校正版 · 19 条) + +> 来源:用户粘贴反馈台账(含反馈人/部门/优先级/禅道/端) +> 第 6 行含「此外」合为一条 · 推荐默认已并入描述 · 不写云效 + +--- + +### 1(已确认) + +## 原始诉求(AutoRDO) + +**标题**:交车管理:交车导入展示交车明细 + +**描述**: +交、还车场景下,交车导入功能需展示交车明细 +明细字段与现有交车详情页一致,全量带出 +本期仅调整交车导入,还车导入不同步改动 +反馈:2026-05-27 · 杨凤娥 / 业务管理部 · 【交还车管理】 · P1-高 · 已完成 · 禅道 140 · PC端 · 用户已确认 + +### 2(已确认) + +## 原始诉求(AutoRDO) + +**标题**:故障管理:选择车辆后展示车辆里程 + +**描述**: +故障管理中,选定车辆后展示该车里程信息 +里程优先取车机/GPS,无则回落车辆档案登记里程 +反馈:2026-05-26 · 王建功 / 运维部 · 【故障管理】 · 待评估 · 禅道 131 · PC及小程序 · 用户已确认 + +### 3(已确认) + +## 原始诉求(AutoRDO) + +**标题**:交还车管理:拍照前增加 3 秒提示 + +**描述**: +交还车管理中,调用相机拍照前增加 3 秒拍照提示 +适用于交车、还车全部拍照场景 +反馈:2026-05-28 · 沈帅 / 运维部 · 【交还车管理】 · P2-中 · 已完成 · 禅道 143 · 小程序 · 用户已确认 + +### 4(已确认) + +## 原始诉求(AutoRDO) + +**标题**:加氢站管理:氢费明细支持批量删除 + +**描述**: +加氢站管理中,氢费明细允许批量删除 +有权限即可删除,删除后不可恢复 +仅未对账、未付款的明细允许删除 +反馈:2026-06-04 · 许诗琪 / 业务管理部 · 【加氢站管理】 · P2-中 · 已完成 · PC端 · 用户已确认 + +### 5(已确认) + +## 原始诉求(AutoRDO) + +**标题**:系统全局:右上角帮助中心(车辆状态说明) + +**描述**: +系统右上角增加「帮助中心」入口 +本期内容为「车辆状态说明」,供用户自行查阅 +入口按帮助中心建设,后续可扩展其他说明 +反馈:2026-06-10 · 王冕 / 数智中心 · 其他 · P2-中 · 已完成 · 禅道 184 · PC端 · 用户已确认 + +### 6(已确认) + +## 原始诉求(AutoRDO) + +**标题**:还车应结款:安全组承接违章违约金保险上浮并增加其他违规费用 + +**描述**: +还车应结款中,「违章处理违约金」「保险上浮」由业务服务组录入,调整为在安全组内填写 +分区名称保持单据原文「业务服务组」「安全组」 +此外,安全组增加「其他违规费用」字段:金额、非必填,计入应退/应补汇总 +反馈:2026-06-10 · 王冕 / 数智中心 · 【还车应结款】 · P1-高 · 处理中 · 禅道 183 · PC端 · 用户已确认 + +### 7(已确认) + +## 原始诉求(AutoRDO) + +**标题**:违章管理:列表审计字段、导入去重与全量更新 + +**描述**: +违章管理优化如下: +1. 列表增加创建人、创建时间、更新人、更新时间 +2. 按「车牌号 + 违法时间」校验重复;导入遇重复时提示「车牌浙F0001违章数据重复,请清理后再导入」 +3. 支持违章数据全量更新:按「车牌号 + 违法时间」做 upsert,而非整表覆盖 +4. 重复校验同时作用于导入与手工新增 +反馈:2026-06-11 · 秦蔚 / 数智中心 · 【违章管理】 · P2-中 · 已完成 · PC端 · 用户已确认 + +### 8(已确认) + +## 原始诉求(AutoRDO) + +**标题**:车辆氢费明细:加氢站主体变更后自动更新已导入站名 + +**描述**: +加氢站主体信息变更时,车辆氢费明细中已导入数据自动更新为新加氢站名称 +本期仅同步更新站名,不同步其他站点主数据字段 +反馈:2026-05-29 · 王冕 / 数智中心 · 【车辆氢费明细】 · P2-中 · 已排期 · 禅道 150 · PC端 · 用户已确认 + +### 9(已确认) + +## 原始诉求(AutoRDO) + +**标题**:还车应结款:增加按提交人查询 + +**描述**: +还车应结款增加「提交人」查询条件 +提交人对应单据发起人/创建人 +反馈:2026-06-01 · 童军林 / 运维部 · 【还车应结款】 · P3-低 · 已完成 · 禅道 153 · PC端 · 用户已确认 + +### 10(已确认) + +## 原始诉求(AutoRDO) + +**标题**:还车管理:列表展示还车人信息 + +**描述**: +还车管理列表(待办与历史)增加还车人信息;点击「车辆到达」后,待办列表展示还车人 +还车人指执行还车的司机 +反馈:2026-06-02 · 童军林 / 运维部 · 【还车管理】 · P3-低 · 已完成 · 禅道 164 · PC端 · 用户已确认 + +### 11(已确认) + +## 原始诉求(AutoRDO) + +**标题**:加氢站管理:联系电话校验放宽以支持座机 + +**描述**: +站点信息中「联系电话」正则规则放开,允许录入手机号与带区号座机 +具体正则由研发按上述范围实现 +反馈:2026-06-04 · 姚守涛 / 业务管理部 · 【加氢站管理】 · P2-中 · 已完成 · 禅道 168 · PC端 · 用户已确认 + +### 12(已确认) + +## 原始诉求(AutoRDO) + +**标题**:系统全局:优化置灰字体可读性 + +**描述**: +全局优化置灰字体显示,使不可编辑/禁用态文案更清晰 +覆盖系统全部置灰文案 +反馈:2026-06-04 · 童军林 / 运维部 · 【全局优化】 · P3-低 · 已完成 · 禅道 167 · PC端 · 用户已确认 + +### 13(已确认) + +## 原始诉求(AutoRDO) + +**标题**:还车应结款:导出增加四小组操作人及操作状态 + +**描述**: +还车应结款导出列表增加四个小组的操作人信息,含当前展示人员及操作状态 +四个小组对应:业务管理组、安全部、运维部、业务管理组-能源部 +操作状态枚举沿用现网会签状态 +反馈:2026-06-05 · 秦蔚 / 数智中心 · 【还车应结款】 · P3-低 · 已完成 · 禅道 172 · PC端 · 用户已确认 + +### 14(已确认) + +## 原始诉求(AutoRDO) + +**标题**:车辆管理:支持多车牌逗号分隔批量查询 + +**描述**: +车辆管理允许多辆车牌搜索;可批量复制车牌查询 +以英文逗号 `,` 为主分隔符,同时兼容中文逗号、空格、换行 +反馈:2026-06-05 · 姚守涛 / 业务管理部 · 【车辆管理】 · P3-低 · 已完成 · 禅道 173 · PC端 · 用户已确认 + +### 15(已确认) + +## 原始诉求(AutoRDO) + +**标题**:租赁合同等:审批状态旁增加当前审批人 + +**描述**: +租赁合同、提车应收款、还车应结款、租赁业务台账(材料称「租赁账单」)在审批状态/状态右侧增加列「当前审批人」,显示工作流当前审批操作人姓名 +多人并行审批时列出全部当前处理人(顿号或逗号拼接) +反馈:2026-05-27 · 何苗苗 / 业务管理部 · 小程序审批中心 · P2-中 · 已完成 · 禅道 139 · PC端 · 用户已确认 + +### 16(已确认) + +## 原始诉求(AutoRDO) + +**标题**:车辆管理:客户名称后增加项目名称字段 + +**描述**: +车辆管理列表在「客户名称」字段后增加「项目名称」 +项目名称取该车辆关联的履约项目/合同项目名称 +反馈:2026-05-27 · 杨凤娥 / 业务管理部 · 【车辆管理】 · P2-中 · 已完成 · 禅道 141 · PC端 · 用户已确认 + +### 17(已确认) + +## 原始诉求(AutoRDO) + +**标题**:车辆调拨:增加车牌号与停车场模糊查询 + +**描述**: +调拨管理(车辆调拨)增加车牌号、停车场模糊查询 +反馈:2026-05-28 · 魏山 / 运维部 · 【调拨管理】 · P2-中 · 已完成 · 禅道 145 · 小程序 · 用户已确认 + +### 18(已确认) + +## 原始诉求(AutoRDO) + +**标题**:客户管理与供应商管理:表单页新增保存功能 + +**描述**: +客户管理、供应商管理表单页新增保存功能 +保存为校验通过后落库保存,不替代原提交/审批动作 +客户管理与供应商管理保存规则保持一致 +反馈:2026-06-01 · 王冕 / 数智中心 · 【客户管理/供应商管理】 · P2-中 · 已完成 · PC端 · 用户已确认 + +### 19(已确认) + +## 原始诉求(AutoRDO) + +**标题**:交车管理:交车时间精确到分钟 + +**描述**: +交车管理中,交车时间精确到分钟 +展示、录入、筛选均精确到分钟 +反馈:2026-06-01 · 王冕 / 数智中心 · 【交车管理】 · P3-低 · 已完成 · PC端 · 用户已确认 diff --git a/.tmp-autorodo/19-confirmed-with-meta.md b/.tmp-autorodo/19-confirmed-with-meta.md new file mode 100644 index 0000000..4cf653b --- /dev/null +++ b/.tmp-autorodo/19-confirmed-with-meta.md @@ -0,0 +1,318 @@ +# AutoRDO 已确认定稿(台账 + 元数据识别 · 19 条) + +> 类型/优先级/标签/提交部门/提交人已从台账列自动识别并映射标准名 +> 描述并入推荐默认口径 · 待确认:无 · 不写云效 + +--- + +### 1(已确认) + +## 原始诉求(AutoRDO) + +**标题**:交车管理:交车导入展示交车明细 +**类型**:【优化】 +**优先级**:P1-高 +**标签**:交车管理, PC端 +**提交部门**:业务管理组 +**提交人**:杨凤娥 + +**描述**: +交、还车场景下,交车导入功能需展示交车明细 +明细字段与现有交车详情页一致,全量带出 +本期仅调整交车导入,还车导入不同步改动 +反馈:2026-05-27 · 【交还车管理】 · 已完成 · 禅道 140 · 用户已确认 + +### 2(已确认) + +## 原始诉求(AutoRDO) + +**标题**:故障管理:选择车辆后展示车辆里程 +**类型**:【优化】 +**优先级**:P2-中 +**标签**:故障管理, PC端, 小程序 +**提交部门**:运维部 +**提交人**:王建功 + +**描述**: +故障管理中,选定车辆后展示该车里程信息 +里程优先取车机/GPS,无则回落车辆档案登记里程 +反馈:2026-05-26 · 【故障管理】 · 待评估 · 禅道 131 · 用户已确认 + +### 3(已确认) + +## 原始诉求(AutoRDO) + +**标题**:交还车管理:拍照前增加 3 秒提示 +**类型**:【优化】 +**优先级**:P2-中 +**标签**:交车管理, 还车管理, 小程序 +**提交部门**:运维部 +**提交人**:沈帅 + +**描述**: +交还车管理中,调用相机拍照前增加 3 秒拍照提示 +适用于交车、还车全部拍照场景 +反馈:2026-05-28 · 【交还车管理】 · 已完成 · 禅道 143 · 用户已确认 + +### 4(已确认) + +## 原始诉求(AutoRDO) + +**标题**:加氢站管理:氢费明细支持批量删除 +**类型**:【新增】 +**优先级**:P2-中 +**标签**:加氢站管理, PC端 +**提交部门**:业务管理组 +**提交人**:许诗琪 + +**描述**: +加氢站管理中,氢费明细允许批量删除 +有权限即可删除,删除后不可恢复 +仅未对账、未付款的明细允许删除 +反馈:2026-06-04 · 【加氢站管理】 · 已完成 · 用户已确认 + +### 5(已确认) + +## 原始诉求(AutoRDO) + +**标题**:系统全局:右上角帮助中心(车辆状态说明) +**类型**:【新增】 +**优先级**:P2-中 +**标签**:系统全局, PC端 +**提交部门**:数智部 +**提交人**:王冕 + +**描述**: +系统右上角增加「帮助中心」入口 +本期内容为「车辆状态说明」,供用户自行查阅 +入口按帮助中心建设,后续可扩展其他说明 +反馈:2026-06-10 · 其他 · 已完成 · 禅道 184 · 用户已确认 + +### 6(已确认) + +## 原始诉求(AutoRDO) + +**标题**:还车应结款:安全组承接违章违约金保险上浮并增加其他违规费用 +**类型**:【优化】 +**优先级**:P1-高 +**标签**:还车应结款, PC端 +**提交部门**:数智部 +**提交人**:王冕 + +**描述**: +还车应结款中,「违章处理违约金」「保险上浮」由业务服务组录入,调整为在安全组内填写 +分区名称保持单据原文「业务服务组」「安全组」 +此外,安全组增加「其他违规费用」字段:金额、非必填,计入应退/应补汇总 +反馈:2026-06-10 · 【还车应结款】 · 处理中 · 禅道 183 · 用户已确认 + +### 7(已确认) + +## 原始诉求(AutoRDO) + +**标题**:违章管理:列表审计字段、导入去重与全量更新 +**类型**:【优化】 +**优先级**:P2-中 +**标签**:违章管理, PC端 +**提交部门**:数智部 +**提交人**:秦蔚 + +**描述**: +违章管理优化如下: +1. 列表增加创建人、创建时间、更新人、更新时间 +2. 按「车牌号 + 违法时间」校验重复;导入遇重复时提示「车牌浙F0001违章数据重复,请清理后再导入」 +3. 支持违章数据全量更新:按「车牌号 + 违法时间」做 upsert,而非整表覆盖 +4. 重复校验同时作用于导入与手工新增 +反馈:2026-06-11 · 【违章管理】 · 已完成 · 用户已确认 + +### 8(已确认) + +## 原始诉求(AutoRDO) + +**标题**:车辆氢费明细:加氢站主体变更后自动更新已导入站名 +**类型**:【优化】 +**优先级**:P2-中 +**标签**:车辆氢费明细, PC端 +**提交部门**:数智部 +**提交人**:王冕 + +**描述**: +加氢站主体信息变更时,车辆氢费明细中已导入数据自动更新为新加氢站名称 +本期仅同步更新站名,不同步其他站点主数据字段 +反馈:2026-05-29 · 【车辆氢费明细】 · 已排期 · 禅道 150 · 用户已确认 + +### 9(已确认) + +## 原始诉求(AutoRDO) + +**标题**:还车应结款:增加按提交人查询 +**类型**:【新增】 +**优先级**:P3-低 +**标签**:还车应结款, PC端 +**提交部门**:运维部 +**提交人**:童军林 + +**描述**: +还车应结款增加「提交人」查询条件 +提交人对应单据发起人/创建人 +反馈:2026-06-01 · 【还车应结款】 · 已完成 · 禅道 153 · 用户已确认 + +### 10(已确认) + +## 原始诉求(AutoRDO) + +**标题**:还车管理:列表展示还车人信息 +**类型**:【新增】 +**优先级**:P3-低 +**标签**:还车管理, PC端 +**提交部门**:运维部 +**提交人**:童军林 + +**描述**: +还车管理列表(待办与历史)增加还车人信息;点击「车辆到达」后,待办列表展示还车人 +还车人指执行还车的司机 +反馈:2026-06-02 · 【还车管理】 · 已完成 · 禅道 164 · 用户已确认 + +### 11(已确认) + +## 原始诉求(AutoRDO) + +**标题**:加氢站管理:联系电话校验放宽以支持座机 +**类型**:【优化】 +**优先级**:P2-中 +**标签**:加氢站管理, PC端 +**提交部门**:业务管理组 +**提交人**:姚守涛 + +**描述**: +站点信息中「联系电话」正则规则放开,允许录入手机号与带区号座机 +具体正则由研发按上述范围实现 +反馈:2026-06-04 · 【加氢站管理】 · 已完成 · 禅道 168 · 用户已确认 + +### 12(已确认) + +## 原始诉求(AutoRDO) + +**标题**:系统全局:优化置灰字体可读性 +**类型**:【优化】 +**优先级**:P3-低 +**标签**:系统全局, PC端 +**提交部门**:运维部 +**提交人**:童军林 + +**描述**: +全局优化置灰字体显示,使不可编辑/禁用态文案更清晰 +覆盖系统全部置灰文案 +反馈:2026-06-04 · 【全局优化】 · 已完成 · 禅道 167 · 用户已确认 + +### 13(已确认) + +## 原始诉求(AutoRDO) + +**标题**:还车应结款:导出增加四小组操作人及操作状态 +**类型**:【新增】 +**优先级**:P3-低 +**标签**:还车应结款, PC端 +**提交部门**:数智部 +**提交人**:秦蔚 + +**描述**: +还车应结款导出列表增加四个小组的操作人信息,含当前展示人员及操作状态 +四个小组对应:业务管理组、安全部、运维部、业务管理组-能源部 +操作状态枚举沿用现网会签状态 +反馈:2026-06-05 · 【还车应结款】 · 已完成 · 禅道 172 · 用户已确认 + +### 14(已确认) + +## 原始诉求(AutoRDO) + +**标题**:车辆管理:支持多车牌逗号分隔批量查询 +**类型**:【新增】 +**优先级**:P3-低 +**标签**:车辆管理, PC端 +**提交部门**:业务管理组 +**提交人**:姚守涛 + +**描述**: +车辆管理允许多辆车牌搜索;可批量复制车牌查询 +以英文逗号 `,` 为主分隔符,同时兼容中文逗号、空格、换行 +反馈:2026-06-05 · 【车辆管理】 · 已完成 · 禅道 173 · 用户已确认 + +### 15(已确认) + +## 原始诉求(AutoRDO) + +**标题**:租赁合同等:审批状态旁增加当前审批人 +**类型**:【新增】 +**优先级**:P2-中 +**标签**:租赁合同, 提车应收款, 还车应结款, 租赁业务台账, PC端 +**提交部门**:业务管理组 +**提交人**:何苗苗 + +**描述**: +租赁合同、提车应收款、还车应结款、租赁业务台账(材料称「租赁账单」)在审批状态/状态右侧增加列「当前审批人」,显示工作流当前审批操作人姓名 +多人并行审批时列出全部当前处理人(顿号或逗号拼接) +反馈:2026-05-27 · 小程序审批中心 · 已完成 · 禅道 139 · 用户已确认 + +### 16(已确认) + +## 原始诉求(AutoRDO) + +**标题**:车辆管理:客户名称后增加项目名称字段 +**类型**:【新增】 +**优先级**:P2-中 +**标签**:车辆管理, PC端 +**提交部门**:业务管理组 +**提交人**:杨凤娥 + +**描述**: +车辆管理列表在「客户名称」字段后增加「项目名称」 +项目名称取该车辆关联的履约项目/合同项目名称 +反馈:2026-05-27 · 【车辆管理】 · 已完成 · 禅道 141 · 用户已确认 + +### 17(已确认) + +## 原始诉求(AutoRDO) + +**标题**:车辆调拨:增加车牌号与停车场模糊查询 +**类型**:【新增】 +**优先级**:P2-中 +**标签**:车辆调拨, 小程序 +**提交部门**:运维部 +**提交人**:魏山 + +**描述**: +调拨管理(车辆调拨)增加车牌号、停车场模糊查询 +反馈:2026-05-28 · 【调拨管理】 · 已完成 · 禅道 145 · 用户已确认 + +### 18(已确认) + +## 原始诉求(AutoRDO) + +**标题**:客户管理与供应商管理:表单页新增保存功能 +**类型**:【新增】 +**优先级**:P2-中 +**标签**:客户管理, 供应商管理, PC端 +**提交部门**:数智部 +**提交人**:王冕 + +**描述**: +客户管理、供应商管理表单页新增保存功能 +保存为校验通过后落库保存,不替代原提交/审批动作 +客户管理与供应商管理保存规则保持一致 +反馈:2026-06-01 · 【客户管理/供应商管理】 · 已完成 · 用户已确认 + +### 19(已确认) + +## 原始诉求(AutoRDO) + +**标题**:交车管理:交车时间精确到分钟 +**类型**:【优化】 +**优先级**:P3-低 +**标签**:交车管理, PC端 +**提交部门**:数智部 +**提交人**:王冕 + +**描述**: +交车管理中,交车时间精确到分钟 +展示、录入、筛选均精确到分钟 +反馈:2026-06-01 · 【交车管理】 · 已完成 · 用户已确认 diff --git a/.tmp-autorodo/26-confirmed.md b/.tmp-autorodo/26-confirmed.md new file mode 100644 index 0000000..75c5eab --- /dev/null +++ b/.tmp-autorodo/26-confirmed.md @@ -0,0 +1,387 @@ +# AutoRDO 已确认定稿(推荐默认) + +> 用户指令:全部采用推荐默认 +> 第 24 与第 27 已合并为一条 +> 共 **26** 条定稿 · 不写云效 + +--- + +### 采用的推荐默认一览 + +| 题号 | 推荐 | 口径摘要 | +|---|---|---| +| 1.1 | A | 交车明细=现有交车详情页全量字段 | +| 1.2 | A | 仅交车导入,还车导入不同步改 | +| 2.1 | C | 优先车机/GPS,无则回落车辆档案里程 | +| 3.1 | C | 交还车全部拍照场景 | +| 4.1 | A | 有权限可删,不可恢复 | +| 4.2 | C | 仅未对账/未付款可删 | +| 5.1 | B | 帮助中心入口;本期内容为车辆状态说明,预留扩展 | +| 6.1 | A | 保持单据分区名「业务服务组 / 安全组」 | +| 7.1 | A | 金额、非必填、计入应退/应补汇总 | +| 8.1 | B | 按车牌号+违法时间 upsert | +| 8.2 | B | 导入与手工新增均校验重复 | +| 9.1 | A | 仅自动更新加氢站名称 | +| 10.1 | A | 提交人=单据发起人/创建人 | +| 11.1 | A | 还车人=执行还车的司机 | +| 12.1 | C | 手机+带区号座机;具体正则研发定 | +| 13.1 | A | 全局置灰文案一并优化 | +| 14.1 | A | 四小组=业务管理组、安全部、运维部、业务管理组-能源部 | +| 14.2 | B | 操作状态沿用现网会签状态枚举 | +| 15.1 | B | 英文逗号为主,兼容中文逗号/空格/换行 | +| 16.1 | A | 并行审批列出全部当前处理人 | +| 17 | 默认 | 项目名称取车辆关联履约项目/合同项目名 | +| 19 | 默认 | 校验后保存;客户管理与供应商管理规则一致;不替代提交 | +| 20 | 默认 | 展示、录入、筛选均精确到分钟 | +| 22 | 默认 | 优化待办列表展示、到期提醒与完成状态可见性 | +| 23 | 默认 | 无 GPS 时回落原手工运营城市并提示可刷新 | +| 24+27 | 合并 | 去除审批流;取消新增/编辑/删除审批 | +| 25 | 默认 | 重构列表字段、筛选项与字段联动,清单以现网盘点为准 | +| 26 | 默认 | 导入按车牌+事故时间去重;枚举以产品对照表刷新 | + +--- + +### 1(已确认) + +## 原始诉求(AutoRDO) + +**标题**:交车管理:交车导入展示交车明细 + +**描述**: +交、还车场景下,交车导入功能需展示交车明细 +明细字段与现有交车详情页一致,全量带出 +本期仅调整交车导入,还车导入不同步改动 + +待确认: +无 + +### 2(已确认) + +## 原始诉求(AutoRDO) + +**标题**:故障管理:选择车辆后展示车辆里程 + +**描述**: +故障管理中,选定车辆后展示该车里程信息 +里程优先取车机/GPS,无则回落车辆档案登记里程 + +待确认: +无 + +### 3(已确认) + +## 原始诉求(AutoRDO) + +**标题**:交还车管理:拍照前增加 3 秒提示 + +**描述**: +交还车管理中,调用相机拍照前增加 3 秒拍照提示 +适用于交车、还车全部拍照场景 + +待确认: +无 + +### 4(已确认) + +## 原始诉求(AutoRDO) + +**标题**:加氢站管理:氢费明细支持批量删除 + +**描述**: +加氢站管理中,氢费明细允许批量删除 +有权限即可删除,删除后不可恢复 +仅未对账、未付款的明细允许删除 + +待确认: +无 + +### 5(已确认) + +## 原始诉求(AutoRDO) + +**标题**:系统全局:右上角帮助中心(车辆状态说明) + +**描述**: +系统右上角增加「帮助中心」入口 +本期内容为「车辆状态说明」,供用户自行查阅 +入口按帮助中心建设,后续可扩展其他说明 + +待确认: +无 + +### 6(已确认) + +## 原始诉求(AutoRDO) + +**标题**:还车应结款:违章处理违约金与保险上浮改由安全组填写 + +**描述**: +还车应结款中,「违章处理违约金」「保险上浮」由业务服务组录入,调整为在安全组内填写 +分区名称保持单据原文「业务服务组」「安全组」 + +待确认: +无 + +### 7(已确认) + +## 原始诉求(AutoRDO) + +**标题**:还车应结款:安全组增加其他违规费用 + +**描述**: +还车应结款的安全组增加「其他违规费用」字段 +为金额字段,非必填,计入应退/应补汇总 + +待确认: +无 + +### 8(已确认) + +## 原始诉求(AutoRDO) + +**标题**:违章管理:列表审计字段、导入去重与全量更新 + +**描述**: +违章管理优化如下: +1. 列表增加创建人、创建时间、更新人、更新时间 +2. 按「车牌号 + 违法时间」校验重复;导入遇重复时提示「车牌浙F0001违章数据重复,请清理后再导入」 +3. 支持违章数据全量更新:按「车牌号 + 违法时间」做 upsert,而非整表覆盖 +4. 重复校验同时作用于导入与手工新增 + +待确认: +无 + +### 9(已确认) + +## 原始诉求(AutoRDO) + +**标题**:车辆氢费明细:加氢站主体变更后自动更新已导入站名 + +**描述**: +加氢站主体信息变更时,车辆氢费明细中已导入数据自动更新为新加氢站名称 +本期仅同步更新站名,不同步其他站点主数据字段 + +待确认: +无 + +### 10(已确认) + +## 原始诉求(AutoRDO) + +**标题**:还车应结款:增加按提交人查询 + +**描述**: +还车应结款增加「提交人」查询条件 +提交人对应单据发起人/创建人 + +待确认: +无 + +### 11(已确认) + +## 原始诉求(AutoRDO) + +**标题**:还车管理:列表展示还车人信息 + +**描述**: +还车管理列表(待办与历史)增加还车人信息;点击「车辆到达」后,待办列表展示还车人 +还车人指执行还车的司机 + +待确认: +无 + +### 12(已确认) + +## 原始诉求(AutoRDO) + +**标题**:加氢站管理:联系电话校验放宽以支持座机 + +**描述**: +站点信息中「联系电话」正则规则放开,允许录入手机号与带区号座机 +具体正则由研发按上述范围实现 + +待确认: +无 + +### 13(已确认) + +## 原始诉求(AutoRDO) + +**标题**:系统全局:优化置灰字体可读性 + +**描述**: +全局优化置灰字体显示,使不可编辑/禁用态文案更清晰 +覆盖系统全部置灰文案 + +待确认: +无 + +### 14(已确认) + +## 原始诉求(AutoRDO) + +**标题**:还车应结款:导出增加四小组操作人及操作状态 + +**描述**: +还车应结款导出列表增加四个小组的操作人信息,含当前展示人员及操作状态 +四个小组对应:业务管理组、安全部、运维部、业务管理组-能源部 +操作状态枚举沿用现网会签状态 + +待确认: +无 + +### 15(已确认) + +## 原始诉求(AutoRDO) + +**标题**:车辆管理:支持多车牌逗号分隔批量查询 + +**描述**: +车辆管理允许多辆车牌搜索;可批量复制车牌查询 +以英文逗号 `,` 为主分隔符,同时兼容中文逗号、空格、换行 + +待确认: +无 + +### 16(已确认) + +## 原始诉求(AutoRDO) + +**标题**:租赁合同等:审批状态旁增加当前审批人 + +**描述**: +租赁合同、提车应收款、还车应结款、租赁业务台账(材料称「租赁账单」)在审批状态/状态右侧增加列「当前审批人」,显示工作流当前审批操作人姓名 +多人并行审批时列出全部当前处理人(顿号或逗号拼接) + +待确认: +无 + +### 17(已确认) + +## 原始诉求(AutoRDO) + +**标题**:车辆管理:客户名称后增加项目名称字段 + +**描述**: +车辆管理列表在「客户名称」字段后增加「项目名称」 +项目名称取该车辆关联的履约项目/合同项目名称 + +待确认: +无 + +### 18(已确认) + +## 原始诉求(AutoRDO) + +**标题**:车辆调拨:增加车牌号与停车场模糊查询 + +**描述**: +调拨管理(车辆调拨)增加车牌号、停车场模糊查询 + +待确认: +无 + +### 19(已确认) + +## 原始诉求(AutoRDO) + +**标题**:客户管理与供应商管理:表单页新增保存功能 + +**描述**: +客户管理、供应商管理表单页新增保存功能 +保存为校验通过后落库保存,不替代原提交/审批动作 +客户管理与供应商管理保存规则保持一致 + +待确认: +无 + +### 20(已确认) + +## 原始诉求(AutoRDO) + +**标题**:交车管理:交车时间精确到分钟 + +**描述**: +交车管理中,交车时间精确到分钟 +展示、录入、筛选均精确到分钟 + +待确认: +无 + +### 21(已确认) + +## 原始诉求(AutoRDO) + +**标题**:车辆管理:登记所有权改为多选下拉并支持模糊查询 + +**描述**: +车辆管理中「登记所有权」筛选改为下拉筛选(多选),且支持模糊查询 + +待确认: +无 + +### 22(已确认) + +## 原始诉求(AutoRDO) + +**标题**:年审记录:年审待办优化 + +**描述**: +年审管理(年审记录)中年审待办优化 +优化待办列表展示、到期提醒与完成状态可见性(材料未展开的细项按常规待办体验优化理解) + +待确认: +无 + +### 23(已确认) + +## 原始诉求(AutoRDO) + +**标题**:车辆管理:运营城市改为取 GPS 最后定位省市 + +**描述**: +车辆管理中「运营城市」修改为获取 GPS 最后定位的省市 +无定位数据时回落原手工运营城市,并提示可刷新定位 + +待确认: +无 + +### 24(已确认 · 含原第 27 条) + +## 原始诉求(AutoRDO) + +**标题**:停车场管理:去除审批工作流并取消新增编辑删除审批 + +**描述**: +停车场管理去除审批工作流 +新增、编辑、删除均不再走审批 + +待确认: +无 + +### 25(已确认) + +## 原始诉求(AutoRDO) + +**标题**:交车管理:列表数据及筛选与字段逻辑重构 + +**描述**: +交车管理列表数据及筛选、字段逻辑重构 +重构范围覆盖列表字段展示、筛选项与字段联动规则,具体清单以现网字段盘点为准 + +待确认: +无 + +### 26(已确认) + +## 原始诉求(AutoRDO) + +**标题**:事故管理:新增批量导入并调整下拉枚举 + +**描述**: +事故管理新增批量导入功能,并修改下拉选项枚举值 +批量导入按「车牌号 + 事故时间」去重 +下拉枚举以产品提供的新旧对照表刷新为准 + +待确认: +无 diff --git a/.tmp-autorodo/27-drafts.md b/.tmp-autorodo/27-drafts.md new file mode 100644 index 0000000..23cec07 --- /dev/null +++ b/.tmp-autorodo/27-drafts.md @@ -0,0 +1,336 @@ +# AutoRDO 清洗初稿(本轮素材) + +共 27 条原始诉求(已按行/条拆解;违章管理 3 点子点合并为第 8 条) + +### 1 + +## 原始诉求(AutoRDO) + +**标题**:交车管理:交车导入展示交车明细 + +**描述**: +交、还车场景下,交车导入功能需展示交车明细 + +待确认: +- 「交车明细」包含哪些字段 +- 还车导入是否同步调整 + +### 2 + +## 原始诉求(AutoRDO) + +**标题**:故障管理:选择车辆后展示车辆里程 + +**描述**: +故障管理中,选定车辆后展示该车里程信息 + +待确认: +- 里程数据来源(车机/GPS/人工档案等) + +### 3 + +## 原始诉求(AutoRDO) + +**标题**:交还车管理:拍照前增加 3 秒提示 + +**描述**: +交还车管理中,调用相机拍照前增加 3 秒拍照提示 + +待确认: +- 适用于交车、还车还是全部拍照场景 + +### 4 + +## 原始诉求(AutoRDO) + +**标题**:加氢站管理:氢费明细支持批量删除 + +**描述**: +加氢站管理中,氢费明细允许批量删除 + +待确认: +- 删除权限与是否可恢复 +- 已对账/已付款明细是否允许删除 + +### 5 + +## 原始诉求(AutoRDO) + +**标题**:系统全局:右上角帮助中心(车辆状态说明) + +**描述**: +系统右上角增加「帮助中心」入口,内容为「车辆状态说明」,供用户自行查阅 + +待确认: +- 本期是否仅车辆状态说明,还是按帮助中心入口建设并预留扩展 + +### 6 + +## 原始诉求(AutoRDO) + +**标题**:还车应结款:违章处理违约金与保险上浮改由安全组填写 + +**描述**: +还车应结款中,「违章处理违约金」「保险上浮」由业务服务组录入,调整为在安全组内填写 + +待确认: +- 「业务服务组」「安全组」为单据分区名还是映射为业务服务组/安全部(或业务管理组) + +### 7 + +## 原始诉求(AutoRDO) + +**标题**:还车应结款:安全组增加其他违规费用 + +**描述**: +还车应结款的安全组增加「其他违规费用」字段 + +待确认: +- 字段类型、是否必填及是否计入应退/应补汇总 + +### 8 + +## 原始诉求(AutoRDO) + +**标题**:违章管理:列表审计字段、导入去重与全量更新 + +**描述**: +违章管理优化如下: +1. 列表增加创建人、创建时间、更新人、更新时间 +2. 按「车牌号 + 违法时间」校验重复;导入遇重复时提示「车牌浙F0001违章数据重复,请清理后再导入」 +3. 支持违章数据全量更新 + +待确认: +- 「全量更新」是整表覆盖还是按车牌号+违法时间 upsert +- 重复校验是否同步作用于手工新增 + +### 9 + +## 原始诉求(AutoRDO) + +**标题**:车辆氢费明细:加氢站主体变更后自动更新已导入站名 + +**描述**: +加氢站主体信息变更时,车辆氢费明细中已导入数据自动更新为新加氢站名称 + +待确认: +- 是否仅更新站名,还是同步其他已引用的站点主数据字段 + +### 10 + +## 原始诉求(AutoRDO) + +**标题**:还车应结款:增加按提交人查询 + +**描述**: +还车应结款增加「提交人」查询条件 + +待确认: +- 「提交人」对应发起人、业务管理组提交人还是当前节点提交人 + +### 11 + +## 原始诉求(AutoRDO) + +**标题**:还车管理:列表展示还车人信息 + +**描述**: +还车管理列表(待办与历史)增加还车人信息;点击「车辆到达」后,待办列表展示还车人 + +待确认: +- 「还车人」指司机、客户经办人还是运维操作人 + +### 12 + +## 原始诉求(AutoRDO) + +**标题**:加氢站管理:联系电话校验放宽以支持座机 + +**描述**: +站点信息中「联系电话」正则规则放开,允许录入座机号码 + +待确认: +- 允许的座机格式范围(区号、分机等) + +### 13 + +## 原始诉求(AutoRDO) + +**标题**:系统全局:优化置灰字体可读性 + +**描述**: +全局优化置灰字体显示,使不可编辑/禁用态文案更清晰 + +待确认: +- 优先覆盖全局还是先改高频页面 + +### 14 + +## 原始诉求(AutoRDO) + +**标题**:还车应结款:导出增加四小组操作人及操作状态 + +**描述**: +还车应结款导出列表增加四个小组的操作人信息,含当前展示人员及操作状态 + +待确认: +- 「四个小组」是否对应业务管理组、安全部、运维部、业务管理组-能源部 +- 操作状态枚举是否沿用现网会签状态 + +### 15 + +## 原始诉求(AutoRDO) + +**标题**:车辆管理:支持多车牌逗号分隔批量查询 + +**描述**: +车辆管理允许多辆车牌搜索;可批量复制车牌,用英文逗号 `,` 隔开 + +待确认: +- 是否兼容中文逗号、空格、换行等分隔符 + +### 16 + +## 原始诉求(AutoRDO) + +**标题**:租赁合同等:审批状态旁增加当前审批人 + +**描述**: +租赁合同、提车应收款、还车应结款、租赁业务台账(材料称「租赁账单」)在审批状态/状态右侧增加列「当前审批人」,显示工作流当前审批操作人姓名 + +待确认: +- 多人并行审批时是列出全部当前处理人还是仅主处理人 + +### 17 + +## 原始诉求(AutoRDO) + +**标题**:车辆管理:客户名称后增加项目名称字段 + +**描述**: +车辆管理列表在「客户名称」字段后增加「项目名称」 + +待确认: +- 「项目名称」数据来源及与客户/合同的关联规则 + +### 18 + +## 原始诉求(AutoRDO) + +**标题**:车辆调拨:增加车牌号与停车场模糊查询 + +**描述**: +调拨管理(车辆调拨)增加车牌号、停车场模糊查询 + +待确认: +- 无 + +### 19 + +## 原始诉求(AutoRDO) + +**标题**:客户管理与供应商管理:表单页新增保存功能 + +**描述**: +客户管理、供应商管理表单页新增保存功能 + +待确认: +- 「保存」是草稿保存、校验后保存还是替代原提交 +- 客户管理与供应商管理规则是否完全一致 + +### 20 + +## 原始诉求(AutoRDO) + +**标题**:交车管理:交车时间精确到分钟 + +**描述**: +交车管理中,交车时间精确到分钟 + +待确认: +- 展示、录入、筛选是否均精确到分钟 + +### 21 + +## 原始诉求(AutoRDO) + +**标题**:车辆管理:登记所有权改为多选下拉并支持模糊查询 + +**描述**: +车辆管理中「登记所有权」筛选改为下拉筛选(多选),且支持模糊查询 + +待确认: +- 无 + +### 22 + +## 原始诉求(AutoRDO) + +**标题**:年审记录:年审待办优化 + +**描述**: +年审管理(年审记录)中年审待办优化 + +待确认: +- 具体优化点(列表、提醒、完成率、OCR 刷新等)材料未展开,需补充 + +### 23 + +## 原始诉求(AutoRDO) + +**标题**:车辆管理:运营城市改为取 GPS 最后定位省市 + +**描述**: +车辆管理中「运营城市」修改为获取 GPS 最后定位的省市 + +待确认: +- 无定位数据时的回落规则(空/原手工值/提示) + +### 24 + +## 原始诉求(AutoRDO) + +**标题**:停车场管理:去除审批工作流,新增编辑无需再审批 + +**描述**: +停车场管理去除审批工作流,新增、编辑不用再走审批 + +待确认: +- 与第 27 条是否为同一诉求(第 27 条写取消新增/编辑/删除审批) + +### 25 + +## 原始诉求(AutoRDO) + +**标题**:交车管理:列表数据及筛选与字段逻辑重构 + +**描述**: +交车管理列表数据及筛选、字段逻辑重构 + +待确认: +- 重构范围与目标(字段清单、筛选项、性能或规则变更)需补充说明 + +### 26 + +## 原始诉求(AutoRDO) + +**标题**:事故管理:新增批量导入并调整下拉枚举 + +**描述**: +事故管理新增批量导入功能,并修改下拉选项枚举值 + +待确认: +- 批量导入模板字段与去重规则 +- 下拉枚举变更的具体新旧对照 + +### 27 + +## 原始诉求(AutoRDO) + +**标题**:停车场管理:取消新增编辑删除审批 + +**描述**: +停车场取消新增、编辑、删除的审批 + +待确认: +- 与第 24 条是否合并为一条(24 未写删除,27 含删除) diff --git a/.tmp-ds-h5-simulator.png b/.tmp-ds-h5-simulator.png new file mode 100644 index 0000000..dd95d69 Binary files /dev/null and b/.tmp-ds-h5-simulator.png differ diff --git a/.tmp-ds-mobile-nav-dark.png b/.tmp-ds-mobile-nav-dark.png new file mode 100644 index 0000000..54e0918 Binary files /dev/null and b/.tmp-ds-mobile-nav-dark.png differ diff --git a/.tmp-ds-mobile-nav-light.png b/.tmp-ds-mobile-nav-light.png new file mode 100644 index 0000000..14dc941 Binary files /dev/null and b/.tmp-ds-mobile-nav-light.png differ diff --git a/.tmp-ds-mobile-nav.png b/.tmp-ds-mobile-nav.png new file mode 100644 index 0000000..54e0918 Binary files /dev/null and b/.tmp-ds-mobile-nav.png differ diff --git a/.tmp-ds-showcase-badges.png b/.tmp-ds-showcase-badges.png new file mode 100644 index 0000000..f8e7e1f Binary files /dev/null and b/.tmp-ds-showcase-badges.png differ diff --git a/.tmp-ds-showcase-buttons.png b/.tmp-ds-showcase-buttons.png new file mode 100644 index 0000000..5e76393 Binary files /dev/null and b/.tmp-ds-showcase-buttons.png differ diff --git a/.tmp-ds-showcase-composite-light.png b/.tmp-ds-showcase-composite-light.png new file mode 100644 index 0000000..c2d5870 Binary files /dev/null and b/.tmp-ds-showcase-composite-light.png differ diff --git a/.tmp-ds-showcase-composite.png b/.tmp-ds-showcase-composite.png new file mode 100644 index 0000000..1a82457 Binary files /dev/null and b/.tmp-ds-showcase-composite.png differ diff --git a/.tmp-ds-showcase-controls-light.png b/.tmp-ds-showcase-controls-light.png new file mode 100644 index 0000000..de6409c Binary files /dev/null and b/.tmp-ds-showcase-controls-light.png differ diff --git a/.tmp-ds-showcase-controls-optimized.png b/.tmp-ds-showcase-controls-optimized.png new file mode 100644 index 0000000..810d870 Binary files /dev/null and b/.tmp-ds-showcase-controls-optimized.png differ diff --git a/.tmp-ds-showcase-controls.png b/.tmp-ds-showcase-controls.png new file mode 100644 index 0000000..810d870 Binary files /dev/null and b/.tmp-ds-showcase-controls.png differ diff --git a/.tmp-ds-showcase-custom-datepicker-popover.png b/.tmp-ds-showcase-custom-datepicker-popover.png new file mode 100644 index 0000000..b4bfe2f Binary files /dev/null and b/.tmp-ds-showcase-custom-datepicker-popover.png differ diff --git a/.tmp-ds-showcase-custom-datepicker.png b/.tmp-ds-showcase-custom-datepicker.png new file mode 100644 index 0000000..c9f5b21 Binary files /dev/null and b/.tmp-ds-showcase-custom-datepicker.png differ diff --git a/.tmp-ds-showcase-custom-daterangepicker.png b/.tmp-ds-showcase-custom-daterangepicker.png new file mode 100644 index 0000000..2e6f968 Binary files /dev/null and b/.tmp-ds-showcase-custom-daterangepicker.png differ diff --git a/.tmp-ds-showcase-dark.png b/.tmp-ds-showcase-dark.png new file mode 100644 index 0000000..4f9a80c Binary files /dev/null and b/.tmp-ds-showcase-dark.png differ diff --git a/.tmp-ds-showcase-h5-real.png b/.tmp-ds-showcase-h5-real.png new file mode 100644 index 0000000..65de908 Binary files /dev/null and b/.tmp-ds-showcase-h5-real.png differ diff --git a/.tmp-ds-showcase-h5-sim.png b/.tmp-ds-showcase-h5-sim.png new file mode 100644 index 0000000..2b271c3 Binary files /dev/null and b/.tmp-ds-showcase-h5-sim.png differ diff --git a/.tmp-ds-showcase-light.png b/.tmp-ds-showcase-light.png new file mode 100644 index 0000000..e3608b0 Binary files /dev/null and b/.tmp-ds-showcase-light.png differ diff --git a/.tmp-ds-showcase-single-input-dual-calendar.png b/.tmp-ds-showcase-single-input-dual-calendar.png new file mode 100644 index 0000000..e5e21bf Binary files /dev/null and b/.tmp-ds-showcase-single-input-dual-calendar.png differ diff --git a/.tmp-ds-showcase-top.png b/.tmp-ds-showcase-top.png new file mode 100644 index 0000000..3373515 Binary files /dev/null and b/.tmp-ds-showcase-top.png differ diff --git a/.tmp-h5-vehicle-dark.png b/.tmp-h5-vehicle-dark.png new file mode 100644 index 0000000..b2dd11c Binary files /dev/null and b/.tmp-h5-vehicle-dark.png differ diff --git a/.tmp-h5-vehicle-kanban.png b/.tmp-h5-vehicle-kanban.png new file mode 100644 index 0000000..5e80331 Binary files /dev/null and b/.tmp-h5-vehicle-kanban.png differ diff --git a/.tmp-h5-vehicle-list.png b/.tmp-h5-vehicle-list.png new file mode 100644 index 0000000..3b0b9e8 Binary files /dev/null and b/.tmp-h5-vehicle-list.png differ diff --git a/.tmp-h5-vehicle-workbench.png b/.tmp-h5-vehicle-workbench.png new file mode 100644 index 0000000..e202d36 Binary files /dev/null and b/.tmp-h5-vehicle-workbench.png differ diff --git a/.tmp-inspect-form/extract.txt b/.tmp-inspect-form/extract.txt new file mode 100644 index 0000000..8733d8e --- /dev/null +++ b/.tmp-inspect-form/extract.txt @@ -0,0 +1,1329 @@ +===== PAGE 1 ===== +附件 4:车辆交接验收表 +车辆交接验收表 +车辆交接日期220 、 b|b +ET + iz -20K5- {Doo2 + | +整车外观状况《与公告目| 【人 7 | +录样车相同) | +车辆照片左前45”角 +漆面状况 wv 喇只状况 Va 一张,右后4。 m—| LA +aK) + +_. 交付车续航电量 95%以| 二 /- +er ee al Ar +Ee ML lm | V Ee a + +16 + +===== PAGE 2 ===== +a 7 +CC +frame —[ yaw ie RE20R +ee kh hh +Ca +ee | +ee Ze zh +Ze yp +[TREE EAI + A + a +a Ys +ewe vf frente tt +扳手) ) +a +Ce a | lw | +Cr +oe | ME fom oT vt TT +档) 状况 J +17 + +===== PAGE 3 ===== +415108 +[map te fT +jwvemre +Fil ls (Gs es +状况 +En +a +eT a — += i — 本 1 二 -! | += gy , an 1) = gy een +“ah 是 a “2 1 本 “| +Bee. Bae +bad Gt. Sera +m 9 Je +18 + +===== PAGE 4 ===== +Phe +了 +LB NT 2% Zar +各aa +ce eA & +~ von 负责人《签名), OP + if 区和电话。 [ob 06人| +确认上时间, ob. (.{b + +===== PAGE 5 ===== +附件 4:车辆交接验收表 +多 AG 25025 +车辆交接验收表 +车辆交接日期; aonb bb +基本信息 + 0 有 +[avaaw 有而 | ztaaag | foo, Al | hang —_[Tz20015-HD002. +交车及接车时待检验项目 +国人 +整车外观状况〈与公告目 +冷却风扇转动状况 供氢量 +ere ye | frmmawowe vv +| +车辆照片 (左前45”角 +湖面状况 UL 喇叭状况 Y K, wmas |, +aK) +交付车续航电量 95% PA +钥匙 (2 把) 把 汽车标识LOGO +A 本 lw zl | +16 + +===== PAGE 6 ===== +HT Ta dd Th | +am TY je | | +am | ai | ee | em Pd +aa | 7 [fee | ll | | +sam | rm | x | | | | +[TI +am em zl +am | “Bl PSC~*~dSCS*C‘iSC‘“‘*d +aa | “| lm | x | | Sid | +aaa | 之 meal X | | | id +fame Zi mw lx | +av ee xl | +WORF (13-15 号开口 +oo +A +|和一一 +Frama [vy | aa | ¥ | | | +档位变换《前进档、倒 +标准量冷却液 +17 + +===== PAGE 7 ===== +NG 38515 +meemme fe PP +jammer fv | +eet] TE TT +状况 +区 +mm | /| | | | | | | | + | +国共 ween. 7 +ata 7? ae ow‘ _ | += arp mn = fs m +mt 一 an - ® | +Been Bae. +LE cn Ee Dead +ee ee +图合 + N Pox oo | | +18 + +===== PAGE 8 ===== +各注说明状况, + +pacts, ph, 1 litem +车方负责人 cet A 车方负责人《签名): 330A +联系电话, yig6a561 联系电话; (20624662 |] +aun. LEC musta: 596 6 Lf + +===== PAGE 9 ===== +附件 4:车辆交接验收表 +车辆交接验收表 +swscmaim 22) 6.6 HF 6-2T}0 + YN Ar [mamsoens MTpmeyg-Hpm +交车及接车时待检验项目 +整车外观状况《与公告目 + ° 冷却风扇转动状况 供氧量 28 fo +录样车相同) +J vA EAN Feit 45° ff +TTI, 喇叭状况 一张,右后 44。角一 +张) +交付车续航电量 95%以 一 +钥匙 (2把) 汽车标识 LOGO +Se 2 ce F S| +[na | VL ae eae | | +16 + +===== PAGE 10 ===== +femme Te TY [mn | +1 am am +[ina | ao we 7 | +[aaa | mw | | | | +[sam Vim [x [| | | | +a Vi am | | | | | +TV | +as TV | mw ww | | +a TV mw 7 | | | | +aaa | Vf os | | | | | +as 7 mw lx | | | | +ee imme | | | | | +活动扳手〈13-15 号开口 +room [rm +IT | [mee TT +[ER | V | | | | | +ET区 +档位变换《前进档、倒 +17 + +===== PAGE 11 ===== +np G 21% +[meme | VP +a GO +(00 5 PBL AL a BB AP BLA 加 、, +tn AMe-. \akerg +Jeormrme | VM TT +Ea DQG +warn .. wasn .. + on 于 © mm += ' al | see 是 | +Bee Been | +me freee fue fre fae fas +图例 +ee +18 + +===== PAGE 12 ===== +32 区 +Bevan cw Joolh 和rw (签名): zany +确认时间, wh ble wn: 2526 . 1 + +===== PAGE 13 ===== +附件 4车辆交接验收表 +车辆交接验收表 +车辆交接日期, 多 WELT +基本信息 +接车单位名称; 交车时仪表里程数 发动机号/电机号 TZ120K4—-hpoor- | +交车及接车时待检验项目 +ERIM CGA A +° 冷却风肩转动状况 ee ZL +录样车相同) +V 车辆照片 (左前45”角 +洪面状况 喇叭状况 一张,右后45。角一 +张) +交付车续航电量 95%以 th +钥匙 (2把) 汽车标识LOGO +16 + +===== PAGE 14 ===== +rammed +EO 2 +ra [| 7 aa [7 | [ames | | +En ZO a +fear [| % | |rmme +f * [| | +i +women | VS | ome +EO CO +Ea CO +Ce | [awww XTC +am | V[ |ararsemmn | x | || +i +forme | V | _awwe S| XT TSC SC +Ea A +vane fv | ere +刹车系统状况 ren) ; + +Frwnomn | 7 [wwe iS +aa | V | [amen TV +ae | | aa [| V >| | sd +foe |V | fom PM | | + +VY 标准量冷却液 +17 + +===== PAGE 15 ===== +4NG1bL 16 +av dr +jews PV +Fea +状况 +in | V GO +pmp IIV | GO + +warn... wasn .. +— '- ee = '- ae +于 @ an = 加 Fl +“we: “1 si: “| +Been. Been. +_ 上 mm -> Ia |。 +18 + +===== PAGE 16 ===== +BR OIE Rah STR, +— cae Jah Uf Shana (签名): ad + +mans (UbabOV 联系电话。 [La262"662 ] + +确认时间, yabb.[6 mania: Jorl [AL + +===== PAGE 17 ===== +附件4:车辆交接验收表 +车辆交接验收表 +和六六BA hGlofob +基本信息 +ET [CT = lop Dogg +接车单位名称, 交车时仪表里程数 发动机号/电机号 全Zoo +交车及接车时待检验项目 +整车外观状况〈与公告目 +四 JS 冷却风户转动状况 供氧量 5 , +录样车相同) +车辆照片 (左前45”角 +滚面状况 喇叭状况 J 一张,右后 45。角一 +ik) +a 交付车续航电量 95%以 +Alle (2把) | Lg 汽车标识LOGO 1 Hh +16 + +===== PAGE 18 ===== +FT Ta Ta TZ T +a CE +fnew [| fame | dee +a YO +aa | Vo [rea -i | [|i id +7 +cA 7 CO +sm | av | | +wm xx | | +oa we | | 一 +am | mr ll | | +Var +| +刹车系统状况 =) + +Er A +[aa [Vo [mw ”一 +aa Cc | 一 +档位变换《前进档、倒 + +标准量冷却液 +oe | femme TY TT] +17 + +===== PAGE 19 ===== +$b [08 +[mep fw | +jaws EM TF +Fd Cd ee +状况 +1256D YY +[Mk | VY | +wasn... wasn. +oe fe? a a fats we. +村 了 mm 19 m +ee es = ar 1 = | +Been Been. +ee a ee +图例 +Po fe +18 站 + +===== PAGE 20 ===== +各注说明状况: 2G ZN Zhi , RPE tH, + +和 8%): BEA en 8%): 3340 +联系电话, [7662560 联系电话从 o)b2tb] +aust Jot 6.16 man: goof fff + +===== PAGE 21 ===== +附件 4:车辆交接验收表 +车辆交接验收表 +车辆交接日期了大、从 Ayr L 6169 +基本信息 +ses [emacs | Caufoxvyenczzrcev Baz 2xpo Log +BEML ER: SOE SA EEL 发动机号/电机号 Tz) LKNS—H) 007 +eo oe +HEAR, 528 +AU AED ART 供气量 和 +录样车相同) +J 车辆照片〔左前45* 角 +漆面状况 响只状况 一张,右后45。角一 +张) +; 交付车续航电量 95%以 y +WAR (2 把) | 用 汽车标识LOGO , (oy +16 + +===== PAGE 22 ===== +[em [TV Ta Odden VC +free am ae lv +foreman [ 7 [amma [fee V +fanmwne [VY [use CT TCP CdYCS*‘iSC‘*d +a +[了RAT +[7下/ +sa | YY | ww ll | | | +[am | | OT Td | | +aa | +am va lx | | +YL | | | | | | +活动扳手〈13-15 号开口 +oo +am | | ~ | | +A | -| mw lv | | | | +aaa | Ya Tv | | | CC*d +ae Y | femme TY | PT +档) 状况 +17 + +===== PAGE 23 ===== +FLY +[mpep we fT | | +[2 +2 +状况 +ap | | +mp DGD DD I +warn wean. +wil: | Jie: _! +S Gy 一-二部 = a +—_ 了 “1 = - “ +Been Been. +a re es +图例 +| +18 2 + +===== PAGE 24 ===== +备注说明状况, Zig BF KYM Zaye. FAIA +— cemAp ih Uf ena. ott, ELE + +联系电话, 166s 3 KE: ($22! 24662] + +确认时间: 20.6. le AB Ta dob L(t + +===== PAGE 25 ===== +附件 4:车辆交接验收表 , +车辆交接验收表 +车辆交接日期: 76.66 多/EU79 + USOXKYCCPER TO + eneys-HPo0) +RRR (SAGA VA - 9 +冷却风扇转动状况 供氢量 +JS 车辆照片 〈左前45”角 +洪面状况 喇叭状况 V 一张,右后 44"。角一 J +ak) +交付车续航电量 95%以 +[TCR +16 + +===== PAGE 26 ===== +[TY aa [rene Vd +mv am | [me lv | +0 +sea | rm [| [ *+| | +[RE +[7 +aa al | | | +sam | rm |X [| | | | +femme | | mol | | +am om +am mr IT | +° WORF (13-15 号开口 +ove TV ee +am iv [eT dT | | | +[wan |Y | ae vv | | | +Prmamnewne |Y | aa |V | | | | | +档位变换〈前进档、倒 +标准量冷却液 +17 + +===== PAGE 27 ===== +_ SAAD +CE GO +ED +ro +状况 +[tin EM YE +Ec a A DO +wae wasn += '- 7° ae -'--- an +5 i 1 eS rg i = ee c9 +wee: 1 = “ +Bee Bae. +a +图例 +a a ee +18 + +===== PAGE 28 ===== +FARE Neat EWE, RADI +—n caer eG yp Bric tio an + +联系电话: Jo 联系电话,“{姑026241 7 + +waste: Doh, 6 [k mam: 9,2 6.16 + +===== PAGE 29 ===== +附件 4:车辆交接验收表 +ANG OF . +车辆交接验收表 +车辆交接日期 257 b- 6/6 +基本信息 +交车及接车时待检验项目 +整车外观状况〈与公告目 detanicdeshit V +冷却风扇转动状况 供气量 +录样车相同) v - 7 +车辆照片〈左前45”角 +漆面状况 JS 喇叭状况 J 一张,右后 44。角一 U +张) +| re | | | ae Po | | +二 +16 + +===== PAGE 30 ===== +few am ym +fwanewnn [7 | amma [ y7 fame | Sid +aa | +[wa | vim lx +[7 +ze vv | am > | +aa | am | | +aa [VY | es ll | | i” +am | | mw lx | | | +sm vs 人 +活动扳手〈13-15 号开口 +oo +Sa |” | ls | | | | | | +0 +[rmawnniene |v | [wmmamre | | | | | | +eee | femme TT +档) 状况 +17 + +===== PAGE 31 ===== +ANG to +pm | a | | | | T | +mm | | | | | | | +侧防护围杠/后部护围杠 +状况 v +stem | | | | | | | | +amp [|/ | | | | | | | | + 1 » © 本 = ps 1 | +ex ff a FI = i 7 an +“al 一 an “a” . i" +Bee. Bae. +一 a Se +18 + +===== PAGE 32 ===== +ERK Le ALA AZ MEWS EAL + +amr ae Fp Lh an (签名); ay +联系电话, Mos 7 wai: [fy | 24st2] +wana — 206.6, Ib mania. = 72 4 . LU + +===== PAGE 33 ===== +附件 4: 车辆交接验收表 +HGrbI2O +车辆交接验收表 +车辆交接日期 Zezp, bf +基本信息 +sea jgztxpGDc3Fcev| Se BA hufols 7 +er Feet OH +整车外观状况“与公告目 +SY | 从井风内转动状况 Y | beaut CO +录伴车相同) +车辆照片〈左前45”角 +eH V | oc 一张,右后45*角一 we +LA +ak) +交付车续航电量 95%以 +钥匙 (2 把) | 汽车标识LOGO Vv +上 +16 + +===== PAGE 34 ===== +[Ta me +[TI +aa | | ve Ts | | | +aaa | | vim | x | | —*+d| +a +am mtv | | | +zs | va | | | +sam | mv +am +om | ~ | +5 ve | LT | | +活动扳手〈13-15 号开口 +Ce +mw [| v fw | +CL | | | | | | +Fromme | | ”| | | | | | | +多 +J 标准量冷却液 +档) 状况 +17 + +===== PAGE 35 ===== +QnGGo] v4 +[meen of YT +Er Oe GG +侧防护围杠/后部护围杠 +Fi +jeanne | +[WU TTP +We aen ae we 440 ie += 鄙避 mn a m + + a Ld ”, 外 1 - | + +Bee. Bae +由”一一一一一 + +图例 +ee a +18 + +===== PAGE 36 ===== +i 去 + +路车方负责人《签名): Ash PRET IA CEL): 34y/% +联系电话; ebooks 联系电话, [GD人2 | +确认时间, Lb. 616 确认时间, 5 DF } AL + +===== PAGE 37 ===== +附件 4:车辆交接验收表 +上 / 车辆交接验收表 +车辆交接上:2 jp] (6 +er a cc 2 7 INC +交车及接车时待检验项目 +整车外观状况〈与公告目 UL eases iia +y 风 动状 ‘3 +录样车相同) “ } +车辆照片 〈左前45”角 +浴面状况 J | sina J |—%, tm ase 角一 Be +张) +交付车续航电量 95%以 +钥匙 (2把) 汽车标识LOGO / G 7 | += | +rem [fo IE本7 +| +16 + +===== PAGE 38 ===== +[Fa Te Ta +[7 +[7 +aa fT 7 mee | | | | +Cc +mm 7 em lx | +[7 +er 7 SCdCC=<‘~dr:CSCS:S +aa [| yf | | | +oa | | | rose x [ [| | _—*d +ax +as | +活动扳手〈13-15 号开口 +oo +a +ce | | ”la | | | | | | +[aaa | | 7 | | | | | | +owe | fy femme OT | +档) 状况 +, 17 + +===== PAGE 39 ===== +Ge, PNEN6I74 +mews of oy | UTE +a A +侧防护围杠/后部护围杠 +状况 ¥ +in | | | | | +Ea A +wean .. warn .. += - a = '--- a +S ay on i fl mm +ai i 了 1 —_ i! - ' q +Bee Bee. +fem [espe fee wf _tes_ +图例 +a +18 + +===== PAGE 40 ===== +Ve a + +ETRRA BE: a Gi HEH IHA CER): 303th +联系电话, bbb) 联系电话, )S2024T2] +确认时间, Ww 6. [5 mam: 5 OG, 化 + +===== PAGE 41 ===== +附件 4:车辆交接验收表 +4 NGS tof 车辆交接验收表 +车辆交接日期, 20 26-6 16 +基本信息 +OCR | AOL + Zaea | 97 aR | [rz 2206-H 092 +交车及接车时待检验项目 +整车外观状况〈与公告目 +LA | tomes Y | 供氧量 Ls +录样车相同) +车辆照片 〈左前45”角 +PRT J | mist / 一张,右后 45"角一 V +张) +交付车续航电量 95%以 +HT7Zm 7 ll +16 + +===== PAGE 42 ===== +FE Ta AT +peewee | +[am | jos CS +aa 7 ww xl | | +[7 +a OPC +fanned yom eT TSSC*dEC(‘LC#*d +aa | la lv | +am | | am | | | | +am | 7 esp | | | +am OC +ax +、 J 活动扳手〈13-15 号开口 +oo +[7有 +aaa | | /am | | | +[aaaae | | 7 wk | | | | | +ee LS femme TTT +档) 状况 +17 + +===== PAGE 43 ===== +@ NGS4199 +(wees 7 dd +mi DC OO +aves Pot | +状况 +EE7 fT +warn .. wasn .. +oe att ae — '- —— +全 © i cs & i +B 1] i 上 人图 ae +: aim 1 7 一 ' “a t 7. 一 ' +Bee. Bee. +| +_ 上 oo +18 + +===== PAGE 44 ===== +中 、 + +SEIT CBR): aie Povnnr (BR): EN +WR is: 17666005613 mamis: §— [C2l2eto7 +wun Doh, f. it mIARH: D396 -LO + +===== PAGE 45 ===== +附件 4:车辆交接验收表 +MWGr04| +车辆交接验收表 +车辆交接日期, 2026. b, |b +fence OCT +fanenee. FE Perwoaman | (os on [xsmonno —izza0%s-Haoa +交车及接车时待检验项目 +整车外观状况《与公告目 | ule +风衣转动状况 +录样车相同) Ze +车辆照片(左前45"角 +PRIN VAL LA |一三有后45" 角一 Y +张) +交付车续航电量 95%以 +钥匙 (2把) 1 汽车标识LOGO V rg +上 +> +16 + +===== PAGE 46 ===== +fawn [Vm +am 7 ae +am | aa > ee +aa 7 | 一 +am A +am | 2 om | +[ET +Ea A +aa am dS | +am | well +[CT +5 ax一 + 活动扳手 〈13-15 号开口 +刹车系统状况 A _ 义 +EO +en > an | +aas | me [| | | |) J +owe | fmm TT TT +标准量冷却液 +档) 状况 +17 + +===== PAGE 47 ===== +4G 306] +jmiewemaee Pe +ai | “| | | | | | | +侧防护围杠/后部护围杠 Lv +状况 +EA +[aaa mee | +warn .. waren .. +-'- 7" en Pe we, +对 ae an Ey am +amt . ' io ie 区 +Been Bee +es +图例 +[Co | +18 + +===== PAGE 48 ===== +ee a +SERIA CL): LE KET RHA CH): war +联系电话, [LL loaKLD 联系电话, | go242HE62) +确认时间, 220LN 确认时间, 2024, 1 +19 ™ + +===== PAGE 49 ===== +附件 4:车辆交接验收表 +车辆交接验收表 +车辆交接日期 +) 基本信息 +enna a | C—O +Peo ou +整车外观状况《与公告目 +冷却风户转动状况 供气量 +En +车辆照片(左前45” 角 +HET v4 AIR a 一张,右后4。 朋一 +张) +交付车续航电量 95%以 +钥匙 (2把) 汽车标识 LOGO +wow |B | fren fe fA ee || +16 + +===== PAGE 50 ===== +IT aa iT aa Te +ao me +aa | aa ec dP +a a +ee 了 mm al | 一 +[2 +aa am |一一 +aa [7 am | Sr +aa | 7 | oa |g [| «+P Sd +am | foe 和 | | +mmm vf ere | P| +RF) +GZ |有 一 +aa | | | 一 | +档位变换〈前进档、倒 +oe | femme fw | +17 + +===== PAGE 51 ===== +Nv [Dose +[meemme | ow TC | +Fe | +(0) Bap FAL a OP LAL +状况 vv +a +jum | Z| | +warn. wen. += '--- ae. a - ae += ath 1 pu = 串; . ; mm +_ 了 ”, —_ a +Bee Bee. +a +图例 += +; 18 + +===== PAGE 52 ===== +各注说明状况: FRM WALR , CAE + +er (签名): doh if Cees (签名); EN +wemmis. (1666 056 1 "Tei 0202化2] +确认时间; wb.(6 wun ff if + +===== PAGE 53 ===== +附件 4:车辆交接验收表 +车辆交接验收表 +ewscncim BOB 2026 6. [( $4613 (5 +世人 +[CR +交车及接车时待检验项上 +整车外观状况《与公告目 +录样车相同) +V V/ 车辆照片 〈左前45" 角 +漆面状况 喇叭状况 —ik, Bia 45° fa— +张) + V/ 交付车续航电量 95%以 h +钥匙 (2 把) It | 汽车标识LOGO | 上 x bb +a SC +16 + +===== PAGE 54 ===== +Ta Tv Team Tv T +[CC +away ee +aa va | | +see [V[ fom Xf SiS SS +Er +am ve +aa On +aa TV mw +ae | +a +am Var +ee TY Free +刹车系统状况 扳手) + +Ba [fae wu +[am 7 aa | | +[aaamem | v | aa | | | +owe | | fom le TTT + +标准量冷却液 +17 + +===== PAGE 55 ===== +人ON| +[mep TV [| | | +[pa TMP +便防护围杠/后部护围杠 4 > 、 +de MRE, EXE Ice , +ecw TMP +[pmp | VPP +wasn wt we 640 5 di +“a ea ie = to ~ +Been! Been, _ +a ae +图例 +es + +===== PAGE 56 ===== +Be RN +到 到 让 Se += Hi > = +woo ge & +S 下 BS 下 +SS S~ 好 +、 S +mn SS += = +ne +boa +& x & +3 ES ae +_ & +Ss g & +_ ~~ 4 & +© me roy +oan~ ooh + +===== PAGE 57 ===== +附件 4:车辆交接验收表 +8 5W 2983 +车辆交接验收表 +车辆交接日期: 2p Z, +基本信息 +ET 77 + 站775 24 [ransmns [22006 Dpo? +整车外观状况〈与公告目 V ‘cua | i eat | p I 4 +录样车相同) | IN +车辆照片 (左前45” 角 +漆面状况 V 喇叭状况 / —itk, Hi 45° fa— VA +张) +交付车续航电量 95%以 +foe [tie | A fromm | gl +mw 7 >] ww lv | +16 + +===== PAGE 58 ===== +Fw Te ree [IT +pot am am |之 +[CR +mw xl | | +aa | vv im xy TTCUdTCCSdSSCtC* +YC +pene rms SCdSC~=‘iSC*~*d +a A +aaa [| eam xl | | | +ae +am wm rr | | +Sm Oc | | +活动扳手 (13-15 号开口 +ov +faa | | sg | | | | +[ap | L | mm | | | | +区 +a P| femme fe | fT +7 + +===== PAGE 59 ===== +多AFN2927 +[mp | | | +| +便防护围杠/后部护转杜 +io +[566R | oT +fommone | EP +warn. wasn. +ba al mile J += 只 -* > ian 于 2 ef + 呈 4 a “a id 加 “| { +Been Bae. +m a +18 + +===== PAGE 60 ===== +Bae Bad, ROL + +wan ae DRY Porann (签名), aA +mais. (1666050) main 16952468) | | +确认时间; wih 6.16 man: 29) L + +===== PAGE 61 ===== +附件 4: 车辆交接验收表 +. 车辆交接验收表 +车辆交接日期,)D)C .C_ 人 . +ET fame NG ontnctice == BaASDAIG Pe. +feensee SSCS~*~*~*di | SSS [aM | +交车及接车时待检验项目 +整车外观状况《与公告目 +冷却风扇转动状况 供氢量 ¥h +AEN CART 45° 角 +IWR Va HOARE a 一张,右后465。和角一 +张) +交付车续航电量 95%以 +上《2把) 汽车标识LOGO 攻 +a Oa CO +16° + +===== PAGE 62 ===== +[Ta |和| wa iT +am | +[aa [fame LT ee +a ac "OC +oa | TI和| | +[7人2 +am | 0 +a a a +[CT +aa | 7 oa |g [| | —SidT SS +am | 和 +sk WORF (13-15 号开口 +[EN 轴玫 +PT +[aamm |二| ww |一 | | | +Prawn | | an | | | | | +ee | fm fe | TT +档) 状况 +17 + +===== PAGE 63 ===== +名NTNON +[mep | PT +[NMN +侧防护围杠/后部护围杠 +re Rel Ge (d +[zhin | +pi TE +wean .. ween .. += fp mn zt of m@ +— 1 * " — 加 | +Been Been. +; +m Pe +18 + +===== PAGE 64 ===== +ae +s & 这 a +S 普 > = +Pos 8 = +x & => Se +SN += = += 5 +Tv +& +ay 这 +2g 5 =e +ve f na +Y ys +Pe 立 = +3 —- & & = += wy +一 + +===== PAGE 65 ===== +附件 4:车辆交接验收表 +名RNG72 1 +车辆交接验收表 + +CO +et sea | bob oe | | + +整车外观状况〈与公告目 + +冷却风扇转动状况 供氧量 +车辆照片左前45”角 +漆面状况 喇叭状况 一张,右后44”角一 +a wv 张) / +钥匙 (2把) 汽车标识 LOGO SAREE S00. +: nia ae a > ash +Pewee ime +16 + +===== PAGE 66 ===== +Fw Ta Ta +[| am ee Yd +am [7 和 COdC*idSC“‘i‘“C*” +CR +mm [7 [femme al | | +[7 +em | mw | +aa [| am | | | | | +aa oO eolxX | 一 +am 一| am lx | | | +am Te x | | | +活动扳手〈13-15 号开口 + +oo +[Fa | js tem | +[wa |一| [meme | | | | | | +| paahtiia | -| Cc | | | | | +档位变换《前进档、倒 WA cy wey pn ype + +a 标准量冷却液 fo + +17 + +===== PAGE 67 ===== +Hobo? +[mcm | TTT +jews | +(0 SP LAL a BP BAL. a +状况 +世人 +RM oT Oo | TC +We a4 wi wee a +~~ ae ae: & ! 人 4 ad .! += a 1) ea 性 5) +— 1 bad “| 1 - | +Bee Beer +a +图例 +站 +18 + +===== PAGE 68 ===== +BAIR ERIN + +Haun ceo IAd Bonar 8%): 2 +联系电话; Ma 5b 联系电话, LZ.) 645] +确认时间, wtb le mana: §— 2.5 6, 6.16 + +===== PAGE 69 ===== +附件 4:车辆交接验收表 ‘ +车辆交接验收表 +segs OLD 、 b./b +FT PE + e245 -HDor +BEIIARS CGAE 冷却风扇转动状况 供气量 2 /4 +录样车相同) uv Via “ 0 +车辆照片〈左前45”角 +YETTA Wa 喇叭状况 J 一张,右后 45。角一 +张) +[nC YC +16 + +===== PAGE 70 ===== +[Few Ta Ta TD +| +[aa | | fam es ep +[4 +mm | rn | X | +mm | 7 om xl +am TmZ +aa DY +am oh mA 一 +[ia | wow和 | +am ”| +5 Te | 一 +woe TY] erent +刹车系统状况 vA ; +扳手) +ww Ze | 一 +[wma Za | | 一 +zaawmema | | wa | ll | | +owe "| ZT fem TT TT +\ 标准量冷却液 +档) 状况 +17 + +===== PAGE 71 ===== +[me | uv | | | | [| +wwe | ul | | [| [ff | +(00 5 FL AL Jes BB FB AL + ee ee ee eee + es es ee +6 | ow | TCE CU +wan .. wasn .. += 41。。 .! = 4 _! +于 ol 1 en = a 1 +— 1 电 “| —_ “ | +Bee 们ztm +PP +图例 +[oo | +18 + +===== PAGE 72 ===== +好/Al +Pevsner cee poh if bernns eed Hn + +wexonits (206 S63 wemoniss 169) beet a + +确认时间,7 62, LV 确认时间, pris ar + +===== PAGE 73 ===== +附件 4:车辆交接验收表 +@NGlin +车辆交接验收表 + +seimscrerim, 296.6,16 + CANGHNGGDOZ REVERS |B AASZADFPo) S12. +La ja | Jar 人 as | + +整车外观状况〈与公告 + +录样车相同) + +J 车辆照片 〈左前45”角 、/ +漆面状况 喇叭状况 J | nik) ase 角一 +张) +加 交付车续航电量 95%以 + +‘fille (2把) | 汽车标识LOGO wt |, 6 yh + +LV vv +16 + +===== PAGE 74 ===== +[Sm Ta 之 weam ThT +ii im nor | +[| om [dame +mm Tv +A a +sm |一 am la | | +am | | +saw | ja | +sam | nm |了| | | +aa |一| ol和al | | +am iT la | | +am | tlT T | + 、 Y WORF (13-15 号开口 + +femme Terentia TT +aa > lm | | +aam | 一 as |二| | | | +[zanmum [V7 | | |一| | | +eae | | fom TT TT + +7 + +===== PAGE 75 ===== +AG 14118 +[ppp |一| | | +局本7 +便防护围杠/后部护围本 +状况 -一 +ET +jxmmone | G@ ff +ean. wean.” +—~ t1 本 & =! = 1_ bpd ~! +于 是 > ea Fi ty +—_— 了 an “a 1 ‘7 “| +Been Been. + | +a +18 + +===== PAGE 76 ===== +worn OA NARS NUE, EUAN ER #4 el alas RS, MEE HS +EB ATA cee IA p BRA CR): DY + +联系电话, 170X28N 联系电话; (2074587 ] + +确认时间, rob. 1p wun, 226 ( lf + +===== PAGE 77 ===== +附件 4:车辆交接验收表 +人641 . +HG 车辆交接验收表 +车辆交接日期202 、0, [6 +[TREE和IT +7 08T An [anny 2ogrtDoo2 +交车及接车时待检验项目 +整车外观状况〈与公告目 y +° LA | resnmunseanine. | eee 47 +录样车相同) +车辆照片 (左前45。角 | +漆面状况 /|和 |i A aS" ft LA +张) +交付车续航电量 95%以 +上 +a A 7 +16 1 + +===== PAGE 78 ===== +[FE Ta Ta +a TO +fmewewnn [| 7 fame | | iawn «dS +mm | xz | +aa | rm | | | +a +a OAC YA +a OA A +seam | | am ”| | | | +am | | oa [| s«dY Sid +am 7 mw lv | | | +a OA +WORF (13-15 号开口 +owe |e fem +A +aa | | | 7 | | | +aaanmeg | | 7 aa | | | | | | +cee | LY femme Tf +ED 状况 +17 + +===== PAGE 79 ===== +Pq +[mep | | | | | a ee +Ea OG +Fae Gs Os Ge +状况 +sa 一二一一一一十一 +[mk | 人 +waen .. waen .. += 1_ o: * .! = 1_ j =! | +S os aes 2. im nr T | +whe: “| pr ee . +Been. Bee. +ee +图例 += +18 + +===== PAGE 80 ===== +Ka (签名): br hf dorama (签名); 2H +mares WDE mem [Gop bakeL 7] +mania: 2996,6.16 wan: 9.96, Ub + diff --git a/.tmp-inspect-form/extract_psm4.txt b/.tmp-inspect-form/extract_psm4.txt new file mode 100644 index 0000000..4d92fe4 --- /dev/null +++ b/.tmp-inspect-form/extract_psm4.txt @@ -0,0 +1,1744 @@ +===== PAGE 1 ===== +附件 4:车辆交接验收表 + +多402778 + +车辆交接验收表 +车辆交接日期220 、 b|b +基本信息 +交车单位名称, 车策型号 MN 40K GDC3 faye LBURA VOLS /234- +接车单位名称; 交车时仪表里程数 4297 公里 发动机号/电机号 [z22-0K5- Hboo2r +See SE AAO +项目 交车时状况 | 接车时状况 min 交车时状况| 接车时状况 项目 交车时状况 | 接车时状况 +监外观状况《与公告目| |/ ; +冷却风户转动状况 Wa eat / Vo +录样车相同) +车辆照片(左前45" fy +滚面状况 量具状况 UL 一张,右后6 m—| LA +张) +交付车续航电量95%以| <7 +tie (248) | 把 汽车标识LOGO yr . | SS if, +车辆底盘状况 \ mat Vv 整车配置表 VA + +16 + +===== PAGE 2 ===== +车辆内饰状况 V 灭火器 (1个) Va 用户操作手册 + +灯光状况 V 备胎 CD XxX 质量保修手册 +前后灯总成状况 V 点烟器 1个) | 保修IC 卡 +风挡玻璃状况 V 反光衣 Xx. +仪表盘功能状况 WA 千斤项 X +雨刊器状况 V ‘eonB LA x +FERPA V 车辆反光贴 允L +安全带状况 V = sn ean Wa +左右后视镜状况 V aa | Hy x +车内反光镜状况 V HE (27 SCF) xX +动力舱状况 V 备胎扬棒 x +空调状况 if 轴头保养板手 X +WORF (13-15 号开口 +刹车系统状况 V - x +车辆启动状况 V 挡车器 1人 +制动器助力性能状况 胎压检测值 u +方向盘助力性能状况 Vv 标准量去离子水 Vf +档位变换〈前进档、倒 J + +标准量冷却液 +档) 状况 J + +17 + +===== PAGE 3 ===== +四轮轮胎状况 Wa +挡泥板状况 v +Oa LAL Ba LL | ”UL僵 +状况 + +左右中后门状况 LA +货箱箱体状况 LA + +车辆外观检验 + +正常完好齐全 + +FRR + +v + +18 + +===== PAGE 4 ===== +备注说明状况: + +ERG) Tf lt 2%%, liZat + +条 BK): +名 Wh ak 8%): OP + +电话, we 联系电话; Me brhelr] +确认时间; Doh 6. vet ’ +HUN Joh, (.{b + +19 + +===== PAGE 5 ===== +附件 4:车辆交接验收表 + +多 AG 25025 + +车辆交接验收表 +车辆交接日期, aonb b. lb +基本信息 +SEAL 车辆型号 CH Sofoy Ven C33 FCEY 288 | PAA32n2.7P 015 1238 +接车单位名称, PAoG 交车时仪表里程数 202, 公里 发动机号/电机号 7z220X 5-Hb002. +交车及接车时待检验项目 +项目 交车时状况 | 接车时状况 aH 交车时状况“| 接车时状况 aA 交车时状况 | 接车时状况 +整车外观状况〈与公告目 +冷却风扇转动状况 供 +录样车相同) [一 4 “ 44 GZ +车辆照片(左前45”角 +漆面状况 LA 喇叭状况 Y K, wmas |, +张) +交付车续航电量 95%以 +钥匙 (2把) 把 汽车标识LOGO +| Vv 上 LY, + +车辆底盘状况 S ig WEAOHEA vi + +16 + +===== PAGE 6 ===== +车辆内饰状况 Vi KAS AY V 用户操作手册 + +灯光状况 LA 备胎 (1只) 04 质量保修手册 +前后灯总成状况 l 点烟器 (1个) LA 保修1C卡 +风挡玻璃状况 LA 反光衣 V +仪表盘功能状况 千斤项 x +雨天器状况 LA/ SCH x +座椅状况 UV 车辆反光贴 Y +安全带状况 vy 三角警示牌 / +左右后视镜状况 LA ae aly x +车内反光镜状况 V 钳子27 号轮胎扳手) 4 +动力舱状况 VA 备胎扬棒 x +空调状况 V 轴头保养板手 a +em V WORF (13-15 号开口 x +扳手) +车辆启动状况 Vi 挡车器 | 了 +制动器助力性能状况 V 胎压检测值 V +方向盘助力性能状况 V 标准量去离子水 Vv + +档位变换 (前进档、个| / +档) 状况 + +标准量冷却液 + +17 + +===== PAGE 7 ===== +ENG 3855 + +四轮轮胎状况 U +挡泥板状况 V +MBG mA, +状况 +左右中后门状况 VU +货箱箱体状况 +车辆外观检验 +T人三全= +图in + | ( 人 me 六 = +- is! se ' +ae ne +验车 验车 +正常完好齐全 Sub FPR 变形 脱落 + +图例 + +v N x Oo + +18 + +===== PAGE 8 ===== +备注说明状况: + +pacts, ph, 1 litem + +‘ & +车方负责人 (0 RANG 车方负责人《签名): 330A + +联系电话, yig6a561 联系电话; (20624662 |] +aun. LEC musta: 596 6 Lf + +19 + +===== PAGE 9 ===== +附件 4:车辆交接验收表 + +车辆交接验收表 +swscmaim 22) 6.6 HF 6-2T}0 +基本信息 +iin Fees CHMseHoXV YL DCI ECE 8% LEAS 24 PoL51 256 +接车单位名称: 交车时仪表里程数 44703 公里 发动机号/电机号 Tzm0X4—Hpood +交车及接车时待检验项目 +项目 交车时状况 | 接车时状况 mA 交车时状况“| 接车时状况 项目 交车时状况 | 接车时状况 +整车外观状况〈与公告目 + V 冷却风肩转动状况 v 供氢量 28 fo +录样车相同) +J vA ABR Cem 45° fi +HEARN 喇叭状况 一张,右后 45”角一 +张) +交付车续航电量 95%以 一 +ee (2把) 汽车标识 LOGO 1 +|把 , ¥ Oy! + +车辆底盘状况 V ps 为 整车配置表 + +16 + +===== PAGE 10 ===== +车辆内饰状况 v 灭火器 (1个) Vv 用户操作手册 + +灯光状况 V 备胎 LOD X 质量保修手册 +前后灯总成状况 Vv 点烟器 (1个) V 保修1C卡 +风挡玻璃状况 v mata Va +仪表盘功能状况 V 千斤顶 X +eB V seca x +HERP V 车辆反光贴 VY +安全带状况 V = fa Sank Vv +HEAL ORDL V a3 se V +车内反光镜状况 V wears | 人 +ADA V 备胎据村 X +空调状况 V 轴头保养板手 x +wens v4 WORF (13-15 号开口 入 +扳手) +车辆启动状况 V 挡车器 V +制动器助力性能状况 V 胎压检测值 Vv +方向盘助力性能状况 V 标准量去离子水 Vv +pee Cree ol] \/ 本 WA + +档) 状况 + +17 + +===== PAGE 11 ===== +合fgZ罗 + +四轮轮胎状况 VY +挡泥板状况 Vo +全防护转杠/后部护围枉| VA 卫 - + MR fo 元名硕 +左右中后门状况 V +货箱箱体状况 Vv +车辆外观检验 +wan .. ween. += ae ie _! = 1 PS 1 +于 qe een 1) = 4 +Ey 车项 1 ne 6 车项 +“as 1 mss: ”1 +Be wy +验车 验车 +正常完好齐全 缺少 arm 变形 +gpa 完 脱落 +v N x O + +18 + +===== PAGE 12 ===== +备注说明状况: + +DP Ltt, Pf A4VK, [12g Tbh, + +Brenan cw Joolh an BR): zany + +联系电话, | TGOSD nawis. (G2O4CCZ ) +确认时间, wh ble wines 7520 [le + +19 + +===== PAGE 13 ===== +附件 4:车辆交接验收表 + +车辆交接验收表 +车辆交接日期, 多 WELT +基本信息 +交车单位名称; 车俩型号 (Hwsouyyypl33FCEV | 各号 18143) 42SpoL§51029 +接车单位名称; 交车时仪表里程数 i | 公动机号电机号 TZ120K4—Hpoor- +交车及接车时待检验项目 +aA 交车时状况 | 接车时状况 项目 交车时状况,| 接车时状况 iA 交车时状况 | 接车时状况 +整车外观状况〈与公告目 + V 冷却风遍转动状况 | 供氢量 ZL +录样车相同) +V i” 车辆照片 (左前45”角 +漆面状况 喇叭状况 一张,右后45”角一 +aK) +/ 交付车续航电量 95%以 it +钥匙 (2把) 汽车标识LOGO +: We : Mh +车辆底盘状况 V pigs Xx Paes a + +16 + +===== PAGE 14 ===== +车辆内饰状况 l 灭火器 (1个) Vv 用户操作手册 + +灯光状况 V 备胎 CLD x 质量保修手册 +前后灯总成状况 Wa mas 48) v 保修IC卡 +风挡玻璃状况 LA 反光衣 N +仪表盘功能状况 Vv FAT Xl +雨天器状况 V 轮胎翘要 i +座椅状况 v 车辆反光贴 V +安全带状况 J = fy serie V +A BORK, V ae Ha x +车内反光镜状况 V 钳子〈27 号轮胎扳手) 人 +动力秀状况 V ‘i x +空调状况 V 轴头保养板手 +HERR V 活动扳手〈13-15 号开口 如 +KF) +车辆启动状况 WA 挡车器 vA +制动器助力性能状况 V 胎压检测值 V +方向盘助力性能状况 V 标准量去离子水 Vv + +档位变换《前进档、倒 VA + +标准量冷却液 V + +17 + +===== PAGE 15 ===== +四轮轮胎状况 V +挡泥板状况 V +wipe RAT) VK +状况 +左右中后门状况 V +货箱箱体状况 V +车辆外观检验 +a +ween.” += ernie, ya +| 1 +5 i) 一 5 +ait . 上 +we i +又车 验车 +正常完好齐全 ie HR 变形 脱落 + +J N 、 x 四 ‘e + +18 + +===== PAGE 16 ===== +备注说明状况; 2hry OLE. Ve Reah VE THe + +Be a +MET RIA cae Jah Uf 接车方负责人《签名); ad + +mans (UbabOV 联系电话。 [La262"662 ] + +确认时间, a,6.16 确认时间: dol Ld + +19 + +===== PAGE 17 ===== +附件 4:车辆交接验收表 + +车辆交接验收表 +和六六BA Bhelogob +基本信息 + — CHIN oak CYGDCIBECEY | #988 L69N324 22Pol2102] +接车单位名称: 交车时仪表里程数 扣好2 公里 发动机号/电机号 全Zoo2 | +交车及接车时待检验项目 +mA 交车时状况 | 接车时状况 项目 交车时状况“| 接车时状况 项目 交车时状况 | 接车时状况 + +整车外观状况〈与公告目 / +GA 冷却风户转动状况 供氢量 I + +录样车相同) +V/ / 车辆照片(左前45" 角 + +潜面状况 喇叭状况 一张,右后 44”角一 +张) + +交付车续航电量 95%以 + +ke (2把) | 汽车标识LOGO hy +攀 1 a +WA + +车辆底盘状况 V ia x. 整车配置表 + +16 + +===== PAGE 18 ===== +车辆内饰状况 WA 灭火器 (1个) 一 用户操作手册 V4 + +灯光状况 备胎 (1喉) Vy 质量保修手册 Vv +前后灯总成状况 点烟器 (1个) V 保修IC卡 V +风挡玻璃状况 VC 反光衣 xl +仪表盘功能状况 VY TAT * +两天器状况 VA seaman y +座椅状况 V 车辆反光贴 V +安全带状况 Vv 三角警示牌 VA +FA AR V4 eet x +车内反光镜状况 \ 钳子 27号轮胎扳手) X +ADM. / 备胎据村 M +空调状况 V 轴头保养板手 Xl +HEGRE. WA WORF (13-15 号开口 x +扳手) +车辆启动状况 Vv 挡车器 x +制动器助力性能状况 NA 胎压检测值 +方向盘助力性能状况 vw 标准量去离子水 Vv + +档位变换 (前进档、合| 1/7 +BD 状况 + +标准量冷却液 VJ + +17 + +===== PAGE 19 ===== +$A b |080b + +四轮轮胎状况 Vi +挡泥板状况 S +全防护围本/后部护围本| AAA +状况 +左右中后门状况 Vv +货箱箱体状况 +车辆外观检验 +他 +oe +ween .. +™= | Por 少 1 +2a 7 aan +| 有 +Fy Es +& i one 3 +“as 一 +ae-------ee +验车 验车 +正常完好齐全 aed 开裂 变形 Ie + +图例 +V N x O〇 e + +18 + +===== PAGE 20 ===== +各注说明状况:2Z忆林 ZN 4 8 RPE TH, + +六 wm (签名), BEA Goran BK): 3340 + +联系电话, [7662560 联系电话, J€oJ626¢61 +aust Jot 6.16 man: goof fff + +19 + +===== PAGE 21 ===== +附件 4:车辆交接验收表 + +车辆交接验收表 +车全交接昌期了信人、人 Ayr L 6| 《9 +基本信息 +交征单位名称, Fees CHM pefo KY GDC2 ZF CEV | 228 LB4n 724 2202 +接车单位名称; 交车时仪表里程数 £24, 公里 发动机号/电机号 Tz72o一由go一 +交车及接车时待检验项目 +oi sere, | 接车时状况 tA 交车时状况“| 接车时状况 项目 交车时状况 | 接车时状况 +整车外观状况〈与公告目 +冷却风记转动状况 供气量 和 +录样车相同) +J 车辆照片(左前45* 角 +潜面状况 喇叭状况 一张,右后 45。角一 +张) +交付车续航电量 95%以 +ile (2把) fi 汽车标识LOGO Vv . - 4 1% + +车辆底盘状况 i 为 整车配置表 Vv + +16 + +===== PAGE 22 ===== +车辆内饰状况 \/ 灭火器(1个) x 用户操作手册 V +灯光状况 备胎《1 只) 了 质量保修手册 V +前后灯总成状况 Y 点姻器〈1个) X 保修IC卡 V +风挡玻璃状况 v 反光衣 V +FEAT HEAL FT 74) +ERB V sonata xX) +HERP V 车辆反光巾 V +安全带状况 V =f ean Vv +左右后视镜状况 V Lue 为 +车内反光镜状况 Vv 钳子 〈27 号轮胎扳手) +AT MAR V eA X +空调状况 Vv 轴头保养板手 + +、 / WORF (13-15 号开口 +利车系统状况 _ x +车辆启动状况 V 挡车器 Vo +制动器助力性能状况 胎压检测值 V +方向盘助力性能状况 V 标准量去离子水 Vv +档位变换 《前进档、个| AAA 国 V + +标准量冷却液 + +档) 状况 + +17 + +===== PAGE 23 ===== +FLY + +四轮轮胎状况 +挡泥板状况 机 +全防护围杠/后部护围本| {7 +状况 , +左右中后门状况 +货箱箱体状况 ¥v + +车辆外观检验 + +验车 验车 + +正常完好齐全 缺少 WR 变形 脱落 + +图例 +v N x O 四 + +18 + +===== PAGE 24 ===== +9 有417、 FAY. ABE DB + +\ + +aon (外 hi Bram, (签名), ELE + +联系电话, 166s 3 KE: ($22! 24662] +确认时间: 20.6. le AB Ta dob L(t + +19 + +===== PAGE 25 ===== +附件 4:车辆交接验收表 + +车辆交接验收表 +车辆交接日期: 76.66 多人FU797 +基本信息 +Se MORE: nice CHM SafoXk CDI ERS LEASH LU POLS. 266 +接车单位名称: 交车时仪表里程数 [$95] 公里 发动机号/电机号 Tz222\5-HPo 0) +交车及接车时待检验项目 +项目 交车时状况 | 接车时状况 项目 交车时状况“| 接车时状况 aA 交车时状况 | 接车时状况 +整车外观状况《与公告目 VA - 9 +冷却风肩转动状况 供氧量 +录样车相同) V Sf +JS 车辆照片 〈左前45”角 +漆面状况 喇叭状况 V 一张,右后45”角一 +张) +钥匙 (2 把) 汽车标识LOGO U SATE SSH ei +at | 杷 rik 上 ]6 + +车辆底盘状况 S 脚执 x 整车配置表 / + +16 + +===== PAGE 26 ===== +车辆内饰状况 灭火器 (1个) v |. 用户操作手册 V + +灯光状况 Vv ih 5D r 质量保修手册 V +前后灯总成状况 V 点烟器 (1 个) OY 保修IC卡 v +风挡玻璃状况 反光衣 v +仪表盘功能状况 V 千斤项 X +两天器状况 vw senna YX +PERPRDR VV 车辆反光贴 Vv +安全带状况 Vv 三角警示牌 Vv +左右后视镜状况 Vv 3) ft X +车内反光人镜状况 Vv ar 27 号轮眙扳手) | X +HAM Vv 备胎据村 X +空调状况 Y 轴头保养板手 x +A/ WORF (13-15 号开口 +刹车系统状况 er) x +车辆启动状况 V 挡车器 V +制动器助力性能状况 V 胎压检测值 V +方向盘助力性能状况 Vv 标准量去离子水 Vv +档位变换《前进档、个| \/ vest vos \/ + +17 + +===== PAGE 27 ===== +四轮轮胎状况 VA +挡泥板状况 V +侧防护围杠/后部护围杠 Va +状况 +左右中后门状况 +货箱箱休状况 Sf +车辆外观检验 +— [a +wasn at” ween.” +_ = '- 7° Hip « cv- a +a 和 - | _ +—m I 7 ' —_ i! “7 于 +ae a eee we 4 +验车 验车 +正常完好齐全 seb FR 变形 脱落 +图例 +v N x fe) + +18 + +===== PAGE 28 ===== +备注说明状况: 28术 ZI Kn 、 3多 ERPGID + +—n (签名) SO Bric tio an + +WAS. Jo mami (976246607) +waste: Doh, 6 [k mam: 9,2 6.16 + +19 + +===== PAGE 29 ===== +附件 4: 车辆交接验收表 + +Aho OT + +车辆交接验收表 +车辆交接日期 257 b- 6/6 +基本信息 +交车单位名称: 车辆型号 CH InS0 to Ak PCO Cew | 车架号 CRNA 2720S) 20 F +『 PK: A by =时仪对 B im +wenvem. HAZY 交车时仪表里程数 YB eae 发动机号/电机号 (22S Upod +交车及接车时待检验项目 +项目 交车时状况 | 接车时状况 aH 交车时状况“| 接车时状况 SIA 交车时状况 | 接车时状况 +整车外观状况《与公告目 detanicdeshit V +PA RUHR, 供氧量 +录样车相同) v us v3 +车辆照片 〈左前45”角 +潜面状况 JS 喇叭状况 |一张, 右后45”角一 LU +张) +钥匙 (2把) ) 汽车标识 LOGO 这 Wan +二 + +车辆底盘状况 J 脚执 WA 整车配置表 wa + +16 + +===== PAGE 30 ===== +车辆内饰状况 4 灭火器(1个) 2 用户操作手册 + +灯光状况 ‘li AR Vv 质量保修手册 +前后灯总成状况 \ 点烟器 (1个) 保修IC 卡 +风挡玻璃状况 Vi 反光衣 Yo +仪表盘功能状况 J 千斤顶 X +两副器状况 v 轮胎普要 x +HERP 28 Fup he as did | 和Et 过 +安全带状况 WA 三角警示牌 - +左右后视镜状况 v 牵引拖多 V +车内反光镜状况 V 钳子〈27 号轮胎扳手) +动力舱状况 vv 备胎扬棱 K +空调状况 V 轴头保养板手 人 +peer J WORF (13-15 号开口 人 +扳手) +车辆启动状况 V 挡车器 +制动器助力性能状况 7 胎压检测值 V +方向盘助力性能状况 V/ 标准量去离子水 / +档位变换《前进档、合| ,, sieve tom J + +档) 状况 + +17 + +===== PAGE 31 ===== +ANG to + +四轮轮胎状况 v +挡泥板状况 J +便防护围杠/后部护围杠 2 +状况 +左右中后门状况 4 +货箱箱体状况 J +车辆外观检验 +T人-二全一 人- - =~ -=多en +warn .. wae .. += i! = © 本 —_ es .! +30) i an FI = fi ees +2 hie 车项 ' DE Fe fi 车项 , +se -- - Bs Bales ‘a +Pere “1 = “1 += Ga a +B 2 <- ee no ---ee +验车 验车 +正常完好齐全 缺少 开裂 变形 Bue +图例 +v N x O e + +18 + +===== PAGE 32 ===== +AU TWAS, EGS Bie + +和 +车方负责 ae Fp Lh 各 (签名): ay + +wai: | 10 7 RA +确认时间, 2024 6.1 《 i = a +确认时间, = 2026. LU + +19 + +===== PAGE 33 ===== +附件 4: 车辆交接验收表 + +Ana eyo + +车辆交接验收表 +车辆交接日期 Zezp, bf +基本信息 +交车单位名称: 车辆型号 Un Sobe Ge p33 bce | PRS BTA baPols U7 +weemarem: Hf ply SAN ORLA TG ee T2209 xS- 1p v9 +er Feet OH +iH 交车时状况 | 接车时状况 mA 交车时状况| 接车时状况 aH 交车时状况 | 接车时状况 +整车外观状况“与公告目 +SY | 从井风内转动状况 Y | beaut CO +录样车相同) +车辆照片〈左前45”角 +eH V | oc 一张,右后45* 角一 we +Y +张) +交付车续航电量 95%以 +钥匙 (2 把) | 汽车标识LOGO Vv +上 + +车辆底盘状况 V | ee UV | sesenettze WA + +16 + +===== PAGE 34 ===== +车辆内饰状况 VY lRRB at J 用户操作手册 + +灯光状况 hau hid PE) 备胎 《1 只) y 质量保修手册 +前后灯总成状况 V 点烟器 (1 个) Vv 保修IC 卡 +风挡玻璃状况 v/ 反光衣 X +仪表盘功能状况 V FAT X +雨刊器状况 V 轮胎竹棍 X +座椅状况 Vv 车辆反光贴 V +安全带状况 Vv 三角和警示牌 V +左右后视镜状况 Z 8 de v7 +车内反光镜状况 J 钳子 〈27 号轮胎扳手) 大 +动力能状况 J 备胎扬棒 X +空调状况 V 轴头保养板手 X +— y WORF (13-15 号开口 X + +扳手) +车辆启动状况 V 挡车器 +制动器助力性能状况 Vv 胎压检测值 v +方向盘助力性能状况 V 标准量去离子水 J +档位变换〈前进档、倒 en / + +J 标准量冷却液 + +档) 状况 + +17 + +===== PAGE 35 ===== +QnGGo] v4 + +四轮轮胎状况 Y + +侧防护围杠/后部护围杠 J +状况 + +左右中后门状况 +货箱箱休状况 7 +车辆外观检验 +全-一例一 全-一一全= +=e a. io +weaen .. wasn. +~~: 1 —™: .- ' +nom 1 en Ps a pais +@ hit 车项 ' DE % fit 华硕 if +入 | 区 国 ite Ly ro gy = a whe + a bs a — - | +‘ 4 1 +Bee. ee th orm acs oe +验车 验车 +本 正常完好齐全 ed WH 变形 脱落 +v N x O + +18 + +===== PAGE 36 ===== +备注说明状况: + +Ghat he. PHY TRTA,, + +Bann (签名): Ash nt (签名), 331人 + +联系电话; ebooks 联系电话, [GD人2 | +确认时间, Lb. bb 确认时间, 5 DF } AL + +19 + +===== PAGE 37 ===== +附件 4:车辆交接验收表 + +MNENG Tb + +车辆交接验收表 +车辆交接上:2 jp] (6 +基本信息 +交车单位名称; 车辆型号 Winere (osbeubke Use A342 1A S267 +接车单位名称, UHL 交车时仪表里程才 7Y的 公里。 | 发动机号/电机号 To2aycHpav + 交车及接车时待检验项目 +aH 交车时状况 | 接车时状况 aH 交车时状况“| 接车时状况 项目 交车时状况 | 接车时状况 +整车外观状况《与公告目 有 nen +? 风 BURY is +录样车相同) 人 } +车辆照片《左前45”角 +eR / | mona J |i. tas” H— - +张) +交付车续航电量 95%以 +Re (248) | 汽车标识LOGO / G7 +上 + +车辆底盘状况 J | noes J) secre 要 + +16 + +===== PAGE 38 ===== +车辆内饰状况 [|灭火器 (1个) WA 用户操作手册 + +灯光状况 / | @i AUR x 质量保修手册 +前后灯总成状况 J 点烟器 (1个) 4 保修IC 卡 +风挡玻璃状况 | Bob +仪表盘功能状况 / 千斤顶 x +ame J ene X +座椅状况 V 车辆反光贴 VA +安全带状况 V 三角警示牌 VA +左右后视镜状况 J ae aL ty v +车内反光镜状况 V 钳子 (27 号轮胎扳手) x +By ANODE V 备胎扬棒 x +空调状况 V 轴头保养板手 Xx +WORF (13-15 号开口 +HEAR Vv X +扳手) +车辆启动状况 V | 挡车器 +制动器助力性能状况 | 胎压检测值 v +方向盘助力性能状况 y 标准量去离子水 +档位变换《前进档、倒 wy, + +14 de | ates a0 + +17 + +===== PAGE 39 ===== +Bie 利信697 +Vv + +四轮轮胎状况 +挡泥板状况 / +40 5 AF ALP LA + +状况 +左右中后门状况 NA +货箱箱体状况 V + +车辆外观检验 + +和zs ee 5 +验车 验车 +正常完好齐全 缺少 开裂 变形 脱落 + +图例 +V N x O e + +18 + +===== PAGE 40 ===== +备注说明状况: + +Sram 8%): a Gi wn vse. Spgs + +联系电话, bbb) 联系电话, )S2024T2] + +确认时间; 2 6. [¢ mam: 5 OG, 化 + +19 + +===== PAGE 41 ===== +附件 4:车辆交接验收表 + +4 NGSte f 车辆交接验收表 + +车俩交接日期 22 26-6 16 + +基本信息 +交车单位名称; 车辆型号Cotg WHY 人|Dc33 FL EV 车架号 LB4A32A28PO-51247 +接车单位名称: 铬 yy 交车时仪表里程数 SIL 公里 发动机号/电机号 {Z 220% -Hb 002 +交车及接车时待检验项目 +项目 交车时状况 | 接车时状况 项目 交车时状况“| 接车时状况 项目 交车时状况 | 接车时状况 +整车外观状况《与公告目 +LA | tomes Y | 供氧量 Ls +录样车相同) +车辆照片 〈左前45”角 +汪面状况 J | J 一张,右后454。有角一 VY +张) +交付车续航电量 95%以 +ills (2把) / 汽车标识LOGO J : \¢ + +车辆底盘状况 |出天 J 整车配置表 V + +16 + +===== PAGE 42 ===== +车辆内饰状况 灭火器 (1个) ])- 用户操作手册 + +灯光状况 V 备胎 RD x 质量保修手册 +前后灯总成状况 Vv 点烟器 〈1个) 4 保修IC 卡 +风挡玻璃状况 | tte x +仪表盘功能状况 / FRM y +两天器状况 UZ sent +座椅状况 y 车辆反光由 aD +安全带状况 v SA tanh a +左右后视镜状况 Y 牵引拖多 a +AE GARR, y WF (27 号轮胎扳手) X +动力舱状况 V An x +空调状况 J 轴头保养板手 x +dexter J 活动扳手〈13-15 号开口 X +扳手) +车辆启动状况 J 挡车器 +制动器助力性能状况 J 胎压检测值 A +方向盘助才性能状况 J) 标准量去离子水 4 +档位变换 〈前进档、合 1 / +标准量冷却液 + +档) 状况 + +17 + +===== PAGE 43 ===== +@ NGS4199 + +四轮轮胎状况 +挡泥板状况 a +MBA GMA |, +状况 +左右中后门状况 ve +货箱箱体状况 Ava +车辆外观检验 +人-=人ce 从ao voy +eee (IR +wae... += > & i 上 +cm ie ier T +Ey i eS / +— = aan | +Bee. +验车 验车 +正常完好齐全 缺少 Fra 变形 脱落 +图例 +V N x Oo e + +18 + +===== PAGE 44 ===== +备注说明状况: + +Povnnr (82): EN + +mamis: §— [C2l2eto7 +mIARH: D396 -LO + +19 + +===== PAGE 45 ===== +附件 4:车辆交接验收表 + +Gy 08] + +车辆交接验收表 +车辆交接日期, 2026. b, |b +基本信息 +交车单位名称; 车辆型号 CH5oyoRXYSDC233FEEV| AS LBA 32A 29P ALS |233 +接车单位名称: 454 和可 交车时仪表里程数 aah 公里 发动机号/电机号 T7220%$- HD) 002. +交车及接车时待检验项目 +项目 交车时状况 | 接车时状况 项上 交车时状况“| 接车时状况 项目 交车时状况 | 接车时状况 +IVER CAE T a 冷却风记转动状况 \A | dealt ZL +录样车相同) +车辆照片(左前45" ff +PRIN VAL LA |一三有后45" 角一 Y +aK) +钥是 (2把) 1 汽车标识LOGO V EEE IEE SRD rg +上 + +车辆底盘状况 J | nate WA iw + +16 + +===== PAGE 46 ===== +车辆内饰状况 V 灭火器 (1 个) Y 用户操作手册 + +灯光状况 备胎(1 只) x 质量保修手册 +前后灯总成状况 VA 点烟器 (1个) 保修IC卡 +风挡玻璃状况 / 反光衣 LA +仪表盘功能状况 VW TAH 义 +两刊器状况 Vv, SC RIOR 六 +Ae 7 车辆反光由 iw +安全带状况 加 三角警示牌 , +左右后视镜状况 Va EB | idly 4% +Peer 7 独子 (27 号轮胎扳手) | y +动力舱状况 VA 备胎据村 x +空调状况 VY 轴头保养板手 x +潭和车系统状况 J WORF 113-15 号开口 义 +扳手) +车辆启动状况 挡车器 +制动器助力性能状况 胎压检测值 性 +方向盘助力性能状况 了 标准量去离子水 二 + +档位变换〈前进档、倒 J +档) 状况 + +标准量冷却液 7 + +17 + +===== PAGE 47 ===== +Bn 406 + +四轮轮胎状况 Ww +挡泥板状况 WA +ac ad ee +状况 +左右中后门状况 S +货箱箱体状况 vy +GMAIL +车辆外观检验 +T人二三全= T人二-企一。 +See 一te +Geen. af wasn .. +1 —™- | eo 上 +ier 讲画 oh -一- +=n ' +"iS eae bl ae: +- — an r +1 —_- i! is 上 +om . +te oe. Bee +验车 验车 +全 D Q SI +sa 正常完好齐 te 开型 变形 脱落 +v x O @ + +18 + +===== PAGE 48 ===== +备注说明状况: + +an BK): LE Prnnnr (签名), war + +联系电话; [LL loaKLD 联系电话,。 | Go22E62] +确认时间: publ mint: Do f b. [4 + +19 + +===== PAGE 49 ===== +附件 4:车辆交接验收表 +BNA I2050 + +车辆交接验收表 +车辆交接日期, +j 基本信息 +ET ra (Alfons AAA +接车单位名称, 交千时仪表里程数 aia Me +FEA: RAEN OTA +FH 交车时状况 | 接车时状况 mH 交车时状况“| 接车时状况 项目 交车时状况 | 接车时状况 +整车外观状况〈与公告目 +awe \一 冷却风户转动状况 wa kez 23% + +录样车相同) + +车辆照片〈左前45”角 + +HET v4 AIR a 一张,右后4。 朋一 + +张) + +交付车续航电量 95%以 + +上 6% + +pike (2把) |把 汽车标识LOGO vv一 + +车辆底盘状况 ~~ wa a 整车配置表 + +16 + +===== PAGE 50 ===== +车辆内饰状况 一 灭火器 (1个) 17 用户操作手册 +灯光状况 Va 备胎 RD 多 质量保修手册 +前后灯总成状况 WA AUREL) wa 保修IC 卡 wa +风挡玻璃状况 ~~ 反光衣 外 +仪表盘功能状况 千斤项 名 +两刊器状况 a Cm 名) +座椅状况 一 车辆反光贴 多 +安全带状况 WA =m a +左右后视镜状况 \ 牵引拖多 Va +车内反光镜状况 网到 钳子〈27 号轮胎扳手) hk +BAA MOAR. wa est He %) +空调状况 wa 轴头保养板手 2) +WORF (13-15 号开口 +刹车系统状况 U% _ zy +车辆启动状况 LZ 挡车器 Ay +制动器助力性能状况 wa HEED a +方向盘助力性能状况 到 标准量去离子水 = + +档位变换〈前进档、倒 +~一 标准量冷却液 ~~ + +#4) 状况 + +17 + +===== PAGE 51 ===== +全Jog + +四轮轮胎状况 wa +挡泥板状况 N +{0 5 FBLA BAT +aR vw +左右中后门状况 Via +货箱箱体状况 \/ +车辆外观检验 +a +sen. +a fa *¢ —. += ayy 1 ml +amt - 下 +Bee +验车 验车 +wa 正常完好齐全 缺少 Ir 变形 脱落 +v N x + +18 + +===== PAGE 52 ===== +arcmin: FAYMMOARR) , BEA HC + +an (签名); doh if Sevnnr (签名): EN + +a + +My Shi +wemmis. (1666 056 1 了 | 。 联系电话 0202化2] +确认时间; wb.(6 wun ff if + +19 + +===== PAGE 53 ===== +附件 4: 车辆交接验收表 + +车辆交接验收表 +ewscncim BOB 2026 6. [( $4613 (5| +基本信息 +交车单位名称; #444 车辆型号 CHM刀ApoKCYGDCG3KE 外号 [BYh7为042 +接车单位名称; 交车时仪表里程数 [1人 公里 发动机号/电机号 T2206 —H Poor +304 BEEN PROT +项目 交车时状况 | 接车时状况 项目 交车时状况“| 接车时状况 aA 交车时状况 | 接车时状况 +整车外观状况〈与公告目 + VA 冷却风肩转动状况 v 供气量 Sh +录样车相同) +V V/ 车辆照片〈左前45”角 +ETAL 喇叭状况 一张,右后 45”角一 +aK) +V/ 交付车续航电量 95% DL +钥匙 (2 把) 汽车标识LOGO 多 +2 iia VA LI 下 邓克 + +车辆底盘状况 Vv 1m 入 Pore / + +16 + +===== PAGE 54 ===== +车辆内人饰状况 Y 灭火器 (1个) Vv 用户操作手册 + +灯光状况 AL 备胎 CL RD x 质量保修手册 +前后灯总成状况 V MR VJ 保修IC卡 +风挡下璃状况 V 反光家 K +RATHER Vv FRB x +两天器状况 v senna x +PERRI V 车辆反光由 v +安全带状况 V 三角警示牌 X +左右后视镜状况 V ae] iy +车内反光镜状况 V 钳子 (27 号轮胎扳手) x +AMIR, V eh x +空调状况 Vv 轴头保养析手 XN +四 V 活动扳手 Cais BF X +扳手) +车辆启动状况 \ 挡车器 Viz +制动器助力性能状况 胎压检测人 \一 +a BRB Ja VE REAR V 标准量去离子水 V + +档位变换〈前进档、倒 VV +档) 状况 + +标准量冷却液 ve + +17 + +===== PAGE 55 ===== +ENGST | +V + +四轮轮胎状况 +挡泥板状况 Vv +rat ae +> 、 + MRE 2}, EE ICL, +左右中后门状况 V +货箱箱体状况 V +车辆外观检验 +人 7 +wasn ween. += ES 可 =! nm Ky pe 上 +号 pay i ”种部 2 a mere +x 车顶 1 oes z 车硕 +amt 了 1 —_ 下 +: n : + +ee oe + +wee 又车 +正常完好齐全 we JPR 变形 Baik +图例 +v N x O @ + +18 + +===== PAGE 56 ===== + +===== PAGE 57 ===== +附件 4:车辆交接验收表 + +Ba FW 2d8S + +车辆交接验收表 +车辆交接日期 2ODA |p , le +基本信息 +seats $f En MINS oXKY GDESSfchyae® LBUBPSP HLS DOE +接车单位名称; 交车时仪表里程 SIPIS Bn | 发动机号/电机号 [2.229X5- D992. +交车及接车时待检验项目 \ +Y +项目 交车时状况 | 接车时状况 aA Sea | 接车时状况 项目 交车时状况| 接车时状况 +整车外观状况《与公告目 sssapusesnsne / 网 了 4 4 +录样车相同) V | Y ‘ |6/0 IN : +车辆照片 左前45”角 +漆面状况 V 喇叭状况 、 / 一张,右后 45”角一 VA +张) +交付车续航电量 95%以 +Alle (248) | ts Hie 汽车标识LOGO / 网 + al % 上 tf +车辆底盘状况 V wae U 整车配置表 Jv + +16 + +===== PAGE 58 ===== +车辆内饰状况 WA 灭火器 4) L- 用户操作手册 + +灯光状况 WA 备胎〈1 只) x 质量保修手册 +前后灯总成状况 ) 点烟器 (1个) LA 保修IC 卡 +风挡玻璃状况 Va 反光衣 x +仪表盘功能状况 | FAW x +两刊器状况 Y 轮胎类要 Ba +座椅状况 uv 车辆反光贴 UL +安全带状况 LA 三角和警示牌 Xx +左右后视镜状况 uw aa Hoty oa +车内反光镜状况 vv 钳子〈27 号轮胎扳手) 七 +动力舱状况 LA 备胎摇棒 xX +空调状况 VU 轴头保养板手 xX +WORF (13-15 号开口 +刹车系统状况 LA _ x +车辆启动状况 wu 挡车器 i 了 +制动器助力性能状况 LA 治压检测值 L7 +方向盘助力性能状况 (一 标准量去离子水 要 + +5 BH Y 标准量冷却液 vw + +17 + +===== PAGE 59 ===== +AAF N25 + +四轮轮胎状况 1 +挡泥板状况 ava +(05 A PT A +8, Va +左右中后门状况 L +货箱箱体状况 WA +车辆外观检验 +和 +wen.” + +— i! “& = 1 i + +- 一 mm 四 四 二 - _— om +ag 1 an et AP ; a +Fa Fi % ins +FA hie 国:… , fe Fe fit a , +sai - - - 8S ce - - 25 + += ae + +On Gn + +OB seein, < + +验车 验车 + +正常完好齐全 缺少 开裂 变形 脱落 + +图例 +v N x O〇 e + +18 + +===== PAGE 60 ===== +备注说明状况: + +SOL eae, Jo RAL + +wan CER): Ih + +联系电话, [L605 ap) +amiantin. 2996, 6. It + +Porann BR): aA + +19 + +===== PAGE 61 ===== +附件 4:车辆交接验收表 + +A+ N03 |( + +车辆交接验收表 +车辆交接日期,)0)L 6 JC +基本信息 +ET nts CCGP TWO +接车单位名称: 交车时仪表里程数 站923》 公里 发动机号/电机号 +交车及接车时待检验项目 +aH 交车时状况 | 接车时状况 aH 交车时状况“| 接车时状况 aE 交车时状况 | 接车时状况 +整车外观状况〈与公告目 +冷却风肩转动状况 供气量 ¥h +录样车相同) vw a 1370 +AEN CART 45° 角 +IWR Va HOARE a 一张,右后465。和角一 +张) +交付车续航电量 95%以 +四 4 +钥匙 (2把) ie 汽车标识LOGO wa . ay y, + +车辆底盘状况 ~*~ Iie 4 整车配置表 + +16, + +===== PAGE 62 ===== +车辆内饰状况 六 灭火器 U4) Ht 用户操作手册 + +灯光状况 wa fei CLAD al 质量保修手册 +前后灯总成状况 , 点烟器 (1个) | 保修IC 卡 +风挡玻璃状况 i 反光衣 h +仪表盘功能状况 、 千斤项 | +雨刊器状况 WA aL vn +HEPA a 车辆反光贴 wa +安全带状况 Wa 三角警示租 wal +左右后视镜状况 wa e/a wal +车内反光镜状况 Zw 钳子〈27 号轮胎扳手) K +动力舱状况 wa 备胎据棱 Ry +空调状况 wa 轴头保养板手 ra +WORF (13-15 号开口 +AA RSORM, vv一 _ ky +车辆启动状况 wa 挡车器 2) +制动器助力性能状况 ~一 胎压检测值 一 +方向盘助力性能状况 ~一 标准量去离子水 wa +档位变换〈前进档、倒 S ovens VY + +档) 状况 + +17 + +===== PAGE 63 ===== +名NTNON + +四轮轮胎状况 一 +挡泥板状况 N +便防护围杠/后部护围杜 +al +状况 +左右中后门状况 uw +货箱箱体状况 WA +车辆外观检验 +r@.- ---- >> @ome +ween .. +wees... 1 +a fi ela: +* +iz ii a , +ros Ht = Pe +— = “| +a i +es oe +验车 验车 +正常完好齐全 缺少 开裂 变形 脱洲 +图例 +v x O + +18 + +===== PAGE 64 ===== + +===== PAGE 65 ===== +附件 车辆交接验收表 +SAG3DL| + +车辆交接验收表 +车辆交接日期 206.616 +基本信息 +Se AA: 车辆型号 CANS o4oNgGDC3 SHE LRAASIADS Pods as +接车单位名称; 交车时仪表里程数 6Job 公里 发动机号/电机号 +交车及接车时待检验项目 +项目 交车时状况 | 接车时状况 项目 交车时状况“| 接车时状况 项目 交车时状况 | 接车时状况 +整车外观状况《与公告目 +冷却风扇转动状况 供气量 +录样车相同) va we 2 +车辆照片(左前45" ff +漆面状况 喇叭状况 一张,右后 45。角一 +a wv 张) / +加 交付车续航电量 95%以 +钥匙 (2把) 【加 汽车标识 LOGO wa A 4 5 + +车辆底盘状况 本 Ee ~一 整车配置表 + +16 + +===== PAGE 66 ===== +“EPH OAR v4 RAR (1个) a 用户操作手册 + +灯光状况 N GIR) 备胎 (1只) 为 质量保修手册 +前后灯总成状况 、 点烟器 CL 个) al 保修IC 卡 +风挡玻璃状况 反光衣 2) + +仪表盘功能状况 Y 千斤顶 名 + +两刊器状况 Y nea 加 + +座椅状况 A 车辆反光贴 vv一 + +安全带状况 \一 三角警示牌 vv一 + +左右后视镜状况 ~ ae | ety + +车内反光镜状况 vw ae C7 号轮胎扳手) | X + +动力舱状况 vv一 备胎扬棱 X + +空调状况 val 轴头保养板手 + +车辆启动状况 al 挡车器 zn ie + +制动器助力性能状况 VA 胎压检测值 + +方向盘助力性能状况 SL 标准量去离子水 人 + +sacs canes. Bt] 人 seats toe +档) 状况 Va + +17 + +===== PAGE 67 ===== +H\G32024 + +DOH HAA +挡泥板状况 +便防护围杠/后部护围杠 yw +状况 +左右中后门状况 iw +货箱箱体状况 ww +车辆外观检验 +全-全一 +ao ee +warn .. +we J +2) 1 jean +at) to 1 the +rm fl wn ita a +Pere ”, + - +ee +验车 验车 +正常完好齐全 aie eH 变形 脱落 +图例 - +x + +18 + +===== PAGE 68 ===== +TANNA AERBENRE + +awn (签名); hd Bann (签名); 2 + +联系电话, (UC AD wenibiss (2. fob) 7 +确认时间 wtb le Wi 296, 6.16 + +19 + +===== PAGE 69 ===== +附件 4:车辆交接验收表 + +440319 + +车辆交接验收表 +车辆交接日期; 2024 4/ +基本信息 +seemnietc. EF. 车辆型号 oj XXGDG feel 8 LBA 32 26Pols [216 +接车单位名称: 交车时仪表里程数 Ve | 侧员B机号 E2.00K5 D002 +eee wt +mH 交车时状况 | 接车时状况 项目 交车时状况“| 接车时状况 IF Scknten, | Renton +SAIN, 5A sssmsesynn ; 了 +录样车相同) vu 1一 供气量 Vy +车辆照片〈左前45”角 +FEHR Vv HAR J _%, HE AS* AR +张) +wile (2把) of vctemein Loco uv seen vont 37g +车辆底盘状况 / wae 7 wane V + +16 + +===== PAGE 70 ===== +车辆内饰状况 Y 灭火器 (1个) ] 用户操作手册 + +灯光状况 v 备胎 (1只) x 质量保修手册 +前后灯总成状况 LA 点烟器 (1个) (oA 保修IC 卡 +风挡玻璃状况 LA 反光衣 a +仪表盘功能状况 WA FAT x +aE 四 sem mH x +座椅状况 / 车辆反光贴 Y +安全带状况 Y = fa eae / +左右后视合状况 VA Eat x +车内反光镜状况 WA 钳子27 号轮胎扳手) x +动力舱状况 A el EH x +空调状况 WA 轴头保养板手 x +_ 活动扳手 〈13-15 号开口 Ne +ANE RGA, vA _ +车辆启动状况 VA 挡车器 /7 +制动器助力性能状况 Y 胎压检测值 LT +方向盘助力性能状况 VY 标准量去离子水 U +档位变换〔前进档、倒 ft sete tom 4 + +档) 状况 + +17 + +===== PAGE 71 ===== +四轮轮胎状况 uv +挡泥板状况 [一 +便防护围杠/后部护围杠 V +状况 +左右中后门状况 四 +货箱箱体状况 LA +车辆外观检验 +1全 i pel tea +weer SSS es +wat .. wasn. +ne 1 .- 1 a - ' +Pe 4 +2 hie 车项 , ' ae Fy fit +car be _- - - 由六 十 是 本 wus +— ad “| —_ “| +四 Om . +iw we +验车 验车 +正常完好齐全 缺少 wR 变形 脱落 +图例 +v N x O + +18 + +===== PAGE 72 ===== +备注说明状况, yg £2, Lf Sah , ItZ@ yr + +Beran cee poh if Benanr Be), 14 + +wexonits (206 S63 wens [GDN LELS LD +确认时间,7 62, LV 确认时间, pris ar + +19 + +===== PAGE 73 ===== +附件 4: 车辆交接验收表 + +ENG24IE +车辆交接验收表 +seimscrerim, 296.6,16 +基本信息 +交车单位名称; 车辆型号 CAN6oXX褒DC33 KEVERS LBIASAA2 Po) 51234 +接车单位名称; 交车时仪表里程数 GL Ce +交车及搂车时待检验项目 +mA 交车时状况 | 接车时状况 iA 交车时状况“| 接车时状况 mA 交车时状况 | 接车时状况 +整车外观状况《与公告 +a eee Va ce J EAU bf, +录样车相同) +J 车辆照片 〈左前45”角 、/ +漆面状况 喇叭状况 J | nik) ase 角一 +张) +加 交付车续航电量 95%以 +‘fille (2把) | 汽车标识LOGO wt |, 6 eh +车辆底盘状况 S| aes ROMER J + +16 + +===== PAGE 74 ===== +车辆内饰状况 一 灭火器 (1个) vv一 用户操作手册 + +灯光状况 va 备胎 (1只) Ki 质量保修手册 +前后灯总成状况 ~ AU 4S) Wa 保修1C卡 +风挡玻璃状况 wa 反光衣 上 +仪表盘功能状况 UY FAT ral +ARLE \一 轮胎站要 2 +HERR wa 车辆反光贴 wa +安全带状况 \一 = fen if +左右后视镜状况 a | Hy \一 +车内反光镜状况 ~~ 钳子〈27 号轮胎扳手) A +动力舱状况 一 备胎扬棱 +空调状况 轴头保养板手 为 +HESKERER Y 活动扳手〈13-15 号开口 2) +扳手) +车辆启动状况 本 挡车器 +制动器助力性能状况 wa 抬压检测值 一 +方向盘助力性能状况 vv 标准量去离子水 wa + +PARR CHER. +: vA tem a vo + +17 + +===== PAGE 75 ===== +AGLI + +四轮轮胎状况 wa +挡泥板状况 \ 一 +便防护围杠/后部护围本 +状况 -一 +左右中后门状况 一 +货箱箱体状况 O +车辆外观检验 +本一一一=二二 人 +|全-一一全一 r全-二全一 +wasn .. ween .. +=! at 1 — it bo ' +ti | 77 ge 3 —_ mets +Fa il 车项 1 th et am ' +sale ‘= - 奈六 sat Pn ais +— 了 an +eewe eee +cal 验车 +正常完好齐全 se ie 变形 脱落 +图例 +v x O + +18 + +===== PAGE 76 ===== +sss fel HAAG FU, MOHAIR FS, Al CRA RD AS, SIME MLS + +Fann CEH): Ih Benn BH): our + +WA Bil (astv WOR MIE: (5.26667 ] +确认时间, rob. 1p wun, 226 ( lf + +19 + +===== PAGE 77 ===== +附件 4:车辆交接验收表 + +分62291 车辆交接验收表 + +saeco: 2026. 6, [6 + +基本信息 +交车单位名称; 车辆型号 CHNyOoHOAAJ4H 33 FCEV| 车架号 LB4A32A20Ro)9 1224 +接车单位名称, 和沙和 本 交车时仪表里程数 47637 公里 发动机号/电机号 TZ 220X5-Hpo02-- +交车及接车时待检验项目 +项目 交车时状况 | 接车时状况 项上 交车时状况“| 接车时状况 项目 交车时状况 | 接车时状况 +整车外观状况《与公告目 + LA | espunseaninn | Beat I4y: +录样车相同) +车辆照片(左前45”角 +漆面状况 / HAAR U | vik) BIR 4S 角一 / +ak) +交付车续航电量 95% LL +钥匙 (2 把) | 汽车标识LOGO J 45, +上 + +车辆底盘状况 J | aie EMR J + +16 + +===== PAGE 78 ===== +车辆内饰状况 af 灭火器 (1个) “) 用户操作手册 + +灯光状况 y 备胎〈1只) 大 质量保修手册 +前后灯总成状况 J 点烟器 (1个) v 保修1C卡 +风挡玻璃状况 Vv 反光衣 又 +仪表盘功能状况 J FRU x +WAR v nea x +座椅状况 v 车辆反光巾 WA +安全带状况 J 三角警示牌 v +左右后视什状况 Vv palit v +车内反光镜状况 V 钳子27 号轮胎扳手) x +动力舱状况 备胎扬桂 x +空调状况 aS 轴头保养板手 +市车系统状况 v WORF (13-15 号开口 JL +扳手) +车辆启动状况 v 挡车器 +制动器助力性能状况 / 胎压检测值 v +Fi A HERETO, / 标准量去离子水 v +1 “es J | bitten / + +17 + +===== PAGE 79 ===== +ANGI + +侧防护围杠/后部护围杠 +状况 + +左右中后门状况 +货箱箱体状况 +“RSL +a cee an +oa le eee +Wasn . Weaen .. +wee. J wie 1 +a 喇? en 2. cme mas +Fa fie B-: ae Fa fie | : +eal Fe PET Bab Pe wl ke +加 站 = : +—_ | 下 1 加 ' +: P : +ae vee we a ae +验车 验车 +正常完好齐全 缺少 开裂 变形 脱落 +图例 +v x O + +18 + +===== PAGE 80 ===== +备注说明状况: + +\ +: os +方负责人《签名); br hf DIRBA 8%): 2H + +mani Mg 77 联系电话 [£59 ba4CL7] +mania: 2996,6.16 mania 9b, 6. Ub + +19 + diff --git a/.tmp-inspect-form/hi_form1_psm4.txt b/.tmp-inspect-form/hi_form1_psm4.txt new file mode 100644 index 0000000..c79bc0a --- /dev/null +++ b/.tmp-inspect-form/hi_form1_psm4.txt @@ -0,0 +1,84 @@ +===== HI PAGE 1 ===== +附件 4:车辆交接验收表 + +gh (28 708 + +车辆交接验收表 +车辆交接日期:2六、所 儿 +基本信息 +交车单位名称; 车辆型号 MEH IGIE gy" LBYZA2Po1S)234- +接车单位名称; 交车时仪表里程数 4297 公里 发动机号/电机号 尼22002- 上 poo2 | +交车及接车时待检验项目 +项目 交车时状况 | 接车时状况 HB 交车时状况“| 接车时状况 BIE 交车时状况 | 接车时状况 +REALM CTAB] 。 |/ +冷却风扇转动状况 iy CRS / Wo +录样车相同) +车辆照片 CAC HH 45° 角 +漆面状况 wv 喇叭状况 ~~ 一张,右后45。角一| LA +张) + +[Li 交付车续航电量 95%以 + +PARE 〈2把) | 把 汽车标识LOGO . y of +WA + +7 + +16 + +===== HI PAGE 2 ===== +车辆内饰状况 ly 灭火器 (1个) Va 用户操作手册 + +灯光状况 VA 备胎〈1只) X 质量保修手册 +前后灯总成状况 U/ 点姻器 (1个) V 保修IC 卡 A +风挡玻璃状况 VY 反光衣 X, +仪表盘功能状况 J 千斤顶 X +雨刮器状况 V/ 轮胎考棍 4 +BERPIRDL V 车辆反光巾 KY +安全带状况 V 三角警示牌 Wa +左右后视镜状况 V #55 Hae X +车内反光镜状况 / 钳子〈27 号轮胎扳手) xX +动力舱状况 V 备胎扬棱 冯 +空调状况 ef 轴头保养板手 nN +WORF (13-15 号开口 +刹车系统状况 UV _ x +车辆启动状况 V 挡车器 | ‘i +制动器助力性能状况 胎压检测值 Va +7a WB 75 HE REAR Wa 标准量去离子水 S +档位变换〈前进档、倒 J cian v +档) 状况 + +17 + +===== HI PAGE 3 ===== +四轮轮胎状况 Wa +挡泥板状况 4 +便防护围杠/后部护围本| UL一 +状况 +左右中后门状况 VA +货箱箱休状况 LA +车辆外观检验 +全=全一 和 +wea 四is + +== |! 一 一 ! —_ |! 一 一 +条 Cr é +ae ‘_ ee me aoeminenesp +正常完好齐全 缺少 开裂 变形 脱落 +图例 +V N x O + +18 + +===== HI PAGE 4 ===== +备注说明状况: + +AGA? TH lt 2@%, Zh + +an (签名): Ih Benes BH): 7和风 + +联系电话; CR22508 联系电话。 [Ko bel | +确认时间,207 [6 确认时间, 02h. (./6 + +19 + diff --git a/.tmp-inspect-form/hi_ocr_p1-5.txt b/.tmp-inspect-form/hi_ocr_p1-5.txt new file mode 100644 index 0000000..505090d --- /dev/null +++ b/.tmp-inspect-form/hi_ocr_p1-5.txt @@ -0,0 +1,85 @@ +===== HI PAGE 1 PSM6 ===== +附件 4:车辆交接验收表 +多 A 23 708 +车辆交接验收表 +车辆交接日期:2六、所 儿 +ECT 27/127) + P2050 | +交车及接车时待检验项目 | +整车外观状况《与公告目| |/ | +冷却风扇转动状况 [一 供氨量 /7 +录样车相同) +车辆照片 (左前45" 角 +漆面状况 wv 喇叭状况 WA 一张,右后6。 eA +| 张) +交付车续航电量 95%以 yp +oem [1g | em | | mg区国生 +£ V +ewe [ym ee +16 + +===== HI PAGE 2 PSM6 ===== +TV Ta |e +TV a | +TV ee |过| +[CE +Cer ol +am a +awe | OV [ome | | +feawwa | VV | came | +TV mw TXT | + wellx ll | | + re +Ea YT +m= fe] ete tf | | +KF) \ +a 7 +ao | —y | [wma | | sd +Crnasneena | yp [wwe | || +档) 状况 VS +17 + +===== HI PAGE 3 PSM6 ===== +4625/08 +Ok ca ( ( ( +2 A nD (OE (nD +mer 国汪0 +状况 +[in | YF +Ec a (OD (OD +ean.” 四is +Rn ae bn le ala cae wea | += fy i = af mm +—_ 1 ? an a 1 ” ”1 + Bee. Bee. + | +图例 + 9 pe +18 + +===== HI PAGE 4 PSM6 ===== +Pp Gadd TH lt 2H, qh +Pian ca). Senor ute ?和风 +联系电话, CR22508 联系电话。 Gob EO | +确认时间,207 [6 确认时间, 02h. (./6 + +===== HI PAGE 5 PSM6 ===== +附件 4:车辆交接验收表 +9 AG 2b2% +车辆交接验收表 +车辆交接日期; 292b b. 1b +基本信息 +ET [CT +交车及接车时待检验项目 +es | ma | ce +整车外观状况《与公告上 sesosamnee +冷却风扇转动状况 供氢量 了 +录样车相同) [一 V4 an 4, +AeA HY Jett 45° 角 +SETI Va 喇叭状况 LN 一张, Haas 角一| 1/ +张) +交付车续航 %b +钥匙 (2 把) | fe 汽车标识LOGO VY an EEE /2f +[CEI[CI +16 + diff --git a/.tmp-inspect-form/hi_ocr_p1_psm4.txt b/.tmp-inspect-form/hi_ocr_p1_psm4.txt new file mode 100644 index 0000000..c85dd6d --- /dev/null +++ b/.tmp-inspect-form/hi_ocr_p1_psm4.txt @@ -0,0 +1,27 @@ +===== PSM4 P1 ===== +附件 4:车辆交接验收表 + +gh (28 708 + +车辆交接验收表 +车辆交接日期:2六、所 儿 +基本信息 +交车单位名称; 车辆型号 MEH IGIE gy" LBYZA2Po1S)234- +接车单位名称; 交车时仪表里程数 4297 公里 发动机号/电机号 尼22002- 上 poo2 | +交车及接车时待检验项目 +项目 交车时状况 | 接车时状况 HB 交车时状况“| 接车时状况 BIE 交车时状况 | 接车时状况 +REALM CTAB] 。 |/ +冷却风扇转动状况 iy CRS / Wo +录样车相同) +车辆照片 CAC HH 45° 角 +漆面状况 wv 喇叭状况 ~~ 一张,右后45。角一| LA +张) + +[Li 交付车续航电量 95%以 + +PARE 〈2把) | 把 汽车标识LOGO . y of +WA + +7 + +16 diff --git a/.tmp-inspect-form/hi_page_001.png b/.tmp-inspect-form/hi_page_001.png new file mode 100644 index 0000000..8e1e26e Binary files /dev/null and b/.tmp-inspect-form/hi_page_001.png differ diff --git a/.tmp-inspect-form/hi_page_002.png b/.tmp-inspect-form/hi_page_002.png new file mode 100644 index 0000000..f88d231 Binary files /dev/null and b/.tmp-inspect-form/hi_page_002.png differ diff --git a/.tmp-inspect-form/hi_page_003.png b/.tmp-inspect-form/hi_page_003.png new file mode 100644 index 0000000..12f35da Binary files /dev/null and b/.tmp-inspect-form/hi_page_003.png differ diff --git a/.tmp-inspect-form/hi_page_004.png b/.tmp-inspect-form/hi_page_004.png new file mode 100644 index 0000000..6336f2b Binary files /dev/null and b/.tmp-inspect-form/hi_page_004.png differ diff --git a/.tmp-inspect-form/hi_page_005.png b/.tmp-inspect-form/hi_page_005.png new file mode 100644 index 0000000..dafb39b Binary files /dev/null and b/.tmp-inspect-form/hi_page_005.png differ diff --git a/.tmp-inspect-form/hi_page_006.png b/.tmp-inspect-form/hi_page_006.png new file mode 100644 index 0000000..953277d Binary files /dev/null and b/.tmp-inspect-form/hi_page_006.png differ diff --git a/.tmp-inspect-form/hi_page_007.png b/.tmp-inspect-form/hi_page_007.png new file mode 100644 index 0000000..9125013 Binary files /dev/null and b/.tmp-inspect-form/hi_page_007.png differ diff --git a/.tmp-inspect-form/hi_page_008.png b/.tmp-inspect-form/hi_page_008.png new file mode 100644 index 0000000..886c28e Binary files /dev/null and b/.tmp-inspect-form/hi_page_008.png differ diff --git a/.tmp-inspect-form/page_001.png b/.tmp-inspect-form/page_001.png new file mode 100644 index 0000000..ffdd369 Binary files /dev/null and b/.tmp-inspect-form/page_001.png differ diff --git a/.tmp-inspect-form/page_002.png b/.tmp-inspect-form/page_002.png new file mode 100644 index 0000000..4ebb704 Binary files /dev/null and b/.tmp-inspect-form/page_002.png differ diff --git a/.tmp-inspect-form/page_003.png b/.tmp-inspect-form/page_003.png new file mode 100644 index 0000000..71a136f Binary files /dev/null and b/.tmp-inspect-form/page_003.png differ diff --git a/.tmp-inspect-form/page_004.png b/.tmp-inspect-form/page_004.png new file mode 100644 index 0000000..199f68e Binary files /dev/null and b/.tmp-inspect-form/page_004.png differ diff --git a/.tmp-inspect-form/page_005.png b/.tmp-inspect-form/page_005.png new file mode 100644 index 0000000..ac01c0a Binary files /dev/null and b/.tmp-inspect-form/page_005.png differ diff --git a/.tmp-inspect-form/page_006.png b/.tmp-inspect-form/page_006.png new file mode 100644 index 0000000..4337fbc Binary files /dev/null and b/.tmp-inspect-form/page_006.png differ diff --git a/.tmp-inspect-form/page_007.png b/.tmp-inspect-form/page_007.png new file mode 100644 index 0000000..d4b0a6f Binary files /dev/null and b/.tmp-inspect-form/page_007.png differ diff --git a/.tmp-inspect-form/page_008.png b/.tmp-inspect-form/page_008.png new file mode 100644 index 0000000..628fa0d Binary files /dev/null and b/.tmp-inspect-form/page_008.png differ diff --git a/.tmp-inspect-form/page_009.png b/.tmp-inspect-form/page_009.png new file mode 100644 index 0000000..f37b68d Binary files /dev/null and b/.tmp-inspect-form/page_009.png differ diff --git a/.tmp-inspect-form/page_010.png b/.tmp-inspect-form/page_010.png new file mode 100644 index 0000000..c3034ab Binary files /dev/null and b/.tmp-inspect-form/page_010.png differ diff --git a/.tmp-inspect-form/page_011.png b/.tmp-inspect-form/page_011.png new file mode 100644 index 0000000..dc3b3cf Binary files /dev/null and b/.tmp-inspect-form/page_011.png differ diff --git a/.tmp-inspect-form/page_012.png b/.tmp-inspect-form/page_012.png new file mode 100644 index 0000000..881057d Binary files /dev/null and b/.tmp-inspect-form/page_012.png differ diff --git a/.tmp-inspect-form/page_013.png b/.tmp-inspect-form/page_013.png new file mode 100644 index 0000000..0cf0e50 Binary files /dev/null and b/.tmp-inspect-form/page_013.png differ diff --git a/.tmp-inspect-form/page_014.png b/.tmp-inspect-form/page_014.png new file mode 100644 index 0000000..75acc4a Binary files /dev/null and b/.tmp-inspect-form/page_014.png differ diff --git a/.tmp-inspect-form/page_015.png b/.tmp-inspect-form/page_015.png new file mode 100644 index 0000000..0c7e69b Binary files /dev/null and b/.tmp-inspect-form/page_015.png differ diff --git a/.tmp-inspect-form/page_016.png b/.tmp-inspect-form/page_016.png new file mode 100644 index 0000000..aeab0da Binary files /dev/null and b/.tmp-inspect-form/page_016.png differ diff --git a/.tmp-inspect-form/page_017.png b/.tmp-inspect-form/page_017.png new file mode 100644 index 0000000..764faa3 Binary files /dev/null and b/.tmp-inspect-form/page_017.png differ diff --git a/.tmp-inspect-form/page_018.png b/.tmp-inspect-form/page_018.png new file mode 100644 index 0000000..6868c3f Binary files /dev/null and b/.tmp-inspect-form/page_018.png differ diff --git a/.tmp-inspect-form/page_019.png b/.tmp-inspect-form/page_019.png new file mode 100644 index 0000000..13fa6cd Binary files /dev/null and b/.tmp-inspect-form/page_019.png differ diff --git a/.tmp-inspect-form/page_020.png b/.tmp-inspect-form/page_020.png new file mode 100644 index 0000000..146f4de Binary files /dev/null and b/.tmp-inspect-form/page_020.png differ diff --git a/.tmp-inspect-form/page_021.png b/.tmp-inspect-form/page_021.png new file mode 100644 index 0000000..130f001 Binary files /dev/null and b/.tmp-inspect-form/page_021.png differ diff --git a/.tmp-inspect-form/page_022.png b/.tmp-inspect-form/page_022.png new file mode 100644 index 0000000..949b2f9 Binary files /dev/null and b/.tmp-inspect-form/page_022.png differ diff --git a/.tmp-inspect-form/page_023.png b/.tmp-inspect-form/page_023.png new file mode 100644 index 0000000..d837b59 Binary files /dev/null and b/.tmp-inspect-form/page_023.png differ diff --git a/.tmp-inspect-form/page_024.png b/.tmp-inspect-form/page_024.png new file mode 100644 index 0000000..b00b7a9 Binary files /dev/null and b/.tmp-inspect-form/page_024.png differ diff --git a/.tmp-inspect-form/page_025.png b/.tmp-inspect-form/page_025.png new file mode 100644 index 0000000..3a93442 Binary files /dev/null and b/.tmp-inspect-form/page_025.png differ diff --git a/.tmp-inspect-form/page_026.png b/.tmp-inspect-form/page_026.png new file mode 100644 index 0000000..eacbcdb Binary files /dev/null and b/.tmp-inspect-form/page_026.png differ diff --git a/.tmp-inspect-form/page_027.png b/.tmp-inspect-form/page_027.png new file mode 100644 index 0000000..ee616fc Binary files /dev/null and b/.tmp-inspect-form/page_027.png differ diff --git a/.tmp-inspect-form/page_028.png b/.tmp-inspect-form/page_028.png new file mode 100644 index 0000000..d5833e9 Binary files /dev/null and b/.tmp-inspect-form/page_028.png differ diff --git a/.tmp-inspect-form/page_029.png b/.tmp-inspect-form/page_029.png new file mode 100644 index 0000000..0cc7cd0 Binary files /dev/null and b/.tmp-inspect-form/page_029.png differ diff --git a/.tmp-inspect-form/page_030.png b/.tmp-inspect-form/page_030.png new file mode 100644 index 0000000..82b3738 Binary files /dev/null and b/.tmp-inspect-form/page_030.png differ diff --git a/.tmp-inspect-form/page_031.png b/.tmp-inspect-form/page_031.png new file mode 100644 index 0000000..39c6d81 Binary files /dev/null and b/.tmp-inspect-form/page_031.png differ diff --git a/.tmp-inspect-form/page_032.png b/.tmp-inspect-form/page_032.png new file mode 100644 index 0000000..07436d1 Binary files /dev/null and b/.tmp-inspect-form/page_032.png differ diff --git a/.tmp-inspect-form/page_033.png b/.tmp-inspect-form/page_033.png new file mode 100644 index 0000000..34cd755 Binary files /dev/null and b/.tmp-inspect-form/page_033.png differ diff --git a/.tmp-inspect-form/page_034.png b/.tmp-inspect-form/page_034.png new file mode 100644 index 0000000..1eb8574 Binary files /dev/null and b/.tmp-inspect-form/page_034.png differ diff --git a/.tmp-inspect-form/page_035.png b/.tmp-inspect-form/page_035.png new file mode 100644 index 0000000..0ac17ee Binary files /dev/null and b/.tmp-inspect-form/page_035.png differ diff --git a/.tmp-inspect-form/page_036.png b/.tmp-inspect-form/page_036.png new file mode 100644 index 0000000..c953bb9 Binary files /dev/null and b/.tmp-inspect-form/page_036.png differ diff --git a/.tmp-inspect-form/page_037.png b/.tmp-inspect-form/page_037.png new file mode 100644 index 0000000..3722baa Binary files /dev/null and b/.tmp-inspect-form/page_037.png differ diff --git a/.tmp-inspect-form/page_038.png b/.tmp-inspect-form/page_038.png new file mode 100644 index 0000000..09aab9d Binary files /dev/null and b/.tmp-inspect-form/page_038.png differ diff --git a/.tmp-inspect-form/page_039.png b/.tmp-inspect-form/page_039.png new file mode 100644 index 0000000..e9ee88b Binary files /dev/null and b/.tmp-inspect-form/page_039.png differ diff --git a/.tmp-inspect-form/page_040.png b/.tmp-inspect-form/page_040.png new file mode 100644 index 0000000..1456b33 Binary files /dev/null and b/.tmp-inspect-form/page_040.png differ diff --git a/.tmp-inspect-form/page_041.png b/.tmp-inspect-form/page_041.png new file mode 100644 index 0000000..1b827ae Binary files /dev/null and b/.tmp-inspect-form/page_041.png differ diff --git a/.tmp-inspect-form/page_042.png b/.tmp-inspect-form/page_042.png new file mode 100644 index 0000000..7933ab8 Binary files /dev/null and b/.tmp-inspect-form/page_042.png differ diff --git a/.tmp-inspect-form/page_043.png b/.tmp-inspect-form/page_043.png new file mode 100644 index 0000000..e151593 Binary files /dev/null and b/.tmp-inspect-form/page_043.png differ diff --git a/.tmp-inspect-form/page_044.png b/.tmp-inspect-form/page_044.png new file mode 100644 index 0000000..ad0d852 Binary files /dev/null and b/.tmp-inspect-form/page_044.png differ diff --git a/.tmp-inspect-form/page_045.png b/.tmp-inspect-form/page_045.png new file mode 100644 index 0000000..1a791af Binary files /dev/null and b/.tmp-inspect-form/page_045.png differ diff --git a/.tmp-inspect-form/page_046.png b/.tmp-inspect-form/page_046.png new file mode 100644 index 0000000..31bdc72 Binary files /dev/null and b/.tmp-inspect-form/page_046.png differ diff --git a/.tmp-inspect-form/page_047.png b/.tmp-inspect-form/page_047.png new file mode 100644 index 0000000..ef7e71c Binary files /dev/null and b/.tmp-inspect-form/page_047.png differ diff --git a/.tmp-inspect-form/page_048.png b/.tmp-inspect-form/page_048.png new file mode 100644 index 0000000..060207d Binary files /dev/null and b/.tmp-inspect-form/page_048.png differ diff --git a/.tmp-inspect-form/page_049.png b/.tmp-inspect-form/page_049.png new file mode 100644 index 0000000..6dfa9e7 Binary files /dev/null and b/.tmp-inspect-form/page_049.png differ diff --git a/.tmp-inspect-form/page_050.png b/.tmp-inspect-form/page_050.png new file mode 100644 index 0000000..ca68275 Binary files /dev/null and b/.tmp-inspect-form/page_050.png differ diff --git a/.tmp-inspect-form/page_051.png b/.tmp-inspect-form/page_051.png new file mode 100644 index 0000000..e81afde Binary files /dev/null and b/.tmp-inspect-form/page_051.png differ diff --git a/.tmp-inspect-form/page_052.png b/.tmp-inspect-form/page_052.png new file mode 100644 index 0000000..40cf957 Binary files /dev/null and b/.tmp-inspect-form/page_052.png differ diff --git a/.tmp-inspect-form/page_053.png b/.tmp-inspect-form/page_053.png new file mode 100644 index 0000000..f37d7e2 Binary files /dev/null and b/.tmp-inspect-form/page_053.png differ diff --git a/.tmp-inspect-form/page_054.png b/.tmp-inspect-form/page_054.png new file mode 100644 index 0000000..b787e85 Binary files /dev/null and b/.tmp-inspect-form/page_054.png differ diff --git a/.tmp-inspect-form/page_055.png b/.tmp-inspect-form/page_055.png new file mode 100644 index 0000000..32d914a Binary files /dev/null and b/.tmp-inspect-form/page_055.png differ diff --git a/.tmp-inspect-form/page_056.png b/.tmp-inspect-form/page_056.png new file mode 100644 index 0000000..083f9bb Binary files /dev/null and b/.tmp-inspect-form/page_056.png differ diff --git a/.tmp-inspect-form/page_057.png b/.tmp-inspect-form/page_057.png new file mode 100644 index 0000000..07faa76 Binary files /dev/null and b/.tmp-inspect-form/page_057.png differ diff --git a/.tmp-inspect-form/page_058.png b/.tmp-inspect-form/page_058.png new file mode 100644 index 0000000..4d2dcec Binary files /dev/null and b/.tmp-inspect-form/page_058.png differ diff --git a/.tmp-inspect-form/page_059.png b/.tmp-inspect-form/page_059.png new file mode 100644 index 0000000..a0d2523 Binary files /dev/null and b/.tmp-inspect-form/page_059.png differ diff --git a/.tmp-inspect-form/page_060.png b/.tmp-inspect-form/page_060.png new file mode 100644 index 0000000..a8b058b Binary files /dev/null and b/.tmp-inspect-form/page_060.png differ diff --git a/.tmp-inspect-form/page_061.png b/.tmp-inspect-form/page_061.png new file mode 100644 index 0000000..76a8742 Binary files /dev/null and b/.tmp-inspect-form/page_061.png differ diff --git a/.tmp-inspect-form/page_062.png b/.tmp-inspect-form/page_062.png new file mode 100644 index 0000000..a9f8d1e Binary files /dev/null and b/.tmp-inspect-form/page_062.png differ diff --git a/.tmp-inspect-form/page_063.png b/.tmp-inspect-form/page_063.png new file mode 100644 index 0000000..38f4eab Binary files /dev/null and b/.tmp-inspect-form/page_063.png differ diff --git a/.tmp-inspect-form/page_064.png b/.tmp-inspect-form/page_064.png new file mode 100644 index 0000000..e55d341 Binary files /dev/null and b/.tmp-inspect-form/page_064.png differ diff --git a/.tmp-inspect-form/page_065.png b/.tmp-inspect-form/page_065.png new file mode 100644 index 0000000..0845a1e Binary files /dev/null and b/.tmp-inspect-form/page_065.png differ diff --git a/.tmp-inspect-form/page_066.png b/.tmp-inspect-form/page_066.png new file mode 100644 index 0000000..6740935 Binary files /dev/null and b/.tmp-inspect-form/page_066.png differ diff --git a/.tmp-inspect-form/page_067.png b/.tmp-inspect-form/page_067.png new file mode 100644 index 0000000..d2e4525 Binary files /dev/null and b/.tmp-inspect-form/page_067.png differ diff --git a/.tmp-inspect-form/page_068.png b/.tmp-inspect-form/page_068.png new file mode 100644 index 0000000..efc2d63 Binary files /dev/null and b/.tmp-inspect-form/page_068.png differ diff --git a/.tmp-inspect-form/page_069.png b/.tmp-inspect-form/page_069.png new file mode 100644 index 0000000..0fa04fa Binary files /dev/null and b/.tmp-inspect-form/page_069.png differ diff --git a/.tmp-inspect-form/page_070.png b/.tmp-inspect-form/page_070.png new file mode 100644 index 0000000..2700f75 Binary files /dev/null and b/.tmp-inspect-form/page_070.png differ diff --git a/.tmp-inspect-form/page_071.png b/.tmp-inspect-form/page_071.png new file mode 100644 index 0000000..e62507d Binary files /dev/null and b/.tmp-inspect-form/page_071.png differ diff --git a/.tmp-inspect-form/page_072.png b/.tmp-inspect-form/page_072.png new file mode 100644 index 0000000..6619d41 Binary files /dev/null and b/.tmp-inspect-form/page_072.png differ diff --git a/.tmp-inspect-form/page_073.png b/.tmp-inspect-form/page_073.png new file mode 100644 index 0000000..41ee5d0 Binary files /dev/null and b/.tmp-inspect-form/page_073.png differ diff --git a/.tmp-inspect-form/page_074.png b/.tmp-inspect-form/page_074.png new file mode 100644 index 0000000..106eb6b Binary files /dev/null and b/.tmp-inspect-form/page_074.png differ diff --git a/.tmp-inspect-form/page_075.png b/.tmp-inspect-form/page_075.png new file mode 100644 index 0000000..6fa80a0 Binary files /dev/null and b/.tmp-inspect-form/page_075.png differ diff --git a/.tmp-inspect-form/page_076.png b/.tmp-inspect-form/page_076.png new file mode 100644 index 0000000..3f02231 Binary files /dev/null and b/.tmp-inspect-form/page_076.png differ diff --git a/.tmp-inspect-form/page_077.png b/.tmp-inspect-form/page_077.png new file mode 100644 index 0000000..0cbad2d Binary files /dev/null and b/.tmp-inspect-form/page_077.png differ diff --git a/.tmp-inspect-form/page_078.png b/.tmp-inspect-form/page_078.png new file mode 100644 index 0000000..dad065b Binary files /dev/null and b/.tmp-inspect-form/page_078.png differ diff --git a/.tmp-inspect-form/page_079.png b/.tmp-inspect-form/page_079.png new file mode 100644 index 0000000..13ab33b Binary files /dev/null and b/.tmp-inspect-form/page_079.png differ diff --git a/.tmp-inspect-form/page_080.png b/.tmp-inspect-form/page_080.png new file mode 100644 index 0000000..b305450 Binary files /dev/null and b/.tmp-inspect-form/page_080.png differ diff --git a/.tmp-lease-check-12.png b/.tmp-lease-check-12.png new file mode 100644 index 0000000..8c38f2a Binary files /dev/null and b/.tmp-lease-check-12.png differ diff --git a/.tmp-lease-check-v2.png b/.tmp-lease-check-v2.png new file mode 100644 index 0000000..8c38f2a Binary files /dev/null and b/.tmp-lease-check-v2.png differ diff --git a/.tmp-lease-expanded-12.png b/.tmp-lease-expanded-12.png new file mode 100644 index 0000000..abb1671 Binary files /dev/null and b/.tmp-lease-expanded-12.png differ diff --git a/.tmp-lease-expanded-v2.png b/.tmp-lease-expanded-v2.png new file mode 100644 index 0000000..abb1671 Binary files /dev/null and b/.tmp-lease-expanded-v2.png differ diff --git a/.tmp-lease-v2-toolbar.png b/.tmp-lease-v2-toolbar.png new file mode 100644 index 0000000..a44b1ac Binary files /dev/null and b/.tmp-lease-v2-toolbar.png differ diff --git a/.tmp-pagination-full.png b/.tmp-pagination-full.png new file mode 100644 index 0000000..9d51158 Binary files /dev/null and b/.tmp-pagination-full.png differ diff --git a/AGENTS.md b/AGENTS.md index 80969ba..5b092de 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,9 +1,6 @@ ## 项目信息 - 项目名称:OneOS1.2 - -# Agent 工作流程 - ## 🧭 核心顺序 ```text @@ -20,12 +17,13 @@ | 产物/场景 | 位置 | 参考文档 | |-----------|------|----------| +| OneOS V2 全局定版设计规范 | `src/resources/design-system/DESIGN.md` | `rules/global-design-spec.md` 与 `.cursor/rules/oneos-v2-design-system.mdc`;**OneOS V2 下新建/修改页面强制前置读取** | | vm-page 列表分页 | `src/common/TablePagination.tsx` + `vm-pagination.css` | `src/prototypes/vm-shared/DESIGN.md` | | 主题 | `src/themes//` | `rules/theme-guide.md` | | 项目资料和文档 | `src/resources/` | `rules/resource-management-guide.md` | | 画布 | `src/prototypes//canvas.excalidraw`、`canvas-assets/` | 原型画布和画布素材 | | 业务逻辑规格 | `src/prototypes//.spec/.md` | `rules/business-logic-documentation-guide.md` | -| AutoPRD + 标注目录 | `.spec/requirements-prd.md` + `annotation-source.json`;定稿写第 10 章功能变更 | skill `oneos-autoprd`;规则 `.cursor/rules/oneos-autoprd-sync.mdc` | +| AutoPRD + 标注目录 | `.spec/requirements-prd.md` + `annotation-source.json`;定稿写第 10 章与 `prdVersion`;云效组合唯一 `YunxiaoPMapp` | skill `oneos-autoprd`;清洗用 `AutoRDO`;规则 `.cursor/rules/oneos-autoprd-sync.mdc` | | 对象存储发布 URL | `.axhub/make/axhub.config.json` → `cloudPublishing.s3` | `rules/cloud-publish-url-guide.md` | | 原型标注布局 | `src/common/prototype-annotation-host.tsx` | `rules/prototype-annotation-layout-guide.md` | diff --git a/CLAUDE.md b/CLAUDE.md index 084b079..5b092de 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,9 +1,6 @@ ## 项目信息 - 项目名称:OneOS1.2 - -# Agent 工作流程 - ## 🧭 核心顺序 ```text @@ -20,11 +17,13 @@ | 产物/场景 | 位置 | 参考文档 | |-----------|------|----------| +| OneOS V2 全局定版设计规范 | `src/resources/design-system/DESIGN.md` | `rules/global-design-spec.md` 与 `.cursor/rules/oneos-v2-design-system.mdc`;**OneOS V2 下新建/修改页面强制前置读取** | | vm-page 列表分页 | `src/common/TablePagination.tsx` + `vm-pagination.css` | `src/prototypes/vm-shared/DESIGN.md` | | 主题 | `src/themes//` | `rules/theme-guide.md` | | 项目资料和文档 | `src/resources/` | `rules/resource-management-guide.md` | | 画布 | `src/prototypes//canvas.excalidraw`、`canvas-assets/` | 原型画布和画布素材 | | 业务逻辑规格 | `src/prototypes//.spec/.md` | `rules/business-logic-documentation-guide.md` | +| AutoPRD + 标注目录 | `.spec/requirements-prd.md` + `annotation-source.json`;定稿写第 10 章与 `prdVersion`;云效组合唯一 `YunxiaoPMapp` | skill `oneos-autoprd`;清洗用 `AutoRDO`;规则 `.cursor/rules/oneos-autoprd-sync.mdc` | | 对象存储发布 URL | `.axhub/make/axhub.config.json` → `cloudPublishing.s3` | `rules/cloud-publish-url-guide.md` | | 原型标注布局 | `src/common/prototype-annotation-host.tsx` | `rules/prototype-annotation-layout-guide.md` | diff --git a/FIELD-INSPECTION-README.md b/FIELD-INSPECTION-README.md new file mode 100644 index 0000000..62f4448 --- /dev/null +++ b/FIELD-INSPECTION-README.md @@ -0,0 +1,17 @@ +# 外勤验车三端 + +静态 HTML 原型,位于 `src/prototypes/`,由 Axhub Make / Vite(`root: src`)按路径直接提供,不进入 React `index.tsx` 预览管线(故意不放 `index.tsx`,避免被 Make 预览模板覆盖)。 + +| 目录 | 形态 | 本地预览 URL | +|------|------|----------------| +| `src/prototypes/vehicle-inspection-web/` | 外勤 Web | `/prototypes/vehicle-inspection-web/index.html` | +| `src/prototypes/vehicle-inspection-miniprogram/` | 小程序壳 | `/prototypes/vehicle-inspection-miniprogram/index.html` | +| `src/prototypes/vehicle-inspection-app/` | APP 壳 | `/prototypes/vehicle-inspection-app/index.html` | + +入口链接挂在 PC「验车入库」详情(`vehicle-inspection`),不单独进导航(linked-only)。点击「Web 外勤 / 小程序 / APP」会自动写入 `oneos.vp.activeInspectionId` 并打开对应页。 + +与 PC 共用 localStorage: + +- `oneos.vp.inspections` +- `oneos.vp.stockedVehicles` +- `oneos.vp.activeInspectionId` diff --git a/docs/superpowers/plans/2026-07-22-message-hub.md b/docs/superpowers/plans/2026-07-22-message-hub.md new file mode 100644 index 0000000..095de84 --- /dev/null +++ b/docs/superpowers/plans/2026-07-22-message-hub.md @@ -0,0 +1,548 @@ +# Message Hub 实施计划 + +> **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:** 落地统一消息中枢(模型 + 本地路由表 + Web 消息中心原型),并让工作台/外壳铃铛读同一中枢。 + +**Architecture:** `src/common/message-hub/` 持有 Message、ChannelDelivery、RouteRule 与 `resolve()`;新原型 `message-center` 提供筛选/列表/详情/端模拟器/去处理;工作台与 `notice-bridge` 通过适配层映射为铃铛摘要。真推送与真唤端不做。 + +**Tech Stack:** React + TypeScript(现有 Axhub Make 原型)、Vitest(`resolve` 单测)、localStorage(可选已读)、现有 `oneos-app-shell` 铃铛 UI。 + +**Spec:** [docs/superpowers/specs/2026-07-22-message-hub-design.md](../specs/2026-07-22-message-hub-design.md) + +--- + +## 文件结构(锁定) + +| 路径 | 职责 | +|------|------| +| `src/common/message-hub/types.ts` | Message / Channel / RouteRule 类型 | +| `src/common/message-hub/routes.ts` | 五端 × 多系统路由表常量 | +| `src/common/message-hub/resolve.ts` | `resolveMessageTarget()` | +| `src/common/message-hub/resolve.test.ts` | 路由优先级与失败态单测 | +| `src/common/message-hub/seed.ts` | 宽首期种子消息(含旧 notices 映射) | +| `src/common/message-hub/store.ts` | 读列表、标已读、未读数(内存 + localStorage) | +| `src/common/message-hub/shell-adapter.ts` | Message → `ShellNoticeItem` | +| `src/common/message-hub/index.ts` | 公共导出 | +| `src/prototypes/message-center/index.tsx` | 原型入口 | +| `src/prototypes/message-center/MessageCenterApp.tsx` | 主 UI | +| `src/prototypes/message-center/styles/index.css` | 样式 | +| `src/prototypes/message-center/annotation-source.json` | 标注目录 | +| `src/prototypes/message-center/.spec/route-resolve.md` | 路由判定全文 | +| `src/prototypes/message-center/.spec/requirements-prd.md` | AutoPRD | +| `src/resources/prd/message-center-autoprd.md` | PRD 副本 | +| 修改 `oneos-app-shell/ShellNoticeCenter.tsx` | 「查看全部」→ 消息中心;详情优先于行内直跳 | +| 修改 `oneos-web-workbench-new` | 铃铛数据改读 message-hub | +| 修改 `nav-menu.json` | 增加「消息中心」菜单项 | + +--- + +### Task 1: message-hub 类型 + resolve + 单测 + +**Files:** +- Create: `src/common/message-hub/types.ts` +- Create: `src/common/message-hub/resolve.ts` +- Create: `src/common/message-hub/resolve.test.ts` +- Create: `src/common/message-hub/index.ts`(先导出类型与 resolve) + +- [ ] **Step 1: 写入类型** + +```ts +// src/common/message-hub/types.ts +export type SourceSystem = 'oneos' | 'vehicle-mid' | 'other-system' | 'external'; +export type MessageClient = 'web' | 'ios' | 'android' | 'harmony' | 'wechat_oa'; +export type ChannelStatus = 'pending' | 'sent' | 'failed' | 'skipped'; +export type MessagePriority = 'normal' | 'high'; +export type ExternalAppId = 'external-app-a' | 'external-app-b'; + +export type ChannelDelivery = { + client: MessageClient; + status: ChannelStatus; + updatedAt?: string; +}; + +export type HubMessage = { + id: string; + sourceSystem: SourceSystem; + bizType: string; + bizId: string; + title: string; + summary: string; + detail: string; + priority: MessagePriority; + createdAt: string; + readAt?: string; + /** 角色占位;空数组表示全员可见 */ + audienceRoleIds: string[]; + channels: ChannelDelivery[]; + /** 展示用业务标签,如「交车任务」 */ + bizTag: string; +}; + +export type RouteRule = { + sourceSystem: SourceSystem; + bizType: string; + client: MessageClient; + targetTemplate: string; + label: string; + externalApp?: ExternalAppId; + /** 精确未命中时,再试该 client(如 harmony → android) */ + fallbackClient?: MessageClient; +}; + +export type ResolveOk = { + ok: true; + uri: string; + label: string; + externalApp?: ExternalAppId; + rule: RouteRule; +}; + +export type ResolveFail = { + ok: false; + reason: 'no_rule'; + message: '当前端暂无可跳转目标'; +}; + +export type ResolveResult = ResolveOk | ResolveFail; +``` + +- [ ] **Step 2: 写失败单测(先不实现 resolve 逻辑体)** + +```ts +// src/common/message-hub/resolve.test.ts +import { describe, expect, it } from 'vitest'; +import { resolveMessageTarget } from './resolve'; +import type { HubMessage, RouteRule } from './types'; + +const baseMsg: HubMessage = { + id: 'm1', + sourceSystem: 'oneos', + bizType: 'approval.arrive', + bizId: 'AP-1', + title: '待审', + summary: '', + detail: '详情', + priority: 'normal', + createdAt: '2026-07-22T10:00:00+08:00', + audienceRoleIds: [], + channels: [], + bizTag: '租赁合同', +}; + +describe('resolveMessageTarget', () => { + it('精确匹配 web 规则', () => { + const rules: RouteRule[] = [ + { + sourceSystem: 'oneos', + bizType: 'approval.arrive', + client: 'web', + targetTemplate: '/prototypes/oneos-web-approval-todo?id={bizId}', + label: '审批待办', + }, + ]; + const r = resolveMessageTarget(baseMsg, 'web', rules); + expect(r.ok).toBe(true); + if (r.ok) expect(r.uri).toBe('/prototypes/oneos-web-approval-todo?id=AP-1'); + }); + + it('harmony 可 fallback 到 android', () => { + const rules: RouteRule[] = [ + { + sourceSystem: 'oneos', + bizType: 'approval.arrive', + client: 'android', + targetTemplate: 'oneos://approval/{bizId}', + label: '安卓审批', + fallbackClient: undefined, + }, + { + sourceSystem: 'oneos', + bizType: 'approval.arrive', + client: 'harmony', + targetTemplate: '', + label: '鸿蒙走安卓模板', + fallbackClient: 'android', + }, + ]; + // 实现约定:harmony 行若 targetTemplate 为空且带 fallbackClient,则用 fallback 的模板 + const r = resolveMessageTarget(baseMsg, 'harmony', rules); + expect(r.ok).toBe(true); + if (r.ok) expect(r.uri).toBe('oneos://approval/AP-1'); + }); + + it('无规则返回固定文案', () => { + const r = resolveMessageTarget(baseMsg, 'wechat_oa', []); + expect(r).toEqual({ + ok: false, + reason: 'no_rule', + message: '当前端暂无可跳转目标', + }); + }); + + it('带 externalApp', () => { + const msg = { ...baseMsg, sourceSystem: 'external' as const, bizType: 'order.action', bizId: 'O-9' }; + const rules: RouteRule[] = [ + { + sourceSystem: 'external', + bizType: 'order.action', + client: 'web', + targetTemplate: 'ext-app-a://order/{bizId}', + label: '外部 App A 订单', + externalApp: 'external-app-a', + }, + ]; + const r = resolveMessageTarget(msg, 'web', rules); + expect(r.ok).toBe(true); + if (r.ok) { + expect(r.uri).toBe('ext-app-a://order/O-9'); + expect(r.externalApp).toBe('external-app-a'); + } + }); +}); +``` + +- [ ] **Step 3: 跑测确认失败** + +Run: `npx vitest --run src/common/message-hub/resolve.test.ts` +Expected: FAIL(模块不存在或函数未导出) + +- [ ] **Step 4: 实现 resolve** + +```ts +// src/common/message-hub/resolve.ts +import type { HubMessage, MessageClient, ResolveResult, RouteRule } from './types'; + +function fillTemplate(template: string, msg: HubMessage): string { + return template + .replaceAll('{bizId}', encodeURIComponent(msg.bizId)) + .replaceAll('{id}', encodeURIComponent(msg.id)); +} + +function findRule( + rules: RouteRule[], + sourceSystem: HubMessage['sourceSystem'], + bizType: string, + client: MessageClient, +): RouteRule | undefined { + return rules.find( + (r) => r.sourceSystem === sourceSystem && r.bizType === bizType && r.client === client, + ); +} + +export function resolveMessageTarget( + msg: HubMessage, + client: MessageClient, + rules: RouteRule[], +): ResolveResult { + const primary = findRule(rules, msg.sourceSystem, msg.bizType, client); + if (primary) { + const useFallback = + (!primary.targetTemplate || primary.targetTemplate.trim() === '') && primary.fallbackClient; + if (useFallback && primary.fallbackClient) { + const fb = findRule(rules, msg.sourceSystem, msg.bizType, primary.fallbackClient); + if (fb?.targetTemplate) { + return { + ok: true, + uri: fillTemplate(fb.targetTemplate, msg), + label: primary.label || fb.label, + externalApp: fb.externalApp ?? primary.externalApp, + rule: fb, + }; + } + } + if (primary.targetTemplate?.trim()) { + return { + ok: true, + uri: fillTemplate(primary.targetTemplate, msg), + label: primary.label, + externalApp: primary.externalApp, + rule: primary, + }; + } + } + return { ok: false, reason: 'no_rule', message: '当前端暂无可跳转目标' }; +} +``` + +- [ ] **Step 5: 跑测通过** + +Run: `npx vitest --run src/common/message-hub/resolve.test.ts` +Expected: PASS(4 tests) + +- [ ] **Step 6: Commit**(仅当用户要求提交时执行) + +```bash +git add src/common/message-hub/ +git commit -m "$(cat <<'EOF' +feat(message-hub): add types and resolve() with vitest coverage + +EOF +)" +``` + +--- + +### Task 2: 路由表 + 种子消息 + +**Files:** +- Create: `src/common/message-hub/routes.ts` +- Create: `src/common/message-hub/seed.ts` +- Modify: `src/common/message-hub/index.ts` + +- [ ] **Step 1: 写入 `ROUTE_RULES`** + +至少覆盖(每个 listed bizType 需含 web/ios/android/harmony/wechat_oa 五行;wechat 无办理页可用空 template + 不配 fallback,使 resolve 失败并在 UI 显示 skipped 通道态): + +| sourceSystem | bizType | web 目标示例 | +|--------------|---------|--------------| +| oneos | approval.arrive | `/prototypes/oneos-web-approval-todo?id={bizId}` | +| oneos | urge.remind | `/prototypes/oneos-web-ops`(可带 query) | +| oneos | contract.expire | `/prototypes/customer-management` | +| oneos | license.expire | `/prototypes/customer-management` | +| oneos | bill.ready | `/prototypes/lease-business-ledger` | +| oneos | h2.balance | `/prototypes/payment-records` | +| oneos | system.release | (无跳转或打开版本说明占位 path) | +| vehicle-mid | fault.overdue | `/prototypes/vehicle-fault-handling#page=detail&id={bizId}` | +| vehicle-mid | vehicle.status | `/prototypes/vehicle-management` | +| other-system | generic.notice | `/prototypes/message-center`(仅详情,无外跳也可失败) | +| external | order.action | `ext-app-a://order/{bizId}` + `externalApp: external-app-a` | +| external | settle.action | `ext-app-b://settle/{bizId}` + `externalApp: external-app-b` | + +harmony 行:`targetTemplate: ''`, `fallbackClient: 'android'`。 +ios/android:`oneos://…` / `oneos-android://…` 占位 scheme。 +wechat_oa:对可 H5 办理的给 `https://oa.example.com/...` 占位;纯 PC 办理给空 template。 + +- [ ] **Step 2: 写入 `SEED_MESSAGES`** + +映射旧 `SEED_NOTICES`(保留原 id 如 `n-1`、`RELEASE_NOTICE_ID` 便于对照)+ 新增 vehicle-mid / other-system / external 各至少规格要求条数。 +`audienceRoleIds` 对齐原 `roleIds`;无角色限制用 `[]`。 +`channels`:web=sent,移动三端=sent 或 pending,wechat_oa 按样例 sent/skipped。 + +- [ ] **Step 3: 导出** + +```ts +// index.ts +export * from './types'; +export * from './resolve'; +export { ROUTE_RULES } from './routes'; +export { SEED_MESSAGES } from './seed'; +``` + +- [ ] **Step 4: 用真实 ROUTE_RULES 补一条集成断言(可选放 resolve.test)** + +```ts +import { ROUTE_RULES } from './routes'; +import { SEED_MESSAGES } from './seed'; + +it('种子 approval 在 web 可解析', () => { + const msg = SEED_MESSAGES.find((m) => m.bizType === 'approval.arrive'); + expect(msg).toBeTruthy(); + const r = resolveMessageTarget(msg!, 'web', ROUTE_RULES); + expect(r.ok).toBe(true); +}); +``` + +Run: `npx vitest --run src/common/message-hub/resolve.test.ts` → PASS + +- [ ] **Step 5: Commit**(用户要求时) + +```bash +git add src/common/message-hub/ +git commit -m "$(cat <<'EOF' +feat(message-hub): add route table and wide-scope seed messages + +EOF +)" +``` + +--- + +### Task 3: store + shell 适配 + +**Files:** +- Create: `src/common/message-hub/store.ts` +- Create: `src/common/message-hub/shell-adapter.ts` +- Modify: `src/common/message-hub/index.ts` + +- [ ] **Step 1: store** + +```ts +// 要点 +const STORAGE_KEY = 'oneos.message-hub.readAt.v1'; +// loadSeed(): HubMessage[] — 合并 SEED 与 localStorage 中的 readAt +// markRead(id): void +// unreadCount(messages, roleId?): number +// listForRole(messages, roleId?): HubMessage[] +// sort: 未读优先 → priority high → createdAt desc +``` + +- [ ] **Step 2: shell-adapter** + +```ts +import type { ShellNoticeItem } from '../oneos-app-shell/notice-bridge'; +import type { HubMessage } from './types'; +import { resolveMessageTarget } from './resolve'; +import { ROUTE_RULES } from './routes'; + +export function toShellNoticeItem(msg: HubMessage): ShellNoticeItem { + const web = resolveMessageTarget(msg, 'web', ROUTE_RULES); + return { + id: msg.id, + type: msg.priority === 'high' && msg.bizType.startsWith('urge') ? '催办提醒' : msg.bizTag, + bizTag: msg.bizTag, + title: msg.title, + summary: msg.summary, + detail: msg.detail, + time: msg.createdAt.replace('T', ' ').slice(0, 16), + read: !!msg.readAt, + href: web.ok && web.uri.startsWith('/') ? web.uri : undefined, + taskId: msg.bizId, + }; +} +``` + +注意:铃铛点击按规格**只打开详情、不在行上直跳**;`href` 仍可供详情内「去处理」使用。`type === '催办提醒'` 用于外壳排序/标签,adapter 对 `urge.remind` 必须输出 `'催办提醒'`。 + +- [ ] **Step 3: 导出 store / adapter** + +- [ ] **Step 4: Commit**(用户要求时) + +--- + +### Task 4: message-center 原型 UI + +**Files:** +- Create: `src/prototypes/message-center/index.tsx` +- Create: `src/prototypes/message-center/MessageCenterApp.tsx` +- Create: `src/prototypes/message-center/styles/index.css` +- 实现前 Read: `~/.agents/skills/ui-ux-pro-max/SKILL.md`,并跑 + `python3 ~/.agents/skills/ui-ux-pro-max/scripts/search.py "admin dashboard notification inbox" --design-system -p "OneOS message-center"` + 视觉对齐现有工作台,不用营销风。 + +- [ ] **Step 1: 入口** + +```tsx +/** + * @name 消息中心 + */ +import React from 'react'; +import { PrototypeAnnotationHost } from '../../common/prototype-annotation-host'; +import annotationSource from './annotation-source.json'; +import MessageCenterApp from './MessageCenterApp'; +import './styles/index.css'; + +export default function MessageCenterPage() { + return ( + <> + + + + ); +} +``` + +(若项目 annotation 挂载方式与 fault-handling 不同,以同目录近期原型为准。) + +- [ ] **Step 2: MessageCenterApp 行为清单(必须全部有)** + +1. 左栏:全部/未读/已读;系统源多选或下拉;业务类型下拉;**端模拟器**五端 +2. 右栏列表:未读点、bizTag、title、时间 +3. 选中详情:detail、channels 触达态表、`resolve` 预览(随端模拟器变)、「去处理」 +4. 去处理:`markRead` → resolve → + - `uri` 以 `/` 开头:`window.location.href` 或外壳 `postMessage({ type: 'ONEOS_SHELL_NAV', href })`(与工作台 `protoNav` 一致) + - 否则:模态「将打开 {externalApp} / {uri}」(不 `window.open` scheme) + - fail:页面内固定文案「当前端暂无可跳转目标」 +5. 触控/对比度遵循 ui-ux-pro-max;表格数字可用 tabular-nums + +- [ ] **Step 3: 本地预览** + +打开 `/prototypes/message-center`,手测:切换端模拟器预览变化;external 消息出示意模态;无 wechat 规则时提示不可跳。 + +- [ ] **Step 4: Commit**(用户要求时) + +--- + +### Task 5: 业务规格 + AutoPRD + 标注 + +**Files:** +- Create: `src/prototypes/message-center/.spec/route-resolve.md`(判定优先级表、前置条件、数据源、用户可见结果、种子 vs 未接 API) +- Create: `src/prototypes/message-center/.spec/requirements-prd.md`(用户故事:起点→运作→闭环;摘要链到 route-resolve) +- Create: `src/resources/prd/message-center-autoprd.md`(与 PRD 同步) +- Create/Update: `annotation-source.json` 顶层 `directory.nodes` 含「产品需求说明(PRD)」全文 + +- [ ] **Step 1: 按 oneos-autoprd 写 PRD**(产品语言,不写文件路径堆砌) + +- [ ] **Step 2: route-resolve.md 必须含** + +| 优先级 | 条件 | 结果 | +|--------|------|------| +| 1 | 精确 source+bizType+client 且 template 非空 | 填充 URI | +| 2 | template 空且 fallbackClient 有对应规则 | 用 fallback URI | +| 3 | 否则 | 不可跳转文案 | + +- [ ] **Step 3: 若存在脚本则运行** + +`node scripts/sync-annotation-directory.mjs`(或项目内同等命令) + +- [ ] **Step 4: Commit**(用户要求时) + +--- + +### Task 6: 导航登记 + +**Files:** +- Modify: `src/prototypes/oneos-prototype-nav/nav-menu.json`(在「工作台」旁或系统能力区增加「消息中心」→ `prototypes/message-center`) +- Run: `npm run nav:sync -- --prototype message-center --note "新增统一消息中心原型"` + +- [ ] **Step 1: 加菜单项** +- [ ] **Step 2: nav:sync** +- [ ] **Step 3: 刷新原型导航页确认可见** +- [ ] **Step 4: Commit**(用户要求时) + +--- + +### Task 7: 工作台铃铛 + 外壳对接中枢 + +**Files:** +- Modify: `src/prototypes/oneos-web-workbench-new/components/WorkbenchPage.tsx`(及 NoticePanel 数据源) +- Modify: `src/prototypes/oneos-web-workbench-new/data/notices.ts` — 改为从 hub re-export 适配函数,或标记 deprecated 并改调用方 +- Modify: `src/common/oneos-app-shell/ShellNoticeCenter.tsx` + - 列表项点击:`onOpen` 打开详情(勿直接 `onHandle`) + - 「查看全部」主按钮导航到 `/prototypes/message-center` +- Modify: `.spec/notice-center.md` 注明数据源迁移至 message-hub +- Update workbench AutoPRD 中通知章节(行为变更需 sync) + +- [ ] **Step 1: Workbench 使用 `loadMessages` + `toShellNoticeItem` / 自有列表组件读 hub** +- [ ] **Step 2: postNoticesSync 仍发 `ShellNoticeItem[]`(adapter 输出)** +- [ ] **Step 3: 外壳「查看全部」→ message-center** +- [ ] **Step 4: 手测:原型演示嵌入工作台时铃铛未读与中枢一致;查看全部进入消息中心** +- [ ] **Step 5: Commit**(用户要求时) + +--- + +### Task 8: 验收对照 + 收尾 + +- [ ] **Step 1: 对照规格 §10 验收清单逐条勾选** +- [ ] **Step 2: `npx vitest --run src/common/message-hub/resolve.test.ts`** +- [ ] **Step 3: 更新设计文档状态旁注明「已实现原型」(若本轮做完)** +- [ ] **Step 4: 停止 brainstorm companion(可选)** + `/Users/sylvawong/.agents/skills/brainstorming/scripts/stop-server.sh /Users/sylvawong/oneos1.2/.superpowers/brainstorm/82675-1784712939` + +--- + +## Spec 覆盖自检 + +| 规格要求 | 任务 | +|----------|------| +| Message / Channel / RouteRule 模型 | Task 1–2 | +| 本地路由 + 优先级 + 固定失败文案 | Task 1, 5 | +| 五端 × 多系统种子与外部 A/B | Task 2 | +| 消息中心 UI + 端模拟器 + 去处理 | Task 4 | +| 铃铛快捷 + 查看全部 | Task 7 | +| 独立中枢架构 | Task 1–4 | +| .spec + AutoPRD + 标注 | Task 5 | +| 导航登记 | Task 6 | +| 不做真推送/真唤端 | Task 4 模态示意 | + +无 TBD 占位;`resolveMessageTarget` / `HubMessage` / `ROUTE_RULES` 命名全计划一致。 diff --git a/docs/superpowers/plans/2026-07-22-vehicle-fault-handling.md b/docs/superpowers/plans/2026-07-22-vehicle-fault-handling.md new file mode 100644 index 0000000..769d0e7 --- /dev/null +++ b/docs/superpowers/plans/2026-07-22-vehicle-fault-handling.md @@ -0,0 +1,62 @@ +# 故障处置 Implementation Plan + +> **For agentic workers:** Use inline execution in this session (user requested 执行). Steps use checkbox syntax. + +**Goal:** 交付 `vehicle-fault-handling` 原型(列表+独立详情),共享故障数据驱动工作台预警与运维看板「故障登记」,并挂到 OneOS → 车辆运维 → 故障处置。 + +**Architecture:** `src/common/vehicle-fault/` 持有类型、种子、localStorage、时限/归档校验与统计聚合;原型页读写该模块;工作台 KPI / OpsCockpit 改读同一聚合。AI 上报无 UI,仅种子模拟待处理原始记录。 + +**Tech Stack:** React + Ant Design + TypeScript;`useHashPage`(list / detail / stats-placeholder / proof-placeholder);`PrototypeAnnotationHost`;localStorage。 + +**Spec:** `docs/superpowers/specs/2026-07-22-vehicle-fault-handling-design.md` + +--- + +## File map + +| Path | Responsibility | +|------|----------------| +| `src/common/vehicle-fault/types.ts` | 状态、附件、通知、FaultRecord | +| `src/common/vehicle-fault/sla.ts` | 截止日、剩余天数、临期/逾期判定、归档硬门槛 | +| `src/common/vehicle-fault/seed.ts` | 演示种子(含临期、逾期、待处理、处理中、挂起、已归档) | +| `src/common/vehicle-fault/storage.ts` | load/save localStorage | +| `src/common/vehicle-fault/stats.ts` | KPI 明细 + 看板 faultStats | +| `src/common/vehicle-fault/notify.ts` | 短信/邮件模板渲染与演示发送记录 | +| `src/common/vehicle-fault/index.ts` | 导出 | +| `src/prototypes/vehicle-fault-handling/*` | UI:列表、详情、占位页、样式、PRD、标注 | +| `src/prototypes/oneos-web-workbench-new/data/kpi.ts` 等 | 接入故障 KPI | +| `src/prototypes/oneos-web-workbench-new/data/ops-cockpit.ts` + OpsCockpit | 故障登记同源统计 | +| `src/prototypes/oneos-prototype-nav/nav-menu.json` | 车辆运维菜单 | + +--- + +### Task 1: 共享数据模块 + +- [x] Create types, sla, seed, storage, stats, notify, index under `src/common/vehicle-fault/` + +### Task 2: 故障处置原型 UI + +- [x] list / detail / placeholders + PRD + annotation + +### Task 3: 工作台接入 + +- [x] KPI + OpsCockpit 同源 + +### Task 4: 导航与收尾 + +- [x] sidebar + nav-menu 车辆运维 → 故障处置 + + +--- + +## Spec coverage + +| Spec 节 | Task | +|---------|------| +| §2 菜单/新原型 | 2, 4 | +| §3 状态机 | 1, 2 | +| §4 时限与模板 | 1 notify, 2 UI | +| §5 列表+独立详情 | 2 | +| §5.3 硬门槛 | 1 sla, 2 | +| §6 工作台 | 3 | +| 二期占位 | 2, 4 | diff --git a/docs/superpowers/plans/2026-07-22-vehicle-purchase-inspection-linear-ui.md b/docs/superpowers/plans/2026-07-22-vehicle-purchase-inspection-linear-ui.md new file mode 100644 index 0000000..89d5e73 --- /dev/null +++ b/docs/superpowers/plans/2026-07-22-vehicle-purchase-inspection-linear-ui.md @@ -0,0 +1,103 @@ +# 车辆采购合同 + 验车入库 Linear UI 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:** 按规格将采购合同、验车入库、外勤三端对齐 Linear 产品浅色(`--ln-*` / `#32a06e`)与 vm-page 壳层,一波交付,不改业务规则。 + +**Architecture:** PC 两原型继续引用 `vehicle-management/style.css`;列表改为筛选卡 + KPI + `OperationActions` + `TablePagination`;表单/详情用顶栏返回 + 分区卡片;外勤三端共享一份 `field-theme.css`(`--ln-*` 子集)并改各 `index.html` 内联色。业务仍走 `src/common/vehicle-purchase/*`。 + +**Tech Stack:** React + Ant Design + Lucide;vm-/ln- CSS;静态 HTML/JS 外勤页。 + +**Spec:** `docs/superpowers/specs/2026-07-22-vehicle-purchase-inspection-linear-ui-design.md` + +--- + +## File map + +| 文件 | 职责 | +|------|------| +| `vehicle-purchase-contract/VehiclePurchaseContractApp.tsx` | 列表壳、KPI、表单分区、主题色 | +| `vehicle-purchase-contract/styles/index.css` | vpc 收敛到 vm/ln | +| `vehicle-purchase-contract/DESIGN.md` | 声明设计基底 | +| `vehicle-inspection/VehicleInspectionApp.tsx` | 同上(验车) | +| `vehicle-inspection/styles/index.css` | vi 收敛 | +| `vehicle-inspection/DESIGN.md` | 声明设计基底 | +| `vehicle-inspection-*/field-theme.css`(新建,三端各一份或同源复制) | 外勤 Linear 浅色 token | +| `vehicle-inspection-*/index.html` | 引用 theme、触控与对比度 | + +--- + +### Task 1: 采购合同列表壳 + 主题色 + +**Files:** +- Modify: `src/prototypes/vehicle-purchase-contract/VehiclePurchaseContractApp.tsx` +- Modify: `src/prototypes/vehicle-purchase-contract/styles/index.css` +- Create: `src/prototypes/vehicle-purchase-contract/DESIGN.md` + +- [ ] **Step 1:** `vmTheme.token.colorPrimary` 改为 `#32a06e`;`colorLink` 同步。 +- [ ] **Step 2:** 列表去掉 `vpc-header` h1/副标题;结构改为: + - `vm-page vpc-page` + - 筛选:`vm-filter-card` / 或 Card `title="筛选条件"` + 关键词/状态/审批 + 查询重置 + - KPI 四卡(草稿、审批中合集、已通过、已生成验车),可点驱动 filter + - `vm-table-section`:右对齐「新建合同」;表格;`OperationActions` 替换散落链接(若尚未用) + - `vm-table-footer` + `TablePagination` +- [ ] **Step 3:** CSS 用 `var(--ln-*)` / `var(--vm-*)` 替换 `#10b981`、`#f8fafc` 等硬编码;保留审批 banner、表单 grid 类名但换 token。 +- [ ] **Step 4:** 写入 `DESIGN.md`:基底 = linear 产品浅色 + vm-shared。 +- [ ] **Step 5:** 浏览器打开 `/prototypes/vehicle-purchase-contract/` 目视:无大标题、主色绿、分页在底。 + +### Task 2: 采购合同创建/编辑/查看分区 + +**Files:** +- Modify: `VehiclePurchaseContractApp.tsx`(create/edit/view 分支) +- Modify: `styles/index.css` + +- [ ] **Step 1:** 顶栏:返回 + 标题 + 主操作(保存/提交/关闭)。 +- [ ] **Step 2:** 表单/详情字段按规格五分区包进 `vpc-section` 卡片(主体 / 车型价格 / 分期 / 交付 / 附件质保)。 +- [ ] **Step 3:** 审批提示条用 `--ln-primary-soft` 背景;总价 `tabular-nums`。 +- [ ] **Step 4:** 目视创建页分区与返回可用。 + +### Task 3: 验车入库列表 + 详情 + +**Files:** +- Modify: `src/prototypes/vehicle-inspection/VehicleInspectionApp.tsx` +- Modify: `src/prototypes/vehicle-inspection/styles/index.css` +- Create: `src/prototypes/vehicle-inspection/DESIGN.md` + +- [ ] **Step 1:** 同 Task1:主题色、去大标题、筛选卡、KPI(待验/验车中/已完成)、表格+分页、`OperationActions`。 +- [ ] **Step 2:** 详情:返回顶栏;摘要;外勤按钮(绑定 + open);车辆表;主操作右对齐。 +- [ ] **Step 3:** CSS 换 `--ln-*`;`DESIGN.md` 声明基底。 +- [ ] **Step 4:** 目视列表与详情;点小程序 URL 200。 + +### Task 4: 外勤三端 Linear 浅色 + +**Files:** +- Create: `field-theme.css` 于 web(内容含 `:root` `--ln-primary` 等),复制到 miniprogram、app +- Modify: 各 `index.html` link stylesheet;调整内联 style 引用 var +- Modify: 如有冲突的 `app.js` 内联色则改为 class + +- [ ] **Step 1:** 定义 `--ln-primary: #32a06e`、canvas、ink、muted、hairline、radius、error。 +- [ ] **Step 2:** 卡片/按钮/底栏用 token;触控按钮 min-height 44px;body ≥16px。 +- [ ] **Step 3:** 三端同步;curl 三个 index.html 为 200;目视一页。 + +### Task 5: 文档与验收 + +- [ ] **Step 1:** 若仅样式:PRD 可跳过全量;若分区标题有语义变化,轻量更新 `.spec/requirements-prd.md` 一句「UI 对齐 Linear 产品浅色」。 +- [ ] **Step 2:** 对照规格 §7 验收清单自检。 +- [ ] **Step 3:** 不自动 commit(除非用户要求)。 + +--- + +## Spec coverage + +| 规格节 | Task | +|--------|------| +| §2 Linear 浅色 | 1–4 | +| §3.1 合同 | 1–2 | +| §3.2 验车 | 3 | +| §3.3 外勤 | 4 | +| §5 业务冻结 | 全程不改 common | +| §7 验收 | 5 | + +## Placeholder scan + +无 TBD;提交步骤尊重用户「未要求不 commit」。 diff --git a/docs/superpowers/specs/2026-07-22-message-hub-design.md b/docs/superpowers/specs/2026-07-22-message-hub-design.md new file mode 100644 index 0000000..8efd6c4 --- /dev/null +++ b/docs/superpowers/specs/2026-07-22-message-hub-design.md @@ -0,0 +1,193 @@ +# 消息中枢(Message Hub)重构设计 + +> 日期:2026-07-22 +> 状态:已确认并完成首期原型(2026-07-22,分支 `feat/message-hub`) +> 范围:产品架构规格 + Web 统一消息中心原型(宽首期) + +## 1. 背景与问题 + +当前 OneOS 原型中的「消息/通知」能力偏弱,实质是工作台站内铃铛阅知(`oneos-web-workbench-new` + `oneos-app-shell` 桥),外加若干业务侧短信/邮件演示。缺少: + +- 独立消息中心 +- Web / iOS / 安卓 / 鸿蒙 / 微信服务号的统一触达与跳转模型 +- 多系统消息源(OneOS、车辆数据中台、其他系统)的接入约定 +- 清晰的「详情 → 来源表单/办理页」深链规则(含外部端双 App) + +本设计定义统一消息中枢,并指导首期 Web 原型落地。 + +## 2. 已确认产品决策 + +| 决策点 | 选择 | +|--------|------| +| 本轮交付 | 规格文档 + Web 统一消息中心原型;移动/服务号以路由表与示意为主,不接真推送/服务号 API | +| 产品入口 | 独立「消息中心」菜单页 + 顶栏铃铛作为未读快捷入口(「查看全部」进入消息中心) | +| 首期广度 | 宽首期:五端触达矩阵 + 多系统消息源样例 | +| 跳转解析 | 消息只携带业务键;各端用**本地路由表**解析目标(不下发各端 URL) | +| 消息与触达 | 一条业务事件 = 一条 Message;多通道(站内/推送/服务号)挂在同一条上 | +| 外部双 App | 占位名 `external-app-a` / `external-app-b`,真名后续替换 | +| 落地架构 | 独立消息中枢:`src/common/message-hub/` + 新原型 `message-center`;工作台铃铛改读中枢 | + +## 3. 目标用户与核心任务 + +- **目标用户**:OneOS 内部运营/业务人员(Web 为主);五端与外部 App 在原型中用「端模拟器」与路由表示意。 +- **核心任务**: + 1. 在统一列表中看到来自多系统的待办/提醒类消息 + 2. 打开详情理解上下文与通道触达状态 + 3. 「去处理」按当前端解析并跳到来源办理页(或明确不可跳) + +## 4. 范围 + +### 4.1 本轮做 + +- 统一 Message 模型、通道触达态、本地 RouteRule 与 `resolve()` +- 新原型 `message-center`:筛选、列表、详情、端模拟器、去处理 +- 工作台/外壳铃铛对接同一中枢(旧 notices 迁移或适配) +- 五端 × 多系统种子与路由表样例(含外部 App A/B 占位) +- `.spec` 路由规则全文 + AutoPRD / 导航登记(实现轮) + +### 4.2 本轮不做 + +- 真实推送 SDK、厂商通道、微信服务号模板下发 +- 跨端已读强一致、服务端已读同步 +- 外部 App 真唤端 / Universal Link 联调 +- 车辆中台真实事件流接入(仅占位样例) +- 独立移动端/鸿蒙原生 UI 工程 + +## 5. 架构 + +```text +业务系统(OneOS / vehicle-mid / other / external) + │ 产生业务事件(原型内为种子写入) + ▼ +┌───────────────────┐ +│ message-hub │ Message 池 · ChannelDelivery · RouteRule · resolve() +└─────────┬─────────┘ + │ + ┌─────┴─────┐ + ▼ ▼ +消息中心页 顶栏铃铛 / 外壳 bridge +(完整 IA) (未读摘要 + 快捷) + │ + ▼ +resolve(client) → 原型内导航 / 深链示意 / 不可跳提示 +``` + +### 5.1 目录约定 + +| 路径 | 职责 | +|------|------| +| `src/common/message-hub/` | 类型、种子、路由表、resolve、读写状态(localStorage 可选用) | +| `src/prototypes/message-center/` | 消息中心主 UI | +| `src/prototypes/message-center/.spec/` | 路由规则、通道矩阵、验收 | +| `src/common/oneos-app-shell/` | 铃铛桥升级为 Message 摘要同步(兼容旧字段可选) | +| `src/prototypes/oneos-web-workbench-new/` | 铃铛数据源改为中枢 | + +## 6. 数据模型 + +### 6.1 Message + +| 字段 | 说明 | +|------|------| +| `id` | 稳定 ID | +| `sourceSystem` | `oneos` \| `vehicle-mid` \| `other-system` \| `external` | +| `bizType` | 如 `approval.arrive`、`urge.remind`、`contract.expire`、`fault.overdue`、`order.action` | +| `bizId` | 来源业务主键 | +| `title` / `summary` / `detail` | 展示文案 | +| `priority` | 如 normal / high(催办类可 high) | +| `createdAt` | ISO 时间 | +| `readAt` | 可选;本地已读 | +| `audience` | 角色/用户占位(原型可按角色过滤,对齐现工作台习惯) | +| `channels` | `ChannelDelivery[]` | + +### 6.2 ChannelDelivery + +| 字段 | 说明 | +|------|------| +| `client` | `web` \| `ios` \| `android` \| `harmony` \| `wechat_oa` | +| `status` | `pending` \| `sent` \| `failed` \| `skipped`(原型模拟) | +| `updatedAt` | 可选 | + +说明:通道状态表示「是否对该端做过触达尝试」的演示态,不是跳转 URL。 + +### 6.3 RouteRule + +| 字段 | 说明 | +|------|------| +| `sourceSystem` | 与 Message 一致 | +| `bizType` | 与 Message 一致 | +| `client` | 目标端 | +| `targetTemplate` | URI 模板,支持 `{bizId}` 等占位 | +| `externalApp` | 可选:`external-app-a` \| `external-app-b` | +| `label` | 人读说明 | + +**解析优先级** + +1. 精确匹配 `sourceSystem + bizType + client` +2. 同系统同 `bizType` 的 fallback(如 `harmony` → `android` 模板,若配置了 fallback) +3. 无规则 → 不跳转,UI 明确提示「当前端暂无可跳转目标」 + +**禁止**:根据标题猜路径;无规则时静默跳首页。 + +## 7. 交互与闭环 + +### 7.1 铃铛 + +- 展示未读数与最近 N 条 +- 点击单条:标已读并打开详情卡片(含「去处理」);不在列表行上直接跳转,避免未看清上下文就离开 +- 「查看全部」→ `/prototypes/message-center` + +### 7.2 消息中心页 + +- 左:筛选(全部/未读/已读、系统源、业务类型)+ **端模拟器**(切换 `client`,影响解析预览与去处理) +- 右:列表 + 详情(正文、通道触达态、解析预览、去处理) + +### 7.3 去处理 + +1. 标记已读(本地) +2. `resolve(message, simulatedClient)` +3. 命中且为 Web 原型 path → 站内导航(直链或外壳 `ONEOS_SHELL_NAV`) +4. 命中且为外部/原生 scheme → 展示示意面板(将打开哪一 App/URI),不真唤端 +5. 未命中 → Toast/详情内错误态,文案固定可验收 + +## 8. 种子与路由样例(宽首期) + +消息源至少覆盖: + +- **oneos**:审批到达、催办、合同/证照到期、账单、系统通知(从现有工作台 notices 映射迁入) +- **vehicle-mid**:故障逾期、车辆状态事件各 ≥1 +- **other-system**:通用接入槽 ≥1 +- **external**:跳转 App A、App B 的操作类各 ≥1 + +五端:每条关键 `bizType` 在路由表中为 `web/ios/android/harmony/wechat_oa` 提供模板或显式 `skipped` 说明(如纯 Web 办理类在 wechat_oa 可 skipped + 文案)。 + +## 9. 视觉与设计基底 + +沿用现有 OneOS Web 管理端语言(工作台、vm 列表习惯、外壳顶栏铃铛),不另起营销风或新主题。实现前对照项目 UI 规范;复杂判定写入 `.spec`。 + +## 10. 验收重点 + +1. 消息中心可按系统源/已读态筛选,列表与详情一致 +2. 端模拟器切换后,同一条消息的解析预览随路由表变化 +3. 「去处理」仅在命中规则时跳转;未命中有明确提示 +4. 外部 App A/B 仅示意,不真唤端 +5. 铃铛未读数与中枢一致;「查看全部」进入消息中心 +6. 旧工作台通知种子可在中枢中看到对应映射条目 +7. 规格中路由优先级表与代码 `resolve()` 行为一致 + +## 11. 风险与后续 + +- 真名替换 `external-app-a/b` 时只改路由表与文案,不改模型 +- 接真 API 时 Message 写入改为服务端,客户端仍只持业务键 + 本地/下发的路由表副本 +- 跨端已读、推送回执可作为下一阶段,不阻塞本轮原型 + +## 12. 决策快照(对齐记录) + +| 问题 | 用户选择 | +|------|----------| +| 交付深度 | 规格 + Web 消息中心原型 | +| 与铃铛关系 | 独立消息中心 + 铃铛快捷 | +| 首期广度 | 宽(五端 + 多系统) | +| 跳转方式 | 业务键 + 本地路由表(由「服务端多 URL」改为此案) | +| 消息与推送 | 一条事件多通道同一条消息 | +| 外部 App | 占位 A/B | +| 架构 | 独立 message-hub + message-center 原型 | diff --git a/docs/superpowers/specs/2026-07-22-oneos-design-system-v2-design.md b/docs/superpowers/specs/2026-07-22-oneos-design-system-v2-design.md new file mode 100644 index 0000000..0f8f550 --- /dev/null +++ b/docs/superpowers/specs/2026-07-22-oneos-design-system-v2-design.md @@ -0,0 +1,63 @@ +# OneOS 设计规范 v2 · 设计决策 + +| 项 | 内容 | +|----|------| +| 日期 | 2026-07-22 | +| 状态 | 已落地文档包 | +| 产物 | `src/resources/design-system/`(DESIGN.md + chapters/00–09 + tokens.json + oneos-ds-tokens.css) | + +## 背景 + +既有 v1 规范仅覆盖中后台列表页(筛选 / KPI / 表 / 分页)。产品需要基于当前平台重做设计规范,约束 Web 与 App 全部页面的显示与组件,实现全站统一。 + +## 决策 + +### App 范围 + +**原生 App + 微信小程序 + H5 现场端**(三端全含),与仓库中 `vehicle-inspection-app` / `miniprogram` / `web` 及 PC 中后台并列。 + +### 文档策略 + +在现有 [`src/resources/design-system/`](../../src/resources/design-system/) **扩写为 v2**,不另建 `design-system-v2` 目录。 + +- 根 `DESIGN.md`:总览与导航 +- `chapters/00–09`:完整约束清单落地 +- v1 列表强制规则并入 `04-patterns.md`,并继续指向 `vm-shared/DESIGN.md` + +### 主色 + +沿用原型现行 **`#32a06e`**(`vehicle-management` / `field-theme`)。 +`oneos-web-legacy` 的 `#10b981` 标为遗留,新页禁止。 + +### 暗色模式 + +本版明确 **仅浅色**;若未来需要,须先补映射表。 + +### 工程入口 + +- `tokens.json` schemaVersion **2** +- 新增 `oneos-ds-tokens.css` 供非列表页与共享变量引用 +- 列表页仍强制 `vehicle-management/style.css` + 公共组件 + +## 章节结构 + +```text +00 总则 → 01 Foundations → 02 Layout → 03 Components +→ 04 Patterns → 05 Content → 06 A11y → 07 Platform +→ 08 Tokens 附录 → 09 迁移检查清单 +``` + +编写优先级见 `PRIORITY.md`:Foundations / Tokens → Layout 模板 → Patterns → Components → 平台差异 → Content/A11y → 检查清单。 + +## 成功标准 + +- 任意新页可定位到唯一页面模板与 Token 来源 +- Web 列表验收项不弱于 v1 +- App/小程序/H5 有热区、安全区、导航对照表 +- AI 提示词指向 v2 章节 + +## 非目标(本轮) + +- 不批量改既有业务页面样式代码 +- 不实现暗色主题 +- 不合并改造 legacy `oneos-tokens.css` 文件内容(仅在规范中标注弃用路径) diff --git a/docs/superpowers/specs/2026-07-22-vehicle-fault-handling-design.md b/docs/superpowers/specs/2026-07-22-vehicle-fault-handling-design.md new file mode 100644 index 0000000..f76b42c --- /dev/null +++ b/docs/superpowers/specs/2026-07-22-vehicle-fault-handling-design.md @@ -0,0 +1,214 @@ +# 故障处置(车辆运维)设计规格 + +| 项 | 说明 | +|---|---| +| 日期 | 2026-07-22 | +| 状态 | 已确认 | +| 方案 | 方案一:新建原型 + 工作台同源数据 | +| 原型 ID(拟定) | `vehicle-fault-handling` | +| 菜单 | OneOS → 车辆运维 → 故障处置 | +| 关联 | `oneos-web-workbench-new`(预警 KPI + 运维看板故障登记) | + +--- + +## 1. 问题与目标 + +运维需要承接 **AI 故障机器人**上报的原始故障(聊天摘要、图片、视频),在系统内完成处置、挂起与归档,并满足 **30 天闭环** 时限与催办。处置记录须足够详细,作为日后向整车厂索赔的**证据链**基础。 + +**本期目标** + +1. 新建「故障处置」列表 + **独立详情页**,完成待处理 → 处理中 → 挂起 → 已归档。 +2. 证据链多附件与归档硬门槛。 +3. 时限与通知规则演示(含短信/邮件模板与发送记录,不接真实通道)。 +4. 工作台预警 KPI + 运维看板「故障登记」与本模块同口径。 + +**非目标(二期 / 占位)** + +- AI 对话上报界面(机器人仅作数据来源) +- 真实短信 / 邮件网关 +- 品牌 · 车型 · 部位 · 等级完整分类统计看板 +- 按品牌车型一键出具故障证明与自动分析结论(本期仅入口占位) + +--- + +## 2. 信息架构 + +```text +OneOS +└── 车辆运维(新一级菜单) + ├── 故障处置 ← 本期主功能 + ├── 分类统计 ← 二期占位 + └── 故障证明 ← 二期占位 +``` + +- 新建原型目录:`src/prototypes/vehicle-fault-handling/` +- 不改造旧合包 `oneos-web-ops` 内「故障管理」页为唯一入口;旧页可暂并存,导航以新菜单为准。 +- AI 上报:无 UI;以种子数据模拟「已入库的待处理原始记录」。 + +--- + +## 3. 状态机 + +| 状态 | 进入条件 | +|------|----------| +| 待处理 | AI 上报后系统自动入库 | +| 处理中 | 运维首次保存处置内容(可分次保存) | +| 挂起 | 运维选择暂时挂起,**必须填写挂起原因** | +| 已归档 | 通过归档硬门槛,完成闭环 | + +**流转规则** + +- `待处理` → 首次保存 → `处理中` +- `待处理` / `处理中` → 挂起 → `挂起` +- `挂起` → 恢复继续 → `处理中` +- `处理中` → 归档校验通过 → `已归档` +- **挂起不可直接归档**;须先恢复为处理中,补齐硬门槛后再归档 + +--- + +## 4. 时限规则(文档中的「SLA」含义) + +此处 **SLA = 处理时效约定**,非另有系统模块: + +- 时针从 **故障记录上报时刻** 起算,**30 个自然日内必须已归档** +- **暂时挂起不停表**(挂起期间仍计入 30 天) + +| 时机 | 条件 | 收件人 | 通道 | 频次 | +|------|------|--------|------|------| +| 临期 | 未归档,且剩余天数 ≤ 7(未逾期),进入临期窗口时触发 | 当前处理人;无人接手 → 运维主管 | 短信 + 邮件 | 进入临期窗口触发一次 | +| 逾期升级 | 已超 30 天,仍未归档 | **固定运维主管** | 短信 + 邮件 | 逾期当天 1 次;之后 **每周 1 次**,直至归档 | + +原型:展示「通知记录」列表与模板正文;标记「已发送(演示)」,不接真实通道。 + +### 4.1 通知模板 + +**临 7 天 · 短信** + +```text +【OneOS故障】${故障号} ${车牌/车型} 将于 ${截止日} 到期(剩7天)仍未归档,请尽快处置。详情见故障处置。 +``` + +**临 7 天 · 邮件** + +- 标题:`【故障临期提醒】${故障号} 距闭环截止还剩 7 天` +- 正文须含:故障号、车辆、上报时间、当前状态、截止日、处理人、系统链接 + +**超 30 天 · 短信** + +```text +【OneOS故障·逾期】${故障号} 已超 30 天未归档(状态:${状态}),请主管督促闭环。 +``` + +**超 30 天 · 邮件** + +- 标题:`【故障逾期升级】${故障号} 已超过 30 天未归档` +- 正文须含:上述字段 + 逾期天数、历史挂起原因摘要、末次催办时间 + +--- + +## 5. 页面结构 + +采用 **列表 + 独立详情页**(非抽屉)。 + +### 5.1 列表页 + +- 筛选:状态、等级、品牌/车型、临期、逾期 +- 搜索:故障号、车牌 +- 列:故障号/车辆、部位·等级、状态、剩余天数(或逾期天数)、操作「处置」 +- 临期 / 逾期视觉标识 + +### 5.2 详情页分区 + +1. **原始记录(只读)**:AI 聊天摘要、图、视频 +2. **处置主档**:故障时间、地点、部位、等级、处置结果、备注、关联车辆(品牌/车型/车牌) +3. **证据链**:多附件(图片、视频、PDF、Word 等常用类型);可预览示意;记录文件名、类型、上传人、时间 +4. **挂起与通知**:挂起原因历史;短信/邮件发送记录 +5. **底栏**:保存(→ 处理中)· 挂起 · 归档(硬门槛) + +### 5.3 归档硬门槛 + +必须全部满足方可归档: + +- 故障时间、地点、部位、等级、处置结果 +- 至少 **1** 个证据附件 +- 备注选填 + +任一缺失:阻断归档并提示缺项。 + +--- + +## 6. 工作台接入 + +数据与 `vehicle-fault-handling` **同源**(本地共享种子 / `src/common` 模块,未接真实 API)。 + +### 6.1 欢迎区预警 KPI(运维角色可见;多角色并集规则沿用工作台现规) + +| 指标 | 口径 | 点击 | +|------|------|------| +| 临 7 天未归档 | 未归档且剩余天数 ≤ 7 且未逾期 | 故障处置列表 · 临期筛选 | +| 已超 30 天未归档 | 未归档且已过截止日 | 列表 · 逾期筛选 | +| 待处理积压 | 状态 = 待处理 | 列表 · 待处理 | + +### 6.2 运维看板「故障登记」 + +- 总数 = 全部故障记录(含已归档) +- 已归档 = 状态为已归档 +- 闭环率 = 已归档 / 总数 +- 可辅显:挂起数、临期数 +- 可见性:主管/兼岗全量 + 按人筛;普通运维本人/本地区(沿用现有 `OpsCockpit` 规则) + +--- + +## 7. 数据对象(摘要) + +`FaultRecord` 主要字段: + +- 标识:故障号、上报时间、截止时间、来源(AI) +- 状态与处理人 +- 原始:聊天摘要、媒体列表 +- 处置:时间、地点、部位、等级、结果、备注、车辆引用 +- 证据附件列表、挂起历史、通知历史 + +二期统计 / 证明所需的品牌、车型、部位、等级字段在一期处置主档中已采集,证明生成逻辑本期不做。 + +--- + +## 8. 组件与实现边界(实现阶段指引) + +| 单元 | 职责 | 依赖 | +|------|------|------| +| `vehicle-fault-handling` 原型 | 列表、详情、状态流转、证据链、通知演示 | 共享故障种子 | +| 共享数据模块(如 `src/common/vehicle-fault/`) | 记录类型、状态派生、时限计算、看板/KPI 聚合 | 无真实 API | +| `oneos-web-workbench-new` | KPI 瓦片 + OpsCockpit 故障列改读共享数据 | 共享模块 | +| 导航 | `nav-menu.json` 增加车辆运维 / 故障处置;`nav:sync` | — | + +错误处理:归档校验失败明确列出缺项;挂起无原因不可提交。 + +--- + +## 9. 已确认决策记录 + +| 决策点 | 选择 | +|--------|------| +| AI 端 | 仅数据来源,不做对话 UI | +| 落地路径 | 方案一:新原型 + 工作台同源 | +| 菜单 | 车辆运维 → 故障处置 | +| 中间态名称 | 处理中 | +| 挂起与时针 | 不停表 | +| 临 7 天含义 | 未归档且剩余天数 ≤ 7(未逾期) | +| 一期范围 | 处置闭环 + 证据链;统计/证明占位 | +| 归档 | 硬门槛 | +| 工作台 | 预警 + 看板都接 | +| 临期收件人 | 处理人,无人接手→主管 | +| 逾期 | 固定主管;当天 1 次 + 每周 1 次 | +| 详情布局 | 列表 + 独立详情页 | + +--- + +## 10. 验收重点(设计层) + +1. 种子数据入库即为待处理;保存后为处理中;挂起必填原因;归档卡硬门槛。 +2. 列表可按临期/逾期筛选;详情可看原始记录与证据链。 +3. 通知记录可演示临期与逾期模板内容。 +4. 工作台运维角色可见三类故障预警;看板故障登记闭环率与模块数据一致。 +5. 菜单路径正确;二期入口为占位说明。 diff --git a/docs/superpowers/specs/2026-07-22-vehicle-purchase-inspection-linear-ui-design.md b/docs/superpowers/specs/2026-07-22-vehicle-purchase-inspection-linear-ui-design.md new file mode 100644 index 0000000..53368b1 --- /dev/null +++ b/docs/superpowers/specs/2026-07-22-vehicle-purchase-inspection-linear-ui-design.md @@ -0,0 +1,178 @@ +# 车辆采购合同 + 验车入库 · UI 对齐 Linear(产品浅色)设计规格 + +**日期:** 2026-07-22 +**状态:** 规格已确认 · UI 已按本规格落地(2026-07-22) +**范围决议:** 全链路(PC 两端 + 外勤三端)· 一波交付 · 列表壳 vm-page · 设计基底 Linear 产品浅色 + +--- + +## 1. 背景与目标 + +现有 `vehicle-purchase-contract`、`vehicle-inspection` 及根下迁入的外勤三端(`vehicle-inspection-{web,miniprogram,app}`)功能已通,但视觉与交互未对齐项目标准主题与列表壳规范:硬编码 Ant Design `#10b981`、自定义 `vpc-`/`vi-` 页头与筛选条、与车辆管理 / 保险采购等业务页不一致。 + +**目标:** 在**不改业务规则**的前提下,按 Linear 产品浅色 token + vm-page 壳层,一波重做 PC 列表/表单/详情与外勤三端视觉与布局,使采购→验车→外勤→入库链路观感统一、可验收。 + +**非目标:** 不改审批阈值、VIN 校验、拒收重交、入库桥、分期付款逻辑;不上 Linear 营销深色 + 薰衣草主色;不单独进导航的外勤页仍从验车详情链出。 + +--- + +## 2. 设计事实源(优先级) + +| 优先级 | 来源 | 用途 | +|--------|------|------| +| 1 | `src/themes/linear/DESIGN.md` | 品牌与组件原则事实源(标准参考主题) | +| 2 | `src/prototypes/vehicle-management/style.css` 中 `--ln-*` / `--vm-*` | **产品浅色落地**(inverse 白底 + 主色 `#32a06e`) | +| 3 | `src/prototypes/vm-shared/DESIGN.md` | 列表壳结构:筛选卡、工具栏、分页、禁止重复大标题等 | +| 4 | 既有参考页 | 保险采购 / 租赁合同列表与详情分区习惯 | + +**明确采用的 Linear 落地方式(已确认):** +选项 1 — Linear 产品浅色,与车辆管理一致;**不**采用官网深色 `#010102` + `#5e6ad2`。 + +**ConfigProvider:** `colorPrimary: '#32a06e'`(或读 CSS 变量等价),字体栈对齐 `--vm-font`(Inter + 系统/苹方)。 + +--- + +## 3. 信息架构与页面结构 + +### 3.1 车辆采购合同(`vehicle-purchase-contract`) + +**列表(默认)** + +```text +vm-page vpc-page +├── 筛选卡(「筛选条件」):关键词、状态、审批类型;查询 / 重置 +├── KPI 行(可选可点筛选):草稿 / 待审或审批中 / 已通过 / 已生成验车 +└── vm-table-section + ├── 工具栏右对齐:新建合同 + ├── 表格 + OperationActions(查看 / 编辑 / 提交审批 / 发起工单等既有动作) + └── TablePagination +``` + +- 列表页**禁止**独立 h1 大标题 + 功能说明副标题(侧栏已标明模块名)。 +- 金额、数量列使用 `tabular-nums` / `ln-numeric` 既有约定。 + +**创建 / 编辑 / 查看** + +```text +顶栏:返回列表 + 标题(新建/编辑/查看 + 合同编号)+ 主操作(保存草稿 / 提交 / 关闭) +主体白底分区(hairline + radius-card): + 1. 合同主体(买/卖/丙方与角色) + 2. 车型与价格(型号编码、产地、单价数量、实时总价与审批类型提示条) + 3. 分期付款计划 + 4. 交付与验车约定 + 5. 质保 / 关联协议 / 附件 +``` + +- ≥500 万自动「非正常审批」提示条保留,样式用 `--ln-primary-soft` / success 语义,不用另套色板。 +- 查看态只读;草稿可编辑;状态驱动操作按钮与现逻辑一致。 + +### 3.2 验车入库(`vehicle-inspection`) + +**列表** + +```text +vm-page vi-page +├── 筛选卡:关键词、任务状态;查询 / 重置 +├── KPI:待验车 / 验车中 / 已完成 +└── 表格 + 操作(进入详情 / 结案等)+ TablePagination +``` + +**详情** + +```text +顶栏:返回 + 任务编号/车型摘要 + 结案、入库等主操作 +├── 任务摘要(Descriptions / 分区字段,对齐交接验收表语境) +├── 外勤入口:Web / 小程序 / APP(点击绑定 activeInspectionId 并打开 /prototypes/.../index.html) +└── 车辆明细表(VIN、里程、电机号、状态、拒收原因、重交操作) +``` + +### 3.3 外勤三端(同波) + +| 原型路径 | 壳 | +|----------|-----| +| `src/prototypes/vehicle-inspection-web/` | Web 外勤 | +| `src/prototypes/vehicle-inspection-miniprogram/` | 小程序壳 | +| `src/prototypes/vehicle-inspection-app/` | APP 壳 | + +- 视觉:Linear 浅色语义(主色绿、白/浅灰 canvas、hairline 卡片);字号 ≥16px;触控目标 ≥44px;`cursor-pointer`;尊重 `prefers-reduced-motion`。 +- 逻辑:继续共用 `field-core.js`;《车辆交接验收表》图例 √/N/×/O、双签、双角度照片、拒收重交**行为不变**。 +- 三端仅外框/底栏差异,表单与列表结构对齐,避免三套互不相干的样式常量。 + +--- + +## 4. 视觉与组件规则(摘要) + +| 项 | 约定 | +|----|------| +| 主色 | `#32a06e`(`--ln-primary`) | +| 画布 | `--ln-canvas-parchment` / `--vm-bg` | +| 卡片面 | `--ln-surface-card` + `--ln-hairline` + radius 8/12 | +| 正文/次要 | `--ln-ink` / `--ln-muted` | +| 成功/警告/错误 | `--ln-success` / `--ln-warning` / `--ln-error` | +| 焦点 | 控件边框改主色(vm-focus-border 模式),避免装饰性外发光 | +| 图标 | Lucide/SVG,不用 emoji 作图标 | +| 列表操作列 | `OperationActions` + 既有 `vm-operation-actions` | + +--- + +## 5. 业务规则(本轮冻结,仅 UI) + +以下保持现有实现与 `.spec` 文档,**本轮不改判定**: + +- 合同金额 ≥ 500 万 → 非正常审批,否则正常 +- 1 合同 → 1 验车任务;明细按到车追加 +- VIN 17 位;交接验收表通过条件;拒收 → 重交 → 重验 +- localStorage 桥:`oneos.vp.*`(含 `activeInspectionId`) + +若 UI 改动导致文案/验收描述变化,**同轮**按 oneos-autoprd 与业务逻辑文档化规则更新 PRD / annotation(纯样式可跳过全量重写;分区标题、状态文案有语义变化则更新)。 + +--- + +## 6. 文件与依赖(实现边界) + +**预期修改** + +- `src/prototypes/vehicle-purchase-contract/VehiclePurchaseContractApp.tsx` + `styles/index.css`(收敛到 vm-/ln- 类,去掉重复页头) +- `src/prototypes/vehicle-inspection/VehicleInspectionApp.tsx` + `styles/index.css` +- 外勤三端 `index.html` / 内联或共享样式(可抽公共 CSS 片段到三端复制或单文件引用,以静态可访问为准) +- 可选:各原型补 `DESIGN.md` 一页,声明「基底 = linear 产品浅色 + vm-shared」 + +**预期不改(除非发现硬编码色破坏一致性)** + +- `src/common/vehicle-purchase/*` 业务逻辑 +- 审批 mock、工单预填、入库桥算法 + +**依赖:** 继续 `import '../vehicle-management/style.css'`;`TablePagination`;`OperationActions`;`PrototypeAnnotationHost`。 + +--- + +## 7. 验收要点 + +1. 两列表页:无重复大标题;筛选卡 +(有)KPI + 底部分页;主色为 `#32a06e`。 +2. 合同表单/详情:分区清晰;审批提示条可见且色板正确。 +3. 验车详情:外勤三按钮可打开 `/prototypes/vehicle-inspection-*/index.html` 且绑定当前任务。 +4. 外勤三端:交接验收表可填报;与 PC 同域 localStorage 互通。 +5. 对照车辆管理列表:间距、圆角、边框、字体观感一致(允许业务字段不同)。 +6. 对比度正文 ≥ 4.5:1;可点击元素有 pointer;外勤触控区达标。 + +--- + +## 8. 决议记录 + +| 项 | 选择 | +|----|------| +| 优化深度 | C 全链路(含外勤) | +| 排期 | 一波做完 | +| PC 路线 | A 严格对齐 vm-page | +| Linear 落地 | 1 产品浅色(`--ln-*` / `#32a06e`) | +| 产品确认草案 | 2026-07-22 认可 | + +--- + +## 9. 自我检查(写规格时) + +- [x] 无 TBD 占位关键路径 +- [x] 未混用深色薰衣草与浅色绿主色 +- [x] 业务规则与 UI 边界写清 +- [x] 外勤 URL 与同波交付写清 +- [x] 与用户确认的 C / 一波 / A / Linear-1 一致 diff --git a/docs/superpowers/specs/2026-07-22-vehicle-return-settlement-ops-transfer-design.md b/docs/superpowers/specs/2026-07-22-vehicle-return-settlement-ops-transfer-design.md index 7af07fa..d4bb904 100644 --- a/docs/superpowers/specs/2026-07-22-vehicle-return-settlement-ops-transfer-design.md +++ b/docs/superpowers/specs/2026-07-22-vehicle-return-settlement-ops-transfer-design.md @@ -90,7 +90,7 @@ | 3 | 整单待审批 / 审批中 | 转交可用;运维费用只读;**不可**部门撤回改数 | | 4 | 新办理人要改费用(整单未在审批中) | 若段状态为已提交 → 先走运维部 **撤回**,再编辑保存/提交 | | 5 | 整单在审批中要改费用 | 先 **整单撤回**,再进入费用明细编辑 | -| 6 | 整单审批完成 | 无转交按钮 | +| 6 | 整单审批完成 | 无转交图标 | ### 3.5 原因 @@ -103,19 +103,21 @@ ### 4.1 入口 -费用明细页 → **运维部**卡片标题栏右侧: +费用明细页 → **运维组**卡片标题栏: | 场景 | 可见操作(在权限内) | |------|----------------------| -| 整单可编 + 运维待提交 | 保存、提交、**转交** | -| 整单可编 + 运维已提交 | 撤回、**转交** | -| 整单待审批 / 审批中 | 费用只读;无保存/提交/撤回;办理人/主管可见 **转交** | -| 整单审批完成 | 无转交 | +| 整单可编 + 运维待提交 | 保存、提交;提交人姓名后 **转交图标** | +| 整单可编 + 运维已提交 | 撤回;提交人姓名后 **转交图标** | +| 整单待审批 / 审批中 | 费用只读;无保存/提交/撤回;提交人姓名后 **转交图标**(办理人/主管) | +| 整单审批完成 | 无转交图标 | + +交互:点击转交图标打开转交弹窗。**不提供**「转交记录」入口。 列表页: -- 待审批 / 审批中:**显示「费用明细」**(不限角色),与现网「隐藏」不同,以本规格为准 -- 运维组办理人展示随 `opsHandler` 更新 +- 待审批 / 审批中:**显示「费用明细」**(不限角色) +- 运维组办理人展示随 `opsHandler` 更新 - **不**增加列表「转交」操作、**不**做批量 ### 4.2 转交弹窗 @@ -131,9 +133,7 @@ ### 4.3 转交记录 -每笔至少记录:时间、操作人、原办理人、新办理人、原因、当时运维段状态、当时整单审批状态。 - -费用明细运维卡片提供「转交记录」入口(时间线或抽屉即可)。 +本期**不提供**转交记录查询 UI(无抽屉/时间线)。转交原因仍必填,用于确认弹窗与后台留痕(原型可不展示)。 --- @@ -144,7 +144,7 @@ | `opsHandler` | **当前办理人**(权威)。初始可由还车人 `returnPerson` 带入;转交只改本字段,**不改**历史还车人 | | `submitOperation.status` | 运维段提交状态(待提交 / 已提交等既有口径) | | `submitOperation.name` | **最近一次提交人**;转交不覆盖;新人再次提交时再更新 | -| `opsTransferLogs[]` | 转交记录数组(见 4.3) | +| `opsTransferLogs[]` | 可选后台留痕;**本期无查询 UI** | 列表「运维组」办理人解析:读 `opsHandler`,不再直接绑死 `returnPerson`。 @@ -169,9 +169,9 @@ 2. 已提交单可转交;新人可撤回后编辑再提交。 3. 主管可代转「非本人」的运维段任务。 4. 整单待审批 / 审批中:列表可进费用明细(不限角色);运维费用只读不可编;办理人/主管仍可转交;费用与审批流不变。 -5. 审批完成后无转交按钮。 +5. 审批完成后无转交图标。 6. 不可转给自己或非运维人员;原因为空不可提交。 -7. 转交记录可查(时间、人、原因、状态快照)。 +7. 转交入口为提交人姓名后方图标;无「转交记录」入口。 --- diff --git a/oneos-v2/src/prototypes/oneos-v2/DesignSystemShowcase.tsx b/oneos-v2/src/prototypes/oneos-v2/DesignSystemShowcase.tsx new file mode 100644 index 0000000..1beb90d --- /dev/null +++ b/oneos-v2/src/prototypes/oneos-v2/DesignSystemShowcase.tsx @@ -0,0 +1,2300 @@ +import React, { useState, useEffect } from 'react'; +import { + Palette, + Type, + Sliders, + Calendar, + Clock, + CheckSquare, + Radio as RadioIcon, + ToggleLeft, + ChevronDown, + ChevronLeft, + ChevronRight, + Search, + Plus, + Download, + Filter, + Eye, + Check, + X, + FileText, + Truck, + Sun, + Moon, + Layers, + LayoutGrid, + Kanban, + Columns, + Sparkles, + Copy, + CheckCircle2, + XCircle, + Clock3, + Smartphone, + Monitor, + Home, + User, + Share2, + MoreHorizontal, + Bell +} from 'lucide-react'; +import '../../resources/design-system/oneos-ds-tokens.css'; +import LeaseContractHub from '../lease-contract-management/LeaseContractHub'; +import { + V2Select, + V2DatePicker, + V2DateRangePicker, + V2SingleInputDateRangePicker, + V2TimePicker, + V2SingleInputTimeRangePicker, + V2RadioGroup, + V2CheckboxGroup, + V2Switch, + V2Steps, + V2Timeline, + V2ApprovalProgress, + V2MobileHeader, + V2MobileBottomNav, + V2MobileActionBar +} from './UIComponents'; + +const COMPANY_OPTIONS = [ + { value: '羚牛氢能(浙江)新能源科技有限公司', label: '羚牛氢能(浙江)新能源科技有限公司' }, + { value: '羚牛氢能(上海)物流有限公司', label: '羚牛氢能(上海)物流有限公司' }, + { value: '嘉兴氢能运力供应链有限公司', label: '嘉兴氢能运力供应链有限公司' }, +]; + +const TEMPLATE_OPTIONS = [ + { value: '标准车辆租赁合同书模板', label: '标准车辆租赁合同书模板' }, + { value: '试用期转正式车辆租赁合同书模板', label: '试用期转正式车辆租赁合同书模板' }, + { value: '现代18吨氢能卡车专章协议', label: '现代18吨氢能卡车专章协议' }, +]; + +const STATUS_OPTIONS = [ + { value: '草稿', label: '草稿未提交' }, + { value: '待审批', label: '待审批与盖章' }, + { value: '履约执行中', label: '履约执行中' }, + { value: '审批通过', label: '审批通过' }, + { value: '超期终止', label: '已超期终止' }, +]; + +export const DesignSystemShowcase: React.FC = () => { + // Main Top Mode Tab + const [topTab, setTopTab] = useState<'showcase' | 'master_page'>('showcase'); + + // Device Viewport Mode ('pc' | 'h5_375' | 'h5_390') + const [viewportMode, setViewportMode] = useState<'pc' | 'h5_375' | 'h5_390'>('pc'); + + // Theme Mode + const [isDark, setIsDark] = useState(false); + + // Active Showcase Anchor + const [activeSection, setActiveSection] = useState('tokens'); + + // Interactive UI Controls Demo States + const [inputText, setInputText] = useState('LC-2026-001'); + const [inputStatus, setInputStatus] = useState<'default' | 'focus' | 'error'>('default'); + const [textareaText, setTextareaText] = useState('该租赁合同包含 3 台氢能重卡,需安排本月 28 号前完成交付核验。'); + const [singleSelect, setSingleSelect] = useState('羚牛氢能(浙江)新能源科技有限公司'); + const [searchSelect, setSearchSelect] = useState('试用期转正式车辆租赁合同书模板'); + const [multiSelect, setMultiSelect] = useState(['待审批', '履约执行中']); + const [radioVal, setRadioVal] = useState('formal'); + const [checkboxState, setCheckboxState] = useState<{ [key: string]: boolean }>({ + c1: true, + c2: false, + c3: true, + }); + const [switchVal, setSwitchVal] = useState(true); + + // Date & Time Pickers Demo States + const [singleDate, setSingleDate] = useState('2026-07-23'); + const [dateRangeStart, setDateRangeStart] = useState('2026-06-01'); + const [dateRangeEnd, setDateRangeEnd] = useState('2026-06-30'); + const [singleInputStart, setSingleInputStart] = useState('2026-06-01'); + const [singleInputEnd, setSingleInputEnd] = useState('2026-07-31'); + const [timeRangeStart, setTimeRangeStart] = useState('08:30:00'); + const [timeRangeEnd, setTimeRangeEnd] = useState('17:30:00'); + const [timeVal, setTimeVal] = useState('14:30:00'); + const [dateTimeVal, setDateTimeVal] = useState('2026-07-23 14:30:00'); + const [amountVal, setAmountVal] = useState(1250000); + + // Steps, Timeline & Approval Progress Demo States + const [currentStep, setCurrentStep] = useState(1); + const [stepDirection, setStepDirection] = useState<'horizontal' | 'vertical'>('horizontal'); + + // H5 Mobile Navigation Demo States + const [mobileTabKey, setMobileTabKey] = useState('contracts'); + const [mobileHeaderTitle, setMobileHeaderTitle] = useState('车辆租赁合同详情'); + const [mobileActionToast, setMobileActionToast] = useState(''); + const [stepStatus, setStepStatus] = useState<'finish' | 'process' | 'wait' | 'error'>('process'); + + // FilterBar & Table Demo States + const [filterExpanded, setFilterExpanded] = useState(true); + const [tableSubExpanded, setTableSubExpanded] = useState(true); + const [miniViewMode, setMiniViewMode] = useState<'list' | 'kanban' | 'split'>('list'); + + // Disabled Demo State + const [demoDisabled, setDemoDisabled] = useState(true); + + // Code Copy Alert + const [copiedKey, setCopiedKey] = useState(null); + + const isH5 = viewportMode !== 'pc'; + + // Toggle dark mode on html + useEffect(() => { + const root = document.documentElement; + if (isDark) { + root.dataset.dsMode = 'dark'; + root.dataset.oneosTheme = 'dark'; + root.classList.add('dark'); + } else { + root.dataset.dsMode = 'light'; + root.dataset.oneosTheme = 'light'; + root.classList.remove('dark'); + } + }, [isDark]); + + const copyToClipboard = (text: string, key: string) => { + navigator.clipboard.writeText(text); + setCopiedKey(key); + setTimeout(() => setCopiedKey(null), 2000); + }; + + // Color Swatch Component + const ColorSwatch = ({ title, varName, hex, desc }: { title: string; varName: string; hex: string; desc: string }) => ( +
+
+
+
+
+
{title}
+
{varName}
+
+
+ +
+
+ {hex} + {desc} +
+
+ ); + + const showcaseContent = ( +
+ {/* Quick Anchor Sub-Nav */} +
+ {[ + { id: 'tokens', label: '🎨 色彩 & Tokens', icon: Palette }, + { id: 'typography', label: '🔤 字体 & 排版', icon: Type }, + { id: 'controls', label: '🎛️ 基础 UI 控件', icon: Sliders }, + { id: 'disabled_states', label: '🚫 控件禁用状态与灰度标准', icon: XCircle }, + { id: 'datepicker', label: '📅 日期 & 时间', icon: Calendar }, + { id: 'buttons', label: '🔘 按钮 & 操作', icon: CheckSquare }, + { id: 'mobile_nav', label: '📱 H5 移动端导航', icon: Smartphone }, + { id: 'badges', label: '🏷️ 状态徽章 Pills', icon: RadioIcon }, + { id: 'composite', label: '📊 13项筛选 & 子表', icon: Filter }, + { id: 'steps_timeline', label: '🪜 步骤条/时间轴/审批', icon: Layers }, + { id: 'views', label: '📐 三视角模板', icon: LayoutGrid }, + { id: 'docs', label: '📄 规范文件路径', icon: FileText }, + ].map((item) => { + const Icon = item.icon; + const isActive = activeSection === item.id; + return ( + + ); + })} +
+ + {/* Section 1: Colors & Tokens */} +
+
+
+ +
+
+

+ 1. 色彩体系与 Design Tokens (Global Color Palette) +

+

+ 以 Stripe Violet 品牌紫(`#533AFD`)为全局第一主色,配合精细的底色与双色模式 Semantic Tokens +

+
+
+ +
+ + + + + + + + + + + + + + + + +
+
+ + {/* Section 2: Typography & Numeric Format */} +
+
+
+ +
+
+

+ 2. 字体排版与 Tabular Nums 数字格式 (Typography & Tabular Numbers) +

+

+ 所有金额、单价、车辆数、VIN 码、时间与日期统一采用等宽字体 `tabular-nums`,移动端正文 ≥ 14px 止自动缩放 +

+
+
+ +
+
+

+ 字号阶梯 (Font Scale) +

+
+
+ Display (24px) + ¥ 2,780,000.00 +
+
+ Head-Lg (18px) + 租赁合同管理与车辆资产枢纽 +
+
+ Head-Sm (16px) + 羚牛氢能 (浙江) 运营项目 +
+
+ Subhead (14px) + 浙A88888F - 49吨氢能重卡 +
+
+ Body-Base (14px H5) + 标准文本、表单录入文本、表格明细 +
+
+ Caption (12px H5) + 次要辅助标注、时间戳、SLA 倒计时微标 +
+
+
+ +
+

+ 等宽数字对齐演示 (Tabular Nums) +

+

+ 全局强制使用 `JetBrains Mono` / `SFMono-Regular` 结合 `font-variant-numeric: tabular-nums`,确保高密度数值对齐不错位。 +

+ +
+
+ 车牌 / VIN 码 + 未格式化 + Tabular Nums +
+ {[ + { plate: '浙A88888F', raw: '¥ 111,111.11', tabular: '¥ 111,111.11' }, + { plate: '浙A99999F', raw: '¥ 888,888.88', tabular: '¥ 888,888.88' }, + { plate: '沪B12345F', raw: '¥ 100,000.00', tabular: '¥ 100,000.00' }, + ].map((row, idx) => ( +
+ {row.plate} + {row.raw} + + {row.tabular} + +
+ ))} +
+
+
+
+ + {/* Section 3: Basic UI Controls */} +
+
+
+ +
+
+

+ 3. 全量基础 UI 控件组件库 (Basic UI Input Controls) +

+

+ 符合 OneOS V2 规范的高规 custom React 控件(触控高 ≥ 44px、H5 移动端自动唤起底部 Sheet Popover 面板) +

+
+
+ +
+ {/* Card 1: Input & Textarea */} +
+

+ + 文本输入框与文本域 (Input & Textarea) +

+ +
+ + setInputText(e.target.value)} + className={`ds-input ${inputStatus === 'error' ? 'ds-input-error' : ''}`} + onFocus={() => setInputStatus('focus')} + onBlur={() => setInputStatus('default')} + /> +
+ +
+ +
+ + +
+
+ +
+ + + +
+ + +
+
    + +
    双方确认
    + + + +
    + + +
    +
    `; + + const evs = []; + document.getElementById('back').onclick = () => { + view = { name: 'list' }; + render(); + }; + document.getElementById('ocr').onclick = () => { + document.getElementById('vin').value = VPField.mockOcr(ocrIdx++); + toast('车架号 OCR 回填(模拟)'); + }; + document.getElementById('all-ok').onclick = () => { + document.querySelectorAll('[data-item]').forEach((row) => { + const ok = row.querySelector('input[value=ok]'); + if (ok) ok.checked = true; + }); + toast('已全部标记 √'); + }; + document.getElementById('ev-l').onclick = () => { + evs.push({ uid: 'ev-l-' + Date.now(), name: '左前45度.jpg', kind: 'photo', photoAngle: 'left45' }); + document.getElementById('ev-list').innerHTML = evs.map((e) => `
  • ${e.name}
  • `).join(''); + }; + document.getElementById('ev-r').onclick = () => { + evs.push({ uid: 'ev-r-' + Date.now(), name: '右后45度.jpg', kind: 'photo', photoAngle: 'right45' }); + document.getElementById('ev-list').innerHTML = evs.map((e) => `
  • ${e.name}
  • `).join(''); + }; + document.getElementById('pass').onclick = () => { + try { + const payload = collectPayload({ seq: line.seq, evidences: evs }); + const t = VPField.passLine(task, line.id, payload); + document.getElementById('esign-box').hidden = false; + document.getElementById('esign-box').textContent = + 'E签宝已生成:' + (t.vehicles.find((v) => v.id === line.id) || {}).esignFileName; + toast('交接验收通过,双签完成(模拟)'); + setTimeout(() => { + view = { name: 'list' }; + render(); + }, 800); + } catch (e) { + toast(e.message || String(e)); + } + }; + document.getElementById('failbtn').onclick = () => { + try { + const payload = collectPayload({ + seq: line.seq, + evidences: evs, + failReason: document.getElementById('remark').value || '交接验收不合格', + }); + VPField.failLine(task, line.id, payload); + toast('已拒收并留痕,可发起重交'); + view = { name: 'list' }; + render(); + } catch (e) { + toast(e.message || String(e)); + } + }; + } + + function render() { + if (view.name === 'edit') renderEdit(); + else renderList(); + } + + render(); +})(); diff --git a/src/prototypes/vehicle-inspection-app-legacy/field-core.js b/src/prototypes/vehicle-inspection-app-legacy/field-core.js new file mode 100644 index 0000000..f8e0939 --- /dev/null +++ b/src/prototypes/vehicle-inspection-app-legacy/field-core.js @@ -0,0 +1,364 @@ +/** + * 外勤验车 · 对齐《附件4:车辆交接验收表》 + */ +(function (global) { + const KEYS = { + inspections: 'oneos.vp.inspections', + stocked: 'oneos.vp.stockedVehicles', + activeId: 'oneos.vp.activeInspectionId', + }; + + const VIN_REGEX = /^[A-HJ-NPR-Z0-9]{17}$/; + const OCR_SAMPLES = ['LB9A32A28P0512347', 'LB9A32A20R0512238', 'LZZ1CLWB0PA123456']; + + const CHECK_GROUPS = [ + { + title: '外观与标识', + items: [ + '整车外观状况(与公告目录样车相同)', + '漆面状况', + '喇叭状况', + '车辆照片(左前45°+右后45°)', + '汽车标识LOGO', + '车辆底盘状况', + '整车配置表', + ], + }, + { + title: '动力与续航', + items: [ + '冷却风扇转动状况', + '供氢量/续航电量(≥95%)', + '钥匙(2把)', + '动力舱状况', + '车辆启动状况', + '档位变换(前进档、倒档)', + ], + }, + { + title: '驾驶室与电器', + items: [ + '车辆内饰状况', + '灯光状况', + '前后灯总成状况', + '风挡玻璃状况', + '仪表盘功能状况', + '雨刮器状况', + '座椅状况', + '安全带状况', + '左右后视镜状况', + '车内反光镜状况', + '空调状况', + '刹车系统状况', + '制动器助力性能状况', + '方向盘助力性能状况', + ], + }, + { + title: '随车工具与备件', + items: [ + '灭火器(1个)', + '备胎(1只)', + '点烟器(1个)', + '反光衣', + '千斤顶', + '轮胎撬棍', + '车辆反光贴', + '三角警示牌', + '钳子/轮胎扳手', + '备胎扬撬', + '轴头保养扳手', + '挡车器', + '胎压检测值', + '标准量去离子水', + '标准量冷却液', + ], + }, + { + title: '文件资料', + items: ['用户操作手册', '质量保修手册', '保修IC卡'], + }, + { + title: '车身结构', + items: [ + '四轮轮胎状况', + '挡泥板状况', + '侧防护围杠/后部护围杠', + '左右中后门状况', + '货箱箱体状况', + ], + }, + ]; + + const CHECK_ITEMS = CHECK_GROUPS.flatMap((g) => g.items); + const SIGNOFF_DOC = '车辆交接验收表'; + const MARKS = [ + { value: 'ok', label: '√' }, + { value: 'missing', label: 'N' }, + { value: 'damage', label: '×' }, + { value: 'deform', label: 'O' }, + ]; + + function read(key, fallback) { + try { + const raw = localStorage.getItem(key); + return raw ? JSON.parse(raw) : fallback; + } catch { + return fallback; + } + } + + function write(key, val) { + localStorage.setItem(key, JSON.stringify(val)); + } + + function nowStr() { + const d = new Date(); + const p = (n) => String(n).padStart(2, '0'); + return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`; + } + + function normalizeVin(raw) { + return String(raw || '') + .trim() + .toUpperCase() + .replace(/\s+/g, ''); + } + + function vinMessage(raw) { + const v = normalizeVin(raw); + if (!v) return '请填写车架号'; + if (v.length !== 17) return '车架号须为 17 位'; + if (!VIN_REGEX.test(v)) return '车架号格式不正确(不可含 I/O/Q)'; + return null; + } + + function getTasks() { + return read(KEYS.inspections, []); + } + + function saveTasks(list) { + write(KEYS.inspections, list); + } + + function getActiveTask() { + const id = localStorage.getItem(KEYS.activeId); + const tasks = getTasks(); + if (id) { + const found = tasks.find((t) => t.id === id); + if (found) return found; + } + return tasks[0] || null; + } + + function updateTask(task) { + const list = getTasks(); + const next = list.some((t) => t.id === task.id) + ? list.map((t) => (t.id === task.id ? task : t)) + : [task, ...list]; + saveTasks(next); + return task; + } + + function mockOcr(i) { + return OCR_SAMPLES[i % OCR_SAMPLES.length]; + } + + function checkItemsOf(task) { + return task.acceptanceCheckItems && task.acceptanceCheckItems.length + ? task.acceptanceCheckItems + : CHECK_ITEMS; + } + + function signoffName(task) { + return task.signoffDocName || SIGNOFF_DOC; + } + + function allMarksOk(marks, items) { + if (!marks) return false; + return items.every((it) => marks[it] === 'ok' || marks[it] === 'na'); + } + + function passLine(task, lineId, payload) { + const vinErr = vinMessage(payload.vin); + if (vinErr) throw new Error(vinErr); + const items = checkItemsOf(task); + if (!allMarksOk(payload.handoverMarks, items)) { + throw new Error('交接验收表存在异常/未检项目,不能签收通过(请全部标 √ 或改为拒收)'); + } + if (!payload.delivererSigned || !payload.receiverSigned) { + throw new Error('须完成交车方与接车方双签'); + } + if (!payload.evidences || payload.evidences.length < 2) { + throw new Error('须上传左前45°与右后45°车辆照片'); + } + const vin = normalizeVin(payload.vin); + const doc = signoffName(task); + const esign = `${task.code}-${String(payload.seq || 1).padStart(2, '0')}-${doc}-电子签章.pdf`; + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { + ...l, + vin, + vehicleModel: payload.vehicleModel || l.vehicleModel || task.vehicleModel, + deliverUnit: payload.deliverUnit || l.deliverUnit || task.defaultDeliverUnit, + receiveUnit: payload.receiveUnit || l.receiveUnit || task.defaultReceiveUnit, + handoverDate: payload.handoverDate || l.handoverDate, + odometerKm: payload.odometerKm != null ? Number(payload.odometerKm) : l.odometerKm, + motorNo: payload.motorNo || l.motorNo, + remark: payload.remark || '', + status: 'passed', + handoverMarks: payload.handoverMarks, + checkItems: items.filter((it) => payload.handoverMarks[it] === 'ok'), + evidences: payload.evidences, + delivererSigned: true, + receiverSigned: true, + esignFileName: esign, + signedAt: nowStr(), + failReason: undefined, + }; + }); + return updateTask({ + ...task, + vehicles, + status: 'in_progress', + updatedAt: nowStr(), + }); + } + + function failLine(task, lineId, payload) { + if (!payload.failReason || !String(payload.failReason).trim()) { + throw new Error('拒收须填写备注/原因'); + } + if (!payload.evidences || !payload.evidences.length) { + throw new Error('拒收须上传现场证据'); + } + const vin = payload.vin ? normalizeVin(payload.vin) : ''; + if (vin && vinMessage(vin)) throw new Error(vinMessage(vin)); + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { + ...l, + vin: vin || l.vin, + vehicleModel: payload.vehicleModel || l.vehicleModel, + deliverUnit: payload.deliverUnit || l.deliverUnit, + receiveUnit: payload.receiveUnit || l.receiveUnit, + handoverDate: payload.handoverDate || l.handoverDate, + odometerKm: payload.odometerKm != null ? Number(payload.odometerKm) : l.odometerKm, + motorNo: payload.motorNo || l.motorNo, + remark: payload.remark || payload.failReason, + status: 'failed', + failReason: String(payload.failReason).trim(), + handoverMarks: payload.handoverMarks || {}, + evidences: payload.evidences, + lastRejectedAt: nowStr(), + }; + }); + return updateTask({ + ...task, + vehicles, + status: 'in_progress', + updatedAt: nowStr(), + }); + } + + function requestRedelivery(task, lineId) { + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { + ...l, + status: 'redelivery', + redeliveryCount: (l.redeliveryCount || 0) + 1, + vin: '', + esignFileName: undefined, + signedAt: undefined, + evidences: [], + checkItems: undefined, + handoverMarks: undefined, + delivererSigned: false, + receiverSigned: false, + }; + }); + return updateTask({ ...task, vehicles, updatedAt: nowStr() }); + } + + function startRedelivery(task, lineId) { + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { ...l, status: 'awaiting' }; + }); + return updateTask({ ...task, vehicles, updatedAt: nowStr() }); + } + + function appendLine(task) { + if (task.vehicles.length >= task.expectedQty) { + throw new Error('已达到合同应验台数'); + } + const seq = task.vehicles.length + 1; + const line = { + id: `${task.id}-line-${seq}`, + seq, + vin: '', + status: 'awaiting', + evidences: [], + redeliveryCount: 0, + vehicleModel: task.vehicleModel, + deliverUnit: task.defaultDeliverUnit, + receiveUnit: task.defaultReceiveUnit, + handoverDate: task.inspectDate, + }; + return updateTask({ + ...task, + vehicles: [...task.vehicles, line], + status: 'in_progress', + updatedAt: nowStr(), + }); + } + + function stockPassed(task) { + const stocked = read(KEYS.stocked, []); + let n = 0; + const vehicles = task.vehicles.map((l) => { + if (l.status !== 'passed' || !l.vin) return l; + const id = `sv-${Date.now()}-${l.seq}`; + stocked.unshift({ + id, + vin: l.vin, + brandModel: l.vehicleModel || task.vehicleModel, + purchaseContractId: task.contractId, + purchaseContractCode: task.contractCode, + purchaseDate: nowStr().slice(0, 10), + status: '未备车', + sourceInspectionTaskId: task.id, + sourceLineId: l.id, + }); + n += 1; + return { ...l, status: 'stocked', stockedAt: nowStr(), stockedVehicleId: id }; + }); + write(KEYS.stocked, stocked); + updateTask({ ...task, vehicles, updatedAt: nowStr() }); + return n; + } + + global.VPField = { + KEYS, + CHECK_ITEMS, + CHECK_GROUPS, + MARKS, + SIGNOFF_DOC, + getActiveTask, + getTasks, + passLine, + failLine, + requestRedelivery, + startRedelivery, + appendLine, + stockPassed, + mockOcr, + vinMessage, + normalizeVin, + checkItemsOf, + signoffName, + nowStr, + }; +})(window); diff --git a/src/prototypes/vehicle-inspection-app-legacy/field-theme.css b/src/prototypes/vehicle-inspection-app-legacy/field-theme.css new file mode 100644 index 0000000..666979f --- /dev/null +++ b/src/prototypes/vehicle-inspection-app-legacy/field-theme.css @@ -0,0 +1,52 @@ +:root { + --ln-primary: #32a06e; + --ln-primary-hover: #3fb87c; + --ln-ink: #18181b; + --ln-muted: #71717a; + --ln-canvas: #f5f6f6; + --ln-surface: #ffffff; + --ln-hairline: #e5e7eb; + --ln-error: #e5484d; + --ln-success: #27a644; + --ln-radius-control: 8px; + --ln-radius-card: 12px; + /* aliases used by existing HTML */ + --bg: var(--ln-canvas); + --card: var(--ln-surface); + --text: var(--ln-ink); + --muted: var(--ln-muted); + --primary: var(--ln-primary); + --danger: var(--ln-error); + --border: var(--ln-hairline); + /* semantic tints */ + --ln-primary-soft: #e8f5ef; + --ln-success-soft: #e6f6ea; + --ln-error-soft: #fdeeee; + --ln-primary-border: rgba(50, 160, 110, 0.45); +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation: none !important; + transition: none !important; + } +} + +button { + min-height: 44px; +} + +button:focus-visible, +input:focus-visible, +textarea:focus-visible, +select:focus-visible { + outline: 2px solid var(--ln-primary); + outline-offset: 1px; +} + +button.primary:hover { + background: var(--ln-primary-hover); + border-color: var(--ln-primary-hover); +} diff --git a/src/prototypes/vehicle-inspection-app-legacy/index.html b/src/prototypes/vehicle-inspection-app-legacy/index.html new file mode 100644 index 0000000..50dfff4 --- /dev/null +++ b/src/prototypes/vehicle-inspection-app-legacy/index.html @@ -0,0 +1,158 @@ + + + + + + 验车入库 · APP + + + + +
    +
    +
    9:41●●● 5G
    +
    + +

    验车入库

    +
    +
    + +
    +
    +
    + + + + diff --git a/src/prototypes/vehicle-inspection-app/app.js b/src/prototypes/vehicle-inspection-app/app.js new file mode 100644 index 0000000..1996790 --- /dev/null +++ b/src/prototypes/vehicle-inspection-app/app.js @@ -0,0 +1,333 @@ +(function () { + const app = document.getElementById('app'); + const toastEl = document.getElementById('toast'); + let view = { name: 'list' }; + let ocrIdx = 0; + + function toast(msg) { + toastEl.textContent = msg; + toastEl.style.display = 'block'; + setTimeout(() => { + toastEl.style.display = 'none'; + }, 2800); + } + + function statusBadge(s) { + if (s === 'stocked') return '已入库'; + if (s === 'passed') return '已签收'; + if (s === 'failed') return '拒收'; + if (s === 'redelivery') return '重交中'; + return '待登记'; + } + + function ensureDemoTask() { + let task = VPField.getActiveTask(); + if (task) { + if (!task.acceptanceCheckItems) task.acceptanceCheckItems = VPField.CHECK_ITEMS.slice(); + if (!task.signoffDocName) task.signoffDocName = VPField.SIGNOFF_DOC; + return task; + } + const demo = { + id: 'insp-demo-field', + code: 'YC-DEMO-0020', + contractId: 'pc-demo-20', + contractCode: 'HT-DEMO-20', + vehicleModel: '4.5T 燃料电池车', + expectedQty: 20, + inspectLocation: '指定交付场地', + inspectDate: '2026-06-16', + status: 'pending', + defaultDeliverUnit: '交车单位(卖方)', + defaultReceiveUnit: '接车单位(买方)', + acceptanceCheckItems: VPField.CHECK_ITEMS.slice(), + signoffDocName: VPField.SIGNOFF_DOC, + vehicles: [1, 2, 3].map((seq) => ({ + id: 'demo-line-' + seq, + seq, + vin: '', + status: 'awaiting', + evidences: [], + redeliveryCount: 0, + vehicleModel: '4.5T 燃料电池车', + deliverUnit: '交车单位(卖方)', + receiveUnit: '接车单位(买方)', + handoverDate: '2026-06-16', + })), + createdAt: VPField.nowStr(), + updatedAt: VPField.nowStr(), + }; + const list = VPField.getTasks(); + list.unshift(demo); + localStorage.setItem(VPField.KEYS.inspections, JSON.stringify(list)); + localStorage.setItem(VPField.KEYS.activeId, demo.id); + return demo; + } + + function renderList() { + const task = ensureDemoTask(); + const lines = task.vehicles + .map( + (l) => + `
    +
    #${l.seq} ${statusBadge(l.status)}
    +
    车架号 ${l.vin || '未登记'} · 里程 ${l.odometerKm != null ? l.odometerKm + ' km' : '—'} + ${l.delivererSigned && l.receiverSigned ? ' · 已双签' : ''}
    +
    `, + ) + .join(''); + app.innerHTML = ` +
    +

    ${task.code}

    +
    附件4 · ${VPField.signoffName(task)}
    +
    ${task.contractCode} · ${task.vehicleModel}
    +
    ${task.inspectLocation} · ${task.inspectDate}
    +
    明细 ${task.vehicles.length}/${task.expectedQty}(可追加到车)
    +
    +
    +
    车辆明细
    + ${lines} +
    + + +
    +

    图例:√正常 · N缺少 · ×损坏 · O变形。通过须全项√且交/接车双签,并上传左前45°+右后45°照片。

    +
    `; + app.querySelectorAll('.line').forEach((el) => { + el.addEventListener('click', () => { + view = { name: 'edit', lineId: el.getAttribute('data-id') }; + render(); + }); + }); + document.getElementById('btn-stock').onclick = () => { + const n = VPField.stockPassed(VPField.getActiveTask()); + toast(n ? `已入库 ${n} 台` : '没有待入库的签收车辆'); + render(); + }; + document.getElementById('btn-append').onclick = () => { + try { + VPField.appendLine(VPField.getActiveTask()); + toast('已追加到车登记位'); + render(); + } catch (e) { + toast(e.message || String(e)); + } + }; + } + + function collectMarks() { + const marks = {}; + document.querySelectorAll('[data-item]').forEach((row) => { + const item = row.getAttribute('data-item'); + const checked = row.querySelector('input[type=radio]:checked'); + if (checked) marks[item] = checked.value; + }); + return marks; + } + + function collectPayload(base) { + return { + ...base, + vin: document.getElementById('vin').value, + vehicleModel: document.getElementById('model').value, + deliverUnit: document.getElementById('deliver').value, + receiveUnit: document.getElementById('receive').value, + handoverDate: document.getElementById('hdate').value, + odometerKm: document.getElementById('odo').value, + motorNo: document.getElementById('motor').value, + remark: document.getElementById('remark').value, + delivererSigned: document.getElementById('sign-d').checked, + receiverSigned: document.getElementById('sign-r').checked, + handoverMarks: collectMarks(), + }; + } + + function renderEdit() { + const task = ensureDemoTask(); + const line = task.vehicles.find((l) => l.id === view.lineId); + if (!line) { + view = { name: 'list' }; + return render(); + } + + if (line.status === 'failed') { + app.innerHTML = ` +
    + +

    第 ${line.seq} 台 · 已拒收

    +

    ${line.failReason || ''}

    +
    +
    `; + document.getElementById('back').onclick = () => { + view = { name: 'list' }; + render(); + }; + document.getElementById('rede').onclick = () => { + VPField.requestRedelivery(task, line.id); + toast('已发起重交'); + view = { name: 'list' }; + render(); + }; + return; + } + if (line.status === 'redelivery') { + app.innerHTML = ` +
    + +

    第 ${line.seq} 台 · 重交中

    +
    +
    `; + document.getElementById('back').onclick = () => { + view = { name: 'list' }; + render(); + }; + document.getElementById('start').onclick = () => { + VPField.startRedelivery(task, line.id); + toast('已进入待登记'); + render(); + }; + return; + } + if (line.status === 'passed' || line.status === 'stocked') { + app.innerHTML = ` +
    + +

    第 ${line.seq} 台

    +

    ${statusBadge(line.status)} · 车架号 ${line.vin}

    +

    里程 ${line.odometerKm != null ? line.odometerKm : '—'} km · 电机号 ${line.motorNo || '—'}

    +
    ${line.esignFileName || ''}
    +
    `; + document.getElementById('back').onclick = () => { + view = { name: 'list' }; + render(); + }; + return; + } + + const groupsHtml = VPField.CHECK_GROUPS.map((g) => { + const rows = g.items + .map((item) => { + const radios = VPField.MARKS.map( + (m) => + ``, + ).join(''); + return `
    +
    ${item}
    +
    ${radios}
    +
    `; + }) + .join(''); + return `
    ${g.title}
    ${rows}
    `; + }).join(''); + + app.innerHTML = ` +
    + +

    附件4 · 第 ${line.seq} 台

    +
    ${VPField.signoffName(task)}
    + + + + + + + + + + + +
    + + + + + +
    交车及接车时待检验项目
    +
    点选 √ / N / × / O(演示可「一键全 √」)
    +
    + ${groupsHtml} + + + + +
    + + +
    +
      + +
      双方确认
      + + + +
      + + +
      +
      `; + + const evs = []; + document.getElementById('back').onclick = () => { + view = { name: 'list' }; + render(); + }; + document.getElementById('ocr').onclick = () => { + document.getElementById('vin').value = VPField.mockOcr(ocrIdx++); + toast('车架号 OCR 回填(模拟)'); + }; + document.getElementById('all-ok').onclick = () => { + document.querySelectorAll('[data-item]').forEach((row) => { + const ok = row.querySelector('input[value=ok]'); + if (ok) ok.checked = true; + }); + toast('已全部标记 √'); + }; + document.getElementById('ev-l').onclick = () => { + evs.push({ uid: 'ev-l-' + Date.now(), name: '左前45度.jpg', kind: 'photo', photoAngle: 'left45' }); + document.getElementById('ev-list').innerHTML = evs.map((e) => `
    • ${e.name}
    • `).join(''); + }; + document.getElementById('ev-r').onclick = () => { + evs.push({ uid: 'ev-r-' + Date.now(), name: '右后45度.jpg', kind: 'photo', photoAngle: 'right45' }); + document.getElementById('ev-list').innerHTML = evs.map((e) => `
    • ${e.name}
    • `).join(''); + }; + document.getElementById('pass').onclick = () => { + try { + const payload = collectPayload({ seq: line.seq, evidences: evs }); + const t = VPField.passLine(task, line.id, payload); + document.getElementById('esign-box').hidden = false; + document.getElementById('esign-box').textContent = + 'E签宝已生成:' + (t.vehicles.find((v) => v.id === line.id) || {}).esignFileName; + toast('交接验收通过,双签完成(模拟)'); + setTimeout(() => { + view = { name: 'list' }; + render(); + }, 800); + } catch (e) { + toast(e.message || String(e)); + } + }; + document.getElementById('failbtn').onclick = () => { + try { + const payload = collectPayload({ + seq: line.seq, + evidences: evs, + failReason: document.getElementById('remark').value || '交接验收不合格', + }); + VPField.failLine(task, line.id, payload); + toast('已拒收并留痕,可发起重交'); + view = { name: 'list' }; + render(); + } catch (e) { + toast(e.message || String(e)); + } + }; + } + + function render() { + if (view.name === 'edit') renderEdit(); + else renderList(); + } + + render(); +})(); diff --git a/src/prototypes/vehicle-inspection-app/field-core.js b/src/prototypes/vehicle-inspection-app/field-core.js new file mode 100644 index 0000000..f8e0939 --- /dev/null +++ b/src/prototypes/vehicle-inspection-app/field-core.js @@ -0,0 +1,364 @@ +/** + * 外勤验车 · 对齐《附件4:车辆交接验收表》 + */ +(function (global) { + const KEYS = { + inspections: 'oneos.vp.inspections', + stocked: 'oneos.vp.stockedVehicles', + activeId: 'oneos.vp.activeInspectionId', + }; + + const VIN_REGEX = /^[A-HJ-NPR-Z0-9]{17}$/; + const OCR_SAMPLES = ['LB9A32A28P0512347', 'LB9A32A20R0512238', 'LZZ1CLWB0PA123456']; + + const CHECK_GROUPS = [ + { + title: '外观与标识', + items: [ + '整车外观状况(与公告目录样车相同)', + '漆面状况', + '喇叭状况', + '车辆照片(左前45°+右后45°)', + '汽车标识LOGO', + '车辆底盘状况', + '整车配置表', + ], + }, + { + title: '动力与续航', + items: [ + '冷却风扇转动状况', + '供氢量/续航电量(≥95%)', + '钥匙(2把)', + '动力舱状况', + '车辆启动状况', + '档位变换(前进档、倒档)', + ], + }, + { + title: '驾驶室与电器', + items: [ + '车辆内饰状况', + '灯光状况', + '前后灯总成状况', + '风挡玻璃状况', + '仪表盘功能状况', + '雨刮器状况', + '座椅状况', + '安全带状况', + '左右后视镜状况', + '车内反光镜状况', + '空调状况', + '刹车系统状况', + '制动器助力性能状况', + '方向盘助力性能状况', + ], + }, + { + title: '随车工具与备件', + items: [ + '灭火器(1个)', + '备胎(1只)', + '点烟器(1个)', + '反光衣', + '千斤顶', + '轮胎撬棍', + '车辆反光贴', + '三角警示牌', + '钳子/轮胎扳手', + '备胎扬撬', + '轴头保养扳手', + '挡车器', + '胎压检测值', + '标准量去离子水', + '标准量冷却液', + ], + }, + { + title: '文件资料', + items: ['用户操作手册', '质量保修手册', '保修IC卡'], + }, + { + title: '车身结构', + items: [ + '四轮轮胎状况', + '挡泥板状况', + '侧防护围杠/后部护围杠', + '左右中后门状况', + '货箱箱体状况', + ], + }, + ]; + + const CHECK_ITEMS = CHECK_GROUPS.flatMap((g) => g.items); + const SIGNOFF_DOC = '车辆交接验收表'; + const MARKS = [ + { value: 'ok', label: '√' }, + { value: 'missing', label: 'N' }, + { value: 'damage', label: '×' }, + { value: 'deform', label: 'O' }, + ]; + + function read(key, fallback) { + try { + const raw = localStorage.getItem(key); + return raw ? JSON.parse(raw) : fallback; + } catch { + return fallback; + } + } + + function write(key, val) { + localStorage.setItem(key, JSON.stringify(val)); + } + + function nowStr() { + const d = new Date(); + const p = (n) => String(n).padStart(2, '0'); + return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`; + } + + function normalizeVin(raw) { + return String(raw || '') + .trim() + .toUpperCase() + .replace(/\s+/g, ''); + } + + function vinMessage(raw) { + const v = normalizeVin(raw); + if (!v) return '请填写车架号'; + if (v.length !== 17) return '车架号须为 17 位'; + if (!VIN_REGEX.test(v)) return '车架号格式不正确(不可含 I/O/Q)'; + return null; + } + + function getTasks() { + return read(KEYS.inspections, []); + } + + function saveTasks(list) { + write(KEYS.inspections, list); + } + + function getActiveTask() { + const id = localStorage.getItem(KEYS.activeId); + const tasks = getTasks(); + if (id) { + const found = tasks.find((t) => t.id === id); + if (found) return found; + } + return tasks[0] || null; + } + + function updateTask(task) { + const list = getTasks(); + const next = list.some((t) => t.id === task.id) + ? list.map((t) => (t.id === task.id ? task : t)) + : [task, ...list]; + saveTasks(next); + return task; + } + + function mockOcr(i) { + return OCR_SAMPLES[i % OCR_SAMPLES.length]; + } + + function checkItemsOf(task) { + return task.acceptanceCheckItems && task.acceptanceCheckItems.length + ? task.acceptanceCheckItems + : CHECK_ITEMS; + } + + function signoffName(task) { + return task.signoffDocName || SIGNOFF_DOC; + } + + function allMarksOk(marks, items) { + if (!marks) return false; + return items.every((it) => marks[it] === 'ok' || marks[it] === 'na'); + } + + function passLine(task, lineId, payload) { + const vinErr = vinMessage(payload.vin); + if (vinErr) throw new Error(vinErr); + const items = checkItemsOf(task); + if (!allMarksOk(payload.handoverMarks, items)) { + throw new Error('交接验收表存在异常/未检项目,不能签收通过(请全部标 √ 或改为拒收)'); + } + if (!payload.delivererSigned || !payload.receiverSigned) { + throw new Error('须完成交车方与接车方双签'); + } + if (!payload.evidences || payload.evidences.length < 2) { + throw new Error('须上传左前45°与右后45°车辆照片'); + } + const vin = normalizeVin(payload.vin); + const doc = signoffName(task); + const esign = `${task.code}-${String(payload.seq || 1).padStart(2, '0')}-${doc}-电子签章.pdf`; + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { + ...l, + vin, + vehicleModel: payload.vehicleModel || l.vehicleModel || task.vehicleModel, + deliverUnit: payload.deliverUnit || l.deliverUnit || task.defaultDeliverUnit, + receiveUnit: payload.receiveUnit || l.receiveUnit || task.defaultReceiveUnit, + handoverDate: payload.handoverDate || l.handoverDate, + odometerKm: payload.odometerKm != null ? Number(payload.odometerKm) : l.odometerKm, + motorNo: payload.motorNo || l.motorNo, + remark: payload.remark || '', + status: 'passed', + handoverMarks: payload.handoverMarks, + checkItems: items.filter((it) => payload.handoverMarks[it] === 'ok'), + evidences: payload.evidences, + delivererSigned: true, + receiverSigned: true, + esignFileName: esign, + signedAt: nowStr(), + failReason: undefined, + }; + }); + return updateTask({ + ...task, + vehicles, + status: 'in_progress', + updatedAt: nowStr(), + }); + } + + function failLine(task, lineId, payload) { + if (!payload.failReason || !String(payload.failReason).trim()) { + throw new Error('拒收须填写备注/原因'); + } + if (!payload.evidences || !payload.evidences.length) { + throw new Error('拒收须上传现场证据'); + } + const vin = payload.vin ? normalizeVin(payload.vin) : ''; + if (vin && vinMessage(vin)) throw new Error(vinMessage(vin)); + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { + ...l, + vin: vin || l.vin, + vehicleModel: payload.vehicleModel || l.vehicleModel, + deliverUnit: payload.deliverUnit || l.deliverUnit, + receiveUnit: payload.receiveUnit || l.receiveUnit, + handoverDate: payload.handoverDate || l.handoverDate, + odometerKm: payload.odometerKm != null ? Number(payload.odometerKm) : l.odometerKm, + motorNo: payload.motorNo || l.motorNo, + remark: payload.remark || payload.failReason, + status: 'failed', + failReason: String(payload.failReason).trim(), + handoverMarks: payload.handoverMarks || {}, + evidences: payload.evidences, + lastRejectedAt: nowStr(), + }; + }); + return updateTask({ + ...task, + vehicles, + status: 'in_progress', + updatedAt: nowStr(), + }); + } + + function requestRedelivery(task, lineId) { + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { + ...l, + status: 'redelivery', + redeliveryCount: (l.redeliveryCount || 0) + 1, + vin: '', + esignFileName: undefined, + signedAt: undefined, + evidences: [], + checkItems: undefined, + handoverMarks: undefined, + delivererSigned: false, + receiverSigned: false, + }; + }); + return updateTask({ ...task, vehicles, updatedAt: nowStr() }); + } + + function startRedelivery(task, lineId) { + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { ...l, status: 'awaiting' }; + }); + return updateTask({ ...task, vehicles, updatedAt: nowStr() }); + } + + function appendLine(task) { + if (task.vehicles.length >= task.expectedQty) { + throw new Error('已达到合同应验台数'); + } + const seq = task.vehicles.length + 1; + const line = { + id: `${task.id}-line-${seq}`, + seq, + vin: '', + status: 'awaiting', + evidences: [], + redeliveryCount: 0, + vehicleModel: task.vehicleModel, + deliverUnit: task.defaultDeliverUnit, + receiveUnit: task.defaultReceiveUnit, + handoverDate: task.inspectDate, + }; + return updateTask({ + ...task, + vehicles: [...task.vehicles, line], + status: 'in_progress', + updatedAt: nowStr(), + }); + } + + function stockPassed(task) { + const stocked = read(KEYS.stocked, []); + let n = 0; + const vehicles = task.vehicles.map((l) => { + if (l.status !== 'passed' || !l.vin) return l; + const id = `sv-${Date.now()}-${l.seq}`; + stocked.unshift({ + id, + vin: l.vin, + brandModel: l.vehicleModel || task.vehicleModel, + purchaseContractId: task.contractId, + purchaseContractCode: task.contractCode, + purchaseDate: nowStr().slice(0, 10), + status: '未备车', + sourceInspectionTaskId: task.id, + sourceLineId: l.id, + }); + n += 1; + return { ...l, status: 'stocked', stockedAt: nowStr(), stockedVehicleId: id }; + }); + write(KEYS.stocked, stocked); + updateTask({ ...task, vehicles, updatedAt: nowStr() }); + return n; + } + + global.VPField = { + KEYS, + CHECK_ITEMS, + CHECK_GROUPS, + MARKS, + SIGNOFF_DOC, + getActiveTask, + getTasks, + passLine, + failLine, + requestRedelivery, + startRedelivery, + appendLine, + stockPassed, + mockOcr, + vinMessage, + normalizeVin, + checkItemsOf, + signoffName, + nowStr, + }; +})(window); diff --git a/src/prototypes/vehicle-inspection-app/field-theme.css b/src/prototypes/vehicle-inspection-app/field-theme.css new file mode 100644 index 0000000..666979f --- /dev/null +++ b/src/prototypes/vehicle-inspection-app/field-theme.css @@ -0,0 +1,52 @@ +:root { + --ln-primary: #32a06e; + --ln-primary-hover: #3fb87c; + --ln-ink: #18181b; + --ln-muted: #71717a; + --ln-canvas: #f5f6f6; + --ln-surface: #ffffff; + --ln-hairline: #e5e7eb; + --ln-error: #e5484d; + --ln-success: #27a644; + --ln-radius-control: 8px; + --ln-radius-card: 12px; + /* aliases used by existing HTML */ + --bg: var(--ln-canvas); + --card: var(--ln-surface); + --text: var(--ln-ink); + --muted: var(--ln-muted); + --primary: var(--ln-primary); + --danger: var(--ln-error); + --border: var(--ln-hairline); + /* semantic tints */ + --ln-primary-soft: #e8f5ef; + --ln-success-soft: #e6f6ea; + --ln-error-soft: #fdeeee; + --ln-primary-border: rgba(50, 160, 110, 0.45); +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation: none !important; + transition: none !important; + } +} + +button { + min-height: 44px; +} + +button:focus-visible, +input:focus-visible, +textarea:focus-visible, +select:focus-visible { + outline: 2px solid var(--ln-primary); + outline-offset: 1px; +} + +button.primary:hover { + background: var(--ln-primary-hover); + border-color: var(--ln-primary-hover); +} diff --git a/src/prototypes/vehicle-inspection-app/index.html b/src/prototypes/vehicle-inspection-app/index.html new file mode 100644 index 0000000..50dfff4 --- /dev/null +++ b/src/prototypes/vehicle-inspection-app/index.html @@ -0,0 +1,158 @@ + + + + + + 验车入库 · APP + + + + +
      +
      +
      9:41●●● 5G
      +
      + +

      验车入库

      +
      +
      + +
      +
      +
      + + + + diff --git a/src/prototypes/vehicle-inspection-legacy/.spec/requirements-prd.md b/src/prototypes/vehicle-inspection-legacy/.spec/requirements-prd.md new file mode 100644 index 0000000..7e8c82e --- /dev/null +++ b/src/prototypes/vehicle-inspection-legacy/.spec/requirements-prd.md @@ -0,0 +1,57 @@ +# 验车入库 · 产品需求说明 + +| 项 | 内容 | +|---|---| +| 模块名称 | 验车入库 | +| 所属系统 | ONE-OS(PC)+ 外勤 Web/小程序/APP | +| 交互原型 | `/prototypes/vehicle-inspection` | +| 业务条线 | 运维管理条线 · 入库入口 | +| 文档状态 | AutoPRD · 首版 | + +--- + +## 1. 一句话与目标 + +**一句话** +采购合同审批通过后自动生成验车任务;运维在现场登记 VIN、记录通过/不通过与证据,通过后经 E签宝签署验收报告并入库。 + +**本期目标** +1. PC 验车任务列表与详情 +2. 部分通过可结案;拒收留痕并可发起重新交付 +3. 外勤三端对齐《附件4:车辆交接验收表》:基本信息、分组检验项(√/N/×/O)、双签、45°照片、车架号 OCR +4. 入库回写车辆资产(原型 localStorage 桥) +5. 大数量合同按到车追加明细 + +**非目标** +- 真实 OCR / 真实 E签宝 API +- 纸质表扫描自动结构化入库(仅人工/外勤填报) + +--- + +## 2. 用户故事 + +| 角色 | 起点 → 运作 → 闭环 | +|------|-------------------| +| 运维(PC) | 打开验车任务 → 查看应验台数与现场结果 → 通过车辆入库 / 结案 | +| 运维(外勤) | 打开绑定任务 → OCR/手输 VIN → 通过并 E签 或不通过+证据 → 回写 PC | +| 采购 | 从采购合同跳转查看关联验车进度 | + +--- + +## 3. 关键逻辑 + +详见 `.spec/vin-and-closure.md`。 + +--- + +## 4. 验收重点 + +1. 合同通过后任务自动出现 +2. VIN 非法不可通过 +3. 不通过必须原因+证据;通过须检查项+现场照片+E签模拟文件 +4. 部分通过可结案;入库车辆可在车辆资产列表看到(同源浏览器) + +## 5. 假设 + +- 外勤三端暂放仓库根目录,由产品自行移动后再挂导航 +- 与 PC 共用 localStorage,需同域访问 diff --git a/src/prototypes/vehicle-inspection-legacy/.spec/vin-and-closure.md b/src/prototypes/vehicle-inspection-legacy/.spec/vin-and-closure.md new file mode 100644 index 0000000..06a21ed --- /dev/null +++ b/src/prototypes/vehicle-inspection-legacy/.spec/vin-and-closure.md @@ -0,0 +1,41 @@ +# VIN / 车辆交接验收表 / 拒收重交 + +对齐样例:`20台车.pdf`(附件4:车辆交接验收表,20 台 × 每台约 4 页)。 + +## 主单据 + +现场验车主单据为 **《车辆交接验收表》**(附件4)。 +合同商务侧《发送签收单》仍可在采购合同语境使用;入库闭环以交接验收表双签为准。 + +## 基本信息字段 + +| 字段 | 说明 | +|------|------| +| 车辆交接日期 | 现场填写 | +| 交车单位 / 接车单位 | 默认可带出合同卖方/买方 | +| 车辆型号 | 默认可带出合同车型 | +| 车架号 | 17 位,OCR 模拟回填 + 正则校验 | +| 交车时仪表里程(公里) | 数字 | +| 发动机号/电机号 | 文本 | + +## 检验项目与图例 + +分组:外观与标识、动力与续航、驾驶室与电器、随车工具与备件、文件资料、车身结构。 + +标记:`√` 正常 · `N` 缺少 · `×` 损坏 · `O` 变形。 + +**通过条件**:全部项目为 √(或不适用)、交车方+接车方双签、左前45°+右后45°照片齐套,并生成交接验收表电子签章文件(原型模拟)。 + +## 拒收与重交 + +拒收须备注原因 + 证据 → 可发起重新交付 → 到车后重验。 + +## 大数量 + +应验台数=合同数量;明细按到车追加(如 20 台样例不一次铺满)。 + +## 代码 + +- `acceptance.ts`(分组检查项) +- 外勤 `field-core.js` / `app.js` +- PC `VehicleInspectionApp.tsx` diff --git a/src/prototypes/vehicle-inspection-legacy/DESIGN.md b/src/prototypes/vehicle-inspection-legacy/DESIGN.md new file mode 100644 index 0000000..89e477e --- /dev/null +++ b/src/prototypes/vehicle-inspection-legacy/DESIGN.md @@ -0,0 +1,6 @@ +# 验车入库 · 设计基底 + +- **主题:** Linear 产品浅色(`src/themes/linear` 落地 via `vehicle-management/style.css`) +- **Token:** `--ln-*`(主色 `#32a06e`)、`--vm-*` +- **列表壳:** `vm-shared` — `vm-page`、筛选卡、KPI、`vm-table-section`、`OperationActions`、`TablePagination` +- **样式入口:** `../vehicle-management-legacy/style.css` + `./styles/index.css` diff --git a/src/prototypes/vehicle-inspection-legacy/LEGACY.md b/src/prototypes/vehicle-inspection-legacy/LEGACY.md new file mode 100644 index 0000000..ce5e184 --- /dev/null +++ b/src/prototypes/vehicle-inspection-legacy/LEGACY.md @@ -0,0 +1,8 @@ +# 【旧版】vehicle inspection + +本目录为设计规范 v2 套用前的**冻结对照副本**(2026-07-22)。 + +- 源原型:`src/prototypes/vehicle-inspection/` +- 请勿在此目录继续改业务;正式改造只在源目录进行 +- 已跳过复制:`.spec/acp`、`.spec/prototype-comment-assets`(批注大图/会话) +- 交叉引用已尽量改写为同套 `*-legacy`,避免引用被改版后的正式样式 diff --git a/src/prototypes/vehicle-inspection-legacy/VehicleInspectionApp.tsx b/src/prototypes/vehicle-inspection-legacy/VehicleInspectionApp.tsx new file mode 100644 index 0000000..8f0bb61 --- /dev/null +++ b/src/prototypes/vehicle-inspection-legacy/VehicleInspectionApp.tsx @@ -0,0 +1,570 @@ +/** + * @name 验车入库 + */ +import '../vehicle-management-legacy/style.css'; +import './styles/index.css'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { + Button, + ConfigProvider, + Descriptions, + Input, + Modal, + Select, + Space, + Table, + Tag, + message, +} from 'antd'; +import type { ColumnsType } from 'antd/es/table'; +import { ArrowLeft } from 'lucide-react'; +import { + type AnnotationSourceDocument, + type AnnotationViewerOptions, +} from '@axhub/annotation'; +import { OperationActions } from '../../common/OperationActions'; +import { PrototypeAnnotationHost } from '../../common/prototype-annotation-host'; +import { TablePagination } from '../../common/TablePagination'; +import annotationSourceDocument from './annotation-source.json'; +import { + type InspectionTask, + type InspectionVehicleLine, + type InspectionTaskStatus, + buildSeedInspections, + loadInspections, + saveInspections, + loadStocked, + saveStocked, + stockVehicleFromLine, + formatNow, + appendInspectionVehicleLine, + requestRedelivery, + startRedeliveryInspection, + DEFAULT_ACCEPTANCE_CHECK_ITEMS, + DEFAULT_SIGNOFF_DOC_NAME, +} from '../../common/vehicle-purchase'; + +const vmTheme = { + token: { + colorPrimary: '#32a06e', + colorLink: '#32a06e', + borderRadius: 8, + fontFamily: + 'Inter, -apple-system, BlinkMacSystemFont, "SF Pro Display", "PingFang SC", "Microsoft YaHei", sans-serif', + }, +}; + +const TASK_STATUS: Record = { + pending: { label: '待验车', color: 'default' }, + in_progress: { label: '验车中', color: 'processing' }, + completed: { label: '已完成', color: 'success' }, +}; + +const LINE_STATUS: Record = { + awaiting: { label: '待登记', color: 'default' }, + passed: { label: '已签收待入库', color: 'blue' }, + failed: { label: '拒收·待重交', color: 'error' }, + redelivery: { label: '重交中', color: 'orange' }, + stocked: { label: '已入库', color: 'success' }, +}; + +function countBy(task: InspectionTask) { + const passed = task.vehicles.filter((v) => v.status === 'passed' || v.status === 'stocked').length; + const failed = task.vehicles.filter( + (v) => v.status === 'failed' || v.status === 'redelivery', + ).length; + const stocked = task.vehicles.filter((v) => v.status === 'stocked').length; + return { passed, failed, stocked }; +} + +export default function VehicleInspectionApp() { + const [tasks, setTasks] = useState([]); + const [keyword, setKeyword] = useState(''); + const [statusFilter, setStatusFilter] = useState('all'); + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [active, setActive] = useState(null); + + useEffect(() => { + setTasks(loadInspections(buildSeedInspections())); + }, []); + + const persist = useCallback((next: InspectionTask[]) => { + setTasks(next); + saveInspections(next); + if (active) { + const refreshed = next.find((t) => t.id === active.id) || null; + setActive(refreshed); + } + }, [active]); + + const filtered = useMemo(() => { + return tasks.filter((t) => { + if (statusFilter !== 'all' && t.status !== statusFilter) return false; + if (keyword.trim()) { + const k = keyword.trim(); + if (!`${t.code}${t.contractCode}${t.vehicleModel}`.includes(k)) return false; + } + return true; + }); + }, [tasks, statusFilter, keyword]); + + const pageRows = useMemo(() => { + const start = (page - 1) * pageSize; + return filtered.slice(start, start + pageSize); + }, [filtered, page, pageSize]); + + const kpi = useMemo( + () => ({ + pending: tasks.filter((t) => t.status === 'pending').length, + inProgress: tasks.filter((t) => t.status === 'in_progress').length, + completed: tasks.filter((t) => t.status === 'completed').length, + }), + [tasks], + ); + + const handleResetFilters = () => { + setKeyword(''); + setStatusFilter('all'); + setPage(1); + }; + + const completeTask = (task: InspectionTask) => { + const { passed, failed } = countBy(task); + if (passed === 0 && failed === 0) { + message.warning('尚无验车结果,无法结案'); + return; + } + Modal.confirm({ + title: '确认结案?', + content: `通过 ${passed} 台,不通过 ${failed} 台。部分通过可结案;不通过车辆保留待处理留痕。`, + onOk: () => { + const next = tasks.map((t) => + t.id === task.id + ? { + ...t, + status: 'completed' as const, + completedAt: formatNow(), + updatedAt: formatNow(), + } + : t, + ); + persist(next); + message.success('任务已结案'); + }, + }); + }; + + const stockPassedLines = (task: InspectionTask) => { + const stocked = loadStocked(); + let added = 0; + const nextVehicles = task.vehicles.map((line) => { + if (line.status !== 'passed' || !line.vin) return line; + const rec = stockVehicleFromLine(task, line); + stocked.unshift(rec); + added += 1; + return { + ...line, + status: 'stocked' as const, + stockedAt: formatNow(), + stockedVehicleId: rec.id, + }; + }); + if (!added) { + message.info('没有待入库的通过车辆'); + return; + } + saveStocked(stocked); + const next = tasks.map((t) => + t.id === task.id + ? { ...t, vehicles: nextVehicles, status: 'in_progress' as const, updatedAt: formatNow() } + : t, + ); + persist(next); + message.success(`已入库 ${added} 台,可在车辆资产核对(原型 localStorage)`); + }; + + const columns: ColumnsType = [ + { + title: '任务编号', + dataIndex: 'code', + width: 140, + render: (v, row) => ( + + ), + }, + { title: '来源合同', dataIndex: 'contractCode', width: 150 }, + { title: '车型', dataIndex: 'vehicleModel', width: 160, ellipsis: true }, + { + title: '应验/签收/拒收', + key: 'counts', + width: 150, + render: (_, row) => { + const c = countBy(row); + return ( + + {row.expectedQty} / {c.passed} / {c.failed} + + ); + }, + }, + { title: '约定地点', dataIndex: 'inspectLocation', width: 160, ellipsis: true }, + { title: '约定日期', dataIndex: 'inspectDate', width: 120 }, + { + title: '状态', + dataIndex: 'status', + width: 100, + render: (s: InspectionTaskStatus) => ( + {TASK_STATUS[s].label} + ), + }, + { + title: '操作', + key: 'op', + width: 100, + fixed: 'right', + render: (_, row) => ( + setActive(row) }} /> + ), + }, + ]; + + const lineColumns: ColumnsType = [ + { title: '序号', dataIndex: 'seq', width: 64, className: 'tabular-nums' }, + { + title: '车架号', + dataIndex: 'vin', + width: 180, + render: (v) => v || 待现场登记, + }, + { + title: '里程(km)', + dataIndex: 'odometerKm', + width: 90, + className: 'tabular-nums', + render: (v) => (v != null ? v : '—'), + }, + { + title: '电机号', + dataIndex: 'motorNo', + width: 120, + ellipsis: true, + render: (v) => v || '—', + }, + { + title: '双签', + key: 'signs', + width: 90, + render: (_, row) => + row.delivererSigned && row.receiverSigned ? ( + 已双签 + ) : ( + 未齐 + ), + }, + { + title: '状态', + dataIndex: 'status', + width: 120, + render: (s: InspectionVehicleLine['status']) => ( + {LINE_STATUS[s].label} + ), + }, + { + title: '拒收原因', + dataIndex: 'failReason', + ellipsis: true, + render: (v) => v || '—', + }, + { + title: '交接验收表', + dataIndex: 'esignFileName', + width: 200, + ellipsis: true, + render: (v) => v || '—', + }, + { + title: '操作', + key: 'lineOp', + width: 160, + render: (_, row) => { + if (!active) return null; + if (row.status === 'failed') { + return ( + + ); + } + if (row.status === 'redelivery') { + return ( + + ); + } + return ; + }, + }, + ]; + + const annotationOptions = useMemo( + () => ({ + showToolbar: true, + showThemeToggle: true, + showColorFilter: true, + emptyWhenNoData: false, + toolbarEdge: 'right', + currentPageId: active ? 'detail' : 'list', + }), + [active], + ); + + return ( + +
      + {!active && ( + <> +
      +
      +

      筛选条件

      +
      +
      +
      + + `, + ).join(''); + return `
      +
      ${item}
      +
      ${radios}
      +
      `; + }) + .join(''); + return `
      ${g.title}
      ${rows}
      `; + }).join(''); + + app.innerHTML = ` +
      + +

      附件4 · 第 ${line.seq} 台

      +
      ${VPField.signoffName(task)}
      + + + + + + + + + + + +
      + + + + + +
      交车及接车时待检验项目
      +
      点选 √ / N / × / O(演示可「一键全 √」)
      +
      + ${groupsHtml} + + + + +
      + + +
      +
        + +
        双方确认
        + + + +
        + + +
        +
        `; + + const evs = []; + document.getElementById('back').onclick = () => { + view = { name: 'list' }; + render(); + }; + document.getElementById('ocr').onclick = () => { + document.getElementById('vin').value = VPField.mockOcr(ocrIdx++); + toast('车架号 OCR 回填(模拟)'); + }; + document.getElementById('all-ok').onclick = () => { + document.querySelectorAll('[data-item]').forEach((row) => { + const ok = row.querySelector('input[value=ok]'); + if (ok) ok.checked = true; + }); + toast('已全部标记 √'); + }; + document.getElementById('ev-l').onclick = () => { + evs.push({ uid: 'ev-l-' + Date.now(), name: '左前45度.jpg', kind: 'photo', photoAngle: 'left45' }); + document.getElementById('ev-list').innerHTML = evs.map((e) => `
      • ${e.name}
      • `).join(''); + }; + document.getElementById('ev-r').onclick = () => { + evs.push({ uid: 'ev-r-' + Date.now(), name: '右后45度.jpg', kind: 'photo', photoAngle: 'right45' }); + document.getElementById('ev-list').innerHTML = evs.map((e) => `
      • ${e.name}
      • `).join(''); + }; + document.getElementById('pass').onclick = () => { + try { + const payload = collectPayload({ seq: line.seq, evidences: evs }); + const t = VPField.passLine(task, line.id, payload); + document.getElementById('esign-box').hidden = false; + document.getElementById('esign-box').textContent = + 'E签宝已生成:' + (t.vehicles.find((v) => v.id === line.id) || {}).esignFileName; + toast('交接验收通过,双签完成(模拟)'); + setTimeout(() => { + view = { name: 'list' }; + render(); + }, 800); + } catch (e) { + toast(e.message || String(e)); + } + }; + document.getElementById('failbtn').onclick = () => { + try { + const payload = collectPayload({ + seq: line.seq, + evidences: evs, + failReason: document.getElementById('remark').value || '交接验收不合格', + }); + VPField.failLine(task, line.id, payload); + toast('已拒收并留痕,可发起重交'); + view = { name: 'list' }; + render(); + } catch (e) { + toast(e.message || String(e)); + } + }; + } + + function render() { + if (view.name === 'edit') renderEdit(); + else renderList(); + } + + render(); +})(); diff --git a/src/prototypes/vehicle-inspection-miniprogram-legacy/field-core.js b/src/prototypes/vehicle-inspection-miniprogram-legacy/field-core.js new file mode 100644 index 0000000..f8e0939 --- /dev/null +++ b/src/prototypes/vehicle-inspection-miniprogram-legacy/field-core.js @@ -0,0 +1,364 @@ +/** + * 外勤验车 · 对齐《附件4:车辆交接验收表》 + */ +(function (global) { + const KEYS = { + inspections: 'oneos.vp.inspections', + stocked: 'oneos.vp.stockedVehicles', + activeId: 'oneos.vp.activeInspectionId', + }; + + const VIN_REGEX = /^[A-HJ-NPR-Z0-9]{17}$/; + const OCR_SAMPLES = ['LB9A32A28P0512347', 'LB9A32A20R0512238', 'LZZ1CLWB0PA123456']; + + const CHECK_GROUPS = [ + { + title: '外观与标识', + items: [ + '整车外观状况(与公告目录样车相同)', + '漆面状况', + '喇叭状况', + '车辆照片(左前45°+右后45°)', + '汽车标识LOGO', + '车辆底盘状况', + '整车配置表', + ], + }, + { + title: '动力与续航', + items: [ + '冷却风扇转动状况', + '供氢量/续航电量(≥95%)', + '钥匙(2把)', + '动力舱状况', + '车辆启动状况', + '档位变换(前进档、倒档)', + ], + }, + { + title: '驾驶室与电器', + items: [ + '车辆内饰状况', + '灯光状况', + '前后灯总成状况', + '风挡玻璃状况', + '仪表盘功能状况', + '雨刮器状况', + '座椅状况', + '安全带状况', + '左右后视镜状况', + '车内反光镜状况', + '空调状况', + '刹车系统状况', + '制动器助力性能状况', + '方向盘助力性能状况', + ], + }, + { + title: '随车工具与备件', + items: [ + '灭火器(1个)', + '备胎(1只)', + '点烟器(1个)', + '反光衣', + '千斤顶', + '轮胎撬棍', + '车辆反光贴', + '三角警示牌', + '钳子/轮胎扳手', + '备胎扬撬', + '轴头保养扳手', + '挡车器', + '胎压检测值', + '标准量去离子水', + '标准量冷却液', + ], + }, + { + title: '文件资料', + items: ['用户操作手册', '质量保修手册', '保修IC卡'], + }, + { + title: '车身结构', + items: [ + '四轮轮胎状况', + '挡泥板状况', + '侧防护围杠/后部护围杠', + '左右中后门状况', + '货箱箱体状况', + ], + }, + ]; + + const CHECK_ITEMS = CHECK_GROUPS.flatMap((g) => g.items); + const SIGNOFF_DOC = '车辆交接验收表'; + const MARKS = [ + { value: 'ok', label: '√' }, + { value: 'missing', label: 'N' }, + { value: 'damage', label: '×' }, + { value: 'deform', label: 'O' }, + ]; + + function read(key, fallback) { + try { + const raw = localStorage.getItem(key); + return raw ? JSON.parse(raw) : fallback; + } catch { + return fallback; + } + } + + function write(key, val) { + localStorage.setItem(key, JSON.stringify(val)); + } + + function nowStr() { + const d = new Date(); + const p = (n) => String(n).padStart(2, '0'); + return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`; + } + + function normalizeVin(raw) { + return String(raw || '') + .trim() + .toUpperCase() + .replace(/\s+/g, ''); + } + + function vinMessage(raw) { + const v = normalizeVin(raw); + if (!v) return '请填写车架号'; + if (v.length !== 17) return '车架号须为 17 位'; + if (!VIN_REGEX.test(v)) return '车架号格式不正确(不可含 I/O/Q)'; + return null; + } + + function getTasks() { + return read(KEYS.inspections, []); + } + + function saveTasks(list) { + write(KEYS.inspections, list); + } + + function getActiveTask() { + const id = localStorage.getItem(KEYS.activeId); + const tasks = getTasks(); + if (id) { + const found = tasks.find((t) => t.id === id); + if (found) return found; + } + return tasks[0] || null; + } + + function updateTask(task) { + const list = getTasks(); + const next = list.some((t) => t.id === task.id) + ? list.map((t) => (t.id === task.id ? task : t)) + : [task, ...list]; + saveTasks(next); + return task; + } + + function mockOcr(i) { + return OCR_SAMPLES[i % OCR_SAMPLES.length]; + } + + function checkItemsOf(task) { + return task.acceptanceCheckItems && task.acceptanceCheckItems.length + ? task.acceptanceCheckItems + : CHECK_ITEMS; + } + + function signoffName(task) { + return task.signoffDocName || SIGNOFF_DOC; + } + + function allMarksOk(marks, items) { + if (!marks) return false; + return items.every((it) => marks[it] === 'ok' || marks[it] === 'na'); + } + + function passLine(task, lineId, payload) { + const vinErr = vinMessage(payload.vin); + if (vinErr) throw new Error(vinErr); + const items = checkItemsOf(task); + if (!allMarksOk(payload.handoverMarks, items)) { + throw new Error('交接验收表存在异常/未检项目,不能签收通过(请全部标 √ 或改为拒收)'); + } + if (!payload.delivererSigned || !payload.receiverSigned) { + throw new Error('须完成交车方与接车方双签'); + } + if (!payload.evidences || payload.evidences.length < 2) { + throw new Error('须上传左前45°与右后45°车辆照片'); + } + const vin = normalizeVin(payload.vin); + const doc = signoffName(task); + const esign = `${task.code}-${String(payload.seq || 1).padStart(2, '0')}-${doc}-电子签章.pdf`; + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { + ...l, + vin, + vehicleModel: payload.vehicleModel || l.vehicleModel || task.vehicleModel, + deliverUnit: payload.deliverUnit || l.deliverUnit || task.defaultDeliverUnit, + receiveUnit: payload.receiveUnit || l.receiveUnit || task.defaultReceiveUnit, + handoverDate: payload.handoverDate || l.handoverDate, + odometerKm: payload.odometerKm != null ? Number(payload.odometerKm) : l.odometerKm, + motorNo: payload.motorNo || l.motorNo, + remark: payload.remark || '', + status: 'passed', + handoverMarks: payload.handoverMarks, + checkItems: items.filter((it) => payload.handoverMarks[it] === 'ok'), + evidences: payload.evidences, + delivererSigned: true, + receiverSigned: true, + esignFileName: esign, + signedAt: nowStr(), + failReason: undefined, + }; + }); + return updateTask({ + ...task, + vehicles, + status: 'in_progress', + updatedAt: nowStr(), + }); + } + + function failLine(task, lineId, payload) { + if (!payload.failReason || !String(payload.failReason).trim()) { + throw new Error('拒收须填写备注/原因'); + } + if (!payload.evidences || !payload.evidences.length) { + throw new Error('拒收须上传现场证据'); + } + const vin = payload.vin ? normalizeVin(payload.vin) : ''; + if (vin && vinMessage(vin)) throw new Error(vinMessage(vin)); + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { + ...l, + vin: vin || l.vin, + vehicleModel: payload.vehicleModel || l.vehicleModel, + deliverUnit: payload.deliverUnit || l.deliverUnit, + receiveUnit: payload.receiveUnit || l.receiveUnit, + handoverDate: payload.handoverDate || l.handoverDate, + odometerKm: payload.odometerKm != null ? Number(payload.odometerKm) : l.odometerKm, + motorNo: payload.motorNo || l.motorNo, + remark: payload.remark || payload.failReason, + status: 'failed', + failReason: String(payload.failReason).trim(), + handoverMarks: payload.handoverMarks || {}, + evidences: payload.evidences, + lastRejectedAt: nowStr(), + }; + }); + return updateTask({ + ...task, + vehicles, + status: 'in_progress', + updatedAt: nowStr(), + }); + } + + function requestRedelivery(task, lineId) { + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { + ...l, + status: 'redelivery', + redeliveryCount: (l.redeliveryCount || 0) + 1, + vin: '', + esignFileName: undefined, + signedAt: undefined, + evidences: [], + checkItems: undefined, + handoverMarks: undefined, + delivererSigned: false, + receiverSigned: false, + }; + }); + return updateTask({ ...task, vehicles, updatedAt: nowStr() }); + } + + function startRedelivery(task, lineId) { + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { ...l, status: 'awaiting' }; + }); + return updateTask({ ...task, vehicles, updatedAt: nowStr() }); + } + + function appendLine(task) { + if (task.vehicles.length >= task.expectedQty) { + throw new Error('已达到合同应验台数'); + } + const seq = task.vehicles.length + 1; + const line = { + id: `${task.id}-line-${seq}`, + seq, + vin: '', + status: 'awaiting', + evidences: [], + redeliveryCount: 0, + vehicleModel: task.vehicleModel, + deliverUnit: task.defaultDeliverUnit, + receiveUnit: task.defaultReceiveUnit, + handoverDate: task.inspectDate, + }; + return updateTask({ + ...task, + vehicles: [...task.vehicles, line], + status: 'in_progress', + updatedAt: nowStr(), + }); + } + + function stockPassed(task) { + const stocked = read(KEYS.stocked, []); + let n = 0; + const vehicles = task.vehicles.map((l) => { + if (l.status !== 'passed' || !l.vin) return l; + const id = `sv-${Date.now()}-${l.seq}`; + stocked.unshift({ + id, + vin: l.vin, + brandModel: l.vehicleModel || task.vehicleModel, + purchaseContractId: task.contractId, + purchaseContractCode: task.contractCode, + purchaseDate: nowStr().slice(0, 10), + status: '未备车', + sourceInspectionTaskId: task.id, + sourceLineId: l.id, + }); + n += 1; + return { ...l, status: 'stocked', stockedAt: nowStr(), stockedVehicleId: id }; + }); + write(KEYS.stocked, stocked); + updateTask({ ...task, vehicles, updatedAt: nowStr() }); + return n; + } + + global.VPField = { + KEYS, + CHECK_ITEMS, + CHECK_GROUPS, + MARKS, + SIGNOFF_DOC, + getActiveTask, + getTasks, + passLine, + failLine, + requestRedelivery, + startRedelivery, + appendLine, + stockPassed, + mockOcr, + vinMessage, + normalizeVin, + checkItemsOf, + signoffName, + nowStr, + }; +})(window); diff --git a/src/prototypes/vehicle-inspection-miniprogram-legacy/field-theme.css b/src/prototypes/vehicle-inspection-miniprogram-legacy/field-theme.css new file mode 100644 index 0000000..666979f --- /dev/null +++ b/src/prototypes/vehicle-inspection-miniprogram-legacy/field-theme.css @@ -0,0 +1,52 @@ +:root { + --ln-primary: #32a06e; + --ln-primary-hover: #3fb87c; + --ln-ink: #18181b; + --ln-muted: #71717a; + --ln-canvas: #f5f6f6; + --ln-surface: #ffffff; + --ln-hairline: #e5e7eb; + --ln-error: #e5484d; + --ln-success: #27a644; + --ln-radius-control: 8px; + --ln-radius-card: 12px; + /* aliases used by existing HTML */ + --bg: var(--ln-canvas); + --card: var(--ln-surface); + --text: var(--ln-ink); + --muted: var(--ln-muted); + --primary: var(--ln-primary); + --danger: var(--ln-error); + --border: var(--ln-hairline); + /* semantic tints */ + --ln-primary-soft: #e8f5ef; + --ln-success-soft: #e6f6ea; + --ln-error-soft: #fdeeee; + --ln-primary-border: rgba(50, 160, 110, 0.45); +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation: none !important; + transition: none !important; + } +} + +button { + min-height: 44px; +} + +button:focus-visible, +input:focus-visible, +textarea:focus-visible, +select:focus-visible { + outline: 2px solid var(--ln-primary); + outline-offset: 1px; +} + +button.primary:hover { + background: var(--ln-primary-hover); + border-color: var(--ln-primary-hover); +} diff --git a/src/prototypes/vehicle-inspection-miniprogram-legacy/index.html b/src/prototypes/vehicle-inspection-miniprogram-legacy/index.html new file mode 100644 index 0000000..ff7e8af --- /dev/null +++ b/src/prototypes/vehicle-inspection-miniprogram-legacy/index.html @@ -0,0 +1,156 @@ + + + + + + 验车入库 · 小程序 + + + + +
        +
        + +
        验车入库
        +
        + +
        +
        +
        + + + + diff --git a/src/prototypes/vehicle-inspection-miniprogram/app.js b/src/prototypes/vehicle-inspection-miniprogram/app.js new file mode 100644 index 0000000..1996790 --- /dev/null +++ b/src/prototypes/vehicle-inspection-miniprogram/app.js @@ -0,0 +1,333 @@ +(function () { + const app = document.getElementById('app'); + const toastEl = document.getElementById('toast'); + let view = { name: 'list' }; + let ocrIdx = 0; + + function toast(msg) { + toastEl.textContent = msg; + toastEl.style.display = 'block'; + setTimeout(() => { + toastEl.style.display = 'none'; + }, 2800); + } + + function statusBadge(s) { + if (s === 'stocked') return '已入库'; + if (s === 'passed') return '已签收'; + if (s === 'failed') return '拒收'; + if (s === 'redelivery') return '重交中'; + return '待登记'; + } + + function ensureDemoTask() { + let task = VPField.getActiveTask(); + if (task) { + if (!task.acceptanceCheckItems) task.acceptanceCheckItems = VPField.CHECK_ITEMS.slice(); + if (!task.signoffDocName) task.signoffDocName = VPField.SIGNOFF_DOC; + return task; + } + const demo = { + id: 'insp-demo-field', + code: 'YC-DEMO-0020', + contractId: 'pc-demo-20', + contractCode: 'HT-DEMO-20', + vehicleModel: '4.5T 燃料电池车', + expectedQty: 20, + inspectLocation: '指定交付场地', + inspectDate: '2026-06-16', + status: 'pending', + defaultDeliverUnit: '交车单位(卖方)', + defaultReceiveUnit: '接车单位(买方)', + acceptanceCheckItems: VPField.CHECK_ITEMS.slice(), + signoffDocName: VPField.SIGNOFF_DOC, + vehicles: [1, 2, 3].map((seq) => ({ + id: 'demo-line-' + seq, + seq, + vin: '', + status: 'awaiting', + evidences: [], + redeliveryCount: 0, + vehicleModel: '4.5T 燃料电池车', + deliverUnit: '交车单位(卖方)', + receiveUnit: '接车单位(买方)', + handoverDate: '2026-06-16', + })), + createdAt: VPField.nowStr(), + updatedAt: VPField.nowStr(), + }; + const list = VPField.getTasks(); + list.unshift(demo); + localStorage.setItem(VPField.KEYS.inspections, JSON.stringify(list)); + localStorage.setItem(VPField.KEYS.activeId, demo.id); + return demo; + } + + function renderList() { + const task = ensureDemoTask(); + const lines = task.vehicles + .map( + (l) => + `
        +
        #${l.seq} ${statusBadge(l.status)}
        +
        车架号 ${l.vin || '未登记'} · 里程 ${l.odometerKm != null ? l.odometerKm + ' km' : '—'} + ${l.delivererSigned && l.receiverSigned ? ' · 已双签' : ''}
        +
        `, + ) + .join(''); + app.innerHTML = ` +
        +

        ${task.code}

        +
        附件4 · ${VPField.signoffName(task)}
        +
        ${task.contractCode} · ${task.vehicleModel}
        +
        ${task.inspectLocation} · ${task.inspectDate}
        +
        明细 ${task.vehicles.length}/${task.expectedQty}(可追加到车)
        +
        +
        +
        车辆明细
        + ${lines} +
        + + +
        +

        图例:√正常 · N缺少 · ×损坏 · O变形。通过须全项√且交/接车双签,并上传左前45°+右后45°照片。

        +
        `; + app.querySelectorAll('.line').forEach((el) => { + el.addEventListener('click', () => { + view = { name: 'edit', lineId: el.getAttribute('data-id') }; + render(); + }); + }); + document.getElementById('btn-stock').onclick = () => { + const n = VPField.stockPassed(VPField.getActiveTask()); + toast(n ? `已入库 ${n} 台` : '没有待入库的签收车辆'); + render(); + }; + document.getElementById('btn-append').onclick = () => { + try { + VPField.appendLine(VPField.getActiveTask()); + toast('已追加到车登记位'); + render(); + } catch (e) { + toast(e.message || String(e)); + } + }; + } + + function collectMarks() { + const marks = {}; + document.querySelectorAll('[data-item]').forEach((row) => { + const item = row.getAttribute('data-item'); + const checked = row.querySelector('input[type=radio]:checked'); + if (checked) marks[item] = checked.value; + }); + return marks; + } + + function collectPayload(base) { + return { + ...base, + vin: document.getElementById('vin').value, + vehicleModel: document.getElementById('model').value, + deliverUnit: document.getElementById('deliver').value, + receiveUnit: document.getElementById('receive').value, + handoverDate: document.getElementById('hdate').value, + odometerKm: document.getElementById('odo').value, + motorNo: document.getElementById('motor').value, + remark: document.getElementById('remark').value, + delivererSigned: document.getElementById('sign-d').checked, + receiverSigned: document.getElementById('sign-r').checked, + handoverMarks: collectMarks(), + }; + } + + function renderEdit() { + const task = ensureDemoTask(); + const line = task.vehicles.find((l) => l.id === view.lineId); + if (!line) { + view = { name: 'list' }; + return render(); + } + + if (line.status === 'failed') { + app.innerHTML = ` +
        + +

        第 ${line.seq} 台 · 已拒收

        +

        ${line.failReason || ''}

        +
        +
        `; + document.getElementById('back').onclick = () => { + view = { name: 'list' }; + render(); + }; + document.getElementById('rede').onclick = () => { + VPField.requestRedelivery(task, line.id); + toast('已发起重交'); + view = { name: 'list' }; + render(); + }; + return; + } + if (line.status === 'redelivery') { + app.innerHTML = ` +
        + +

        第 ${line.seq} 台 · 重交中

        +
        +
        `; + document.getElementById('back').onclick = () => { + view = { name: 'list' }; + render(); + }; + document.getElementById('start').onclick = () => { + VPField.startRedelivery(task, line.id); + toast('已进入待登记'); + render(); + }; + return; + } + if (line.status === 'passed' || line.status === 'stocked') { + app.innerHTML = ` +
        + +

        第 ${line.seq} 台

        +

        ${statusBadge(line.status)} · 车架号 ${line.vin}

        +

        里程 ${line.odometerKm != null ? line.odometerKm : '—'} km · 电机号 ${line.motorNo || '—'}

        +
        ${line.esignFileName || ''}
        +
        `; + document.getElementById('back').onclick = () => { + view = { name: 'list' }; + render(); + }; + return; + } + + const groupsHtml = VPField.CHECK_GROUPS.map((g) => { + const rows = g.items + .map((item) => { + const radios = VPField.MARKS.map( + (m) => + ``, + ).join(''); + return `
        +
        ${item}
        +
        ${radios}
        +
        `; + }) + .join(''); + return `
        ${g.title}
        ${rows}
        `; + }).join(''); + + app.innerHTML = ` +
        + +

        附件4 · 第 ${line.seq} 台

        +
        ${VPField.signoffName(task)}
        + + + + + + + + + + + +
        + + + + + +
        交车及接车时待检验项目
        +
        点选 √ / N / × / O(演示可「一键全 √」)
        +
        + ${groupsHtml} + + + + +
        + + +
        +
          + +
          双方确认
          + + + +
          + + +
          +
          `; + + const evs = []; + document.getElementById('back').onclick = () => { + view = { name: 'list' }; + render(); + }; + document.getElementById('ocr').onclick = () => { + document.getElementById('vin').value = VPField.mockOcr(ocrIdx++); + toast('车架号 OCR 回填(模拟)'); + }; + document.getElementById('all-ok').onclick = () => { + document.querySelectorAll('[data-item]').forEach((row) => { + const ok = row.querySelector('input[value=ok]'); + if (ok) ok.checked = true; + }); + toast('已全部标记 √'); + }; + document.getElementById('ev-l').onclick = () => { + evs.push({ uid: 'ev-l-' + Date.now(), name: '左前45度.jpg', kind: 'photo', photoAngle: 'left45' }); + document.getElementById('ev-list').innerHTML = evs.map((e) => `
        • ${e.name}
        • `).join(''); + }; + document.getElementById('ev-r').onclick = () => { + evs.push({ uid: 'ev-r-' + Date.now(), name: '右后45度.jpg', kind: 'photo', photoAngle: 'right45' }); + document.getElementById('ev-list').innerHTML = evs.map((e) => `
        • ${e.name}
        • `).join(''); + }; + document.getElementById('pass').onclick = () => { + try { + const payload = collectPayload({ seq: line.seq, evidences: evs }); + const t = VPField.passLine(task, line.id, payload); + document.getElementById('esign-box').hidden = false; + document.getElementById('esign-box').textContent = + 'E签宝已生成:' + (t.vehicles.find((v) => v.id === line.id) || {}).esignFileName; + toast('交接验收通过,双签完成(模拟)'); + setTimeout(() => { + view = { name: 'list' }; + render(); + }, 800); + } catch (e) { + toast(e.message || String(e)); + } + }; + document.getElementById('failbtn').onclick = () => { + try { + const payload = collectPayload({ + seq: line.seq, + evidences: evs, + failReason: document.getElementById('remark').value || '交接验收不合格', + }); + VPField.failLine(task, line.id, payload); + toast('已拒收并留痕,可发起重交'); + view = { name: 'list' }; + render(); + } catch (e) { + toast(e.message || String(e)); + } + }; + } + + function render() { + if (view.name === 'edit') renderEdit(); + else renderList(); + } + + render(); +})(); diff --git a/src/prototypes/vehicle-inspection-miniprogram/field-core.js b/src/prototypes/vehicle-inspection-miniprogram/field-core.js new file mode 100644 index 0000000..f8e0939 --- /dev/null +++ b/src/prototypes/vehicle-inspection-miniprogram/field-core.js @@ -0,0 +1,364 @@ +/** + * 外勤验车 · 对齐《附件4:车辆交接验收表》 + */ +(function (global) { + const KEYS = { + inspections: 'oneos.vp.inspections', + stocked: 'oneos.vp.stockedVehicles', + activeId: 'oneos.vp.activeInspectionId', + }; + + const VIN_REGEX = /^[A-HJ-NPR-Z0-9]{17}$/; + const OCR_SAMPLES = ['LB9A32A28P0512347', 'LB9A32A20R0512238', 'LZZ1CLWB0PA123456']; + + const CHECK_GROUPS = [ + { + title: '外观与标识', + items: [ + '整车外观状况(与公告目录样车相同)', + '漆面状况', + '喇叭状况', + '车辆照片(左前45°+右后45°)', + '汽车标识LOGO', + '车辆底盘状况', + '整车配置表', + ], + }, + { + title: '动力与续航', + items: [ + '冷却风扇转动状况', + '供氢量/续航电量(≥95%)', + '钥匙(2把)', + '动力舱状况', + '车辆启动状况', + '档位变换(前进档、倒档)', + ], + }, + { + title: '驾驶室与电器', + items: [ + '车辆内饰状况', + '灯光状况', + '前后灯总成状况', + '风挡玻璃状况', + '仪表盘功能状况', + '雨刮器状况', + '座椅状况', + '安全带状况', + '左右后视镜状况', + '车内反光镜状况', + '空调状况', + '刹车系统状况', + '制动器助力性能状况', + '方向盘助力性能状况', + ], + }, + { + title: '随车工具与备件', + items: [ + '灭火器(1个)', + '备胎(1只)', + '点烟器(1个)', + '反光衣', + '千斤顶', + '轮胎撬棍', + '车辆反光贴', + '三角警示牌', + '钳子/轮胎扳手', + '备胎扬撬', + '轴头保养扳手', + '挡车器', + '胎压检测值', + '标准量去离子水', + '标准量冷却液', + ], + }, + { + title: '文件资料', + items: ['用户操作手册', '质量保修手册', '保修IC卡'], + }, + { + title: '车身结构', + items: [ + '四轮轮胎状况', + '挡泥板状况', + '侧防护围杠/后部护围杠', + '左右中后门状况', + '货箱箱体状况', + ], + }, + ]; + + const CHECK_ITEMS = CHECK_GROUPS.flatMap((g) => g.items); + const SIGNOFF_DOC = '车辆交接验收表'; + const MARKS = [ + { value: 'ok', label: '√' }, + { value: 'missing', label: 'N' }, + { value: 'damage', label: '×' }, + { value: 'deform', label: 'O' }, + ]; + + function read(key, fallback) { + try { + const raw = localStorage.getItem(key); + return raw ? JSON.parse(raw) : fallback; + } catch { + return fallback; + } + } + + function write(key, val) { + localStorage.setItem(key, JSON.stringify(val)); + } + + function nowStr() { + const d = new Date(); + const p = (n) => String(n).padStart(2, '0'); + return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`; + } + + function normalizeVin(raw) { + return String(raw || '') + .trim() + .toUpperCase() + .replace(/\s+/g, ''); + } + + function vinMessage(raw) { + const v = normalizeVin(raw); + if (!v) return '请填写车架号'; + if (v.length !== 17) return '车架号须为 17 位'; + if (!VIN_REGEX.test(v)) return '车架号格式不正确(不可含 I/O/Q)'; + return null; + } + + function getTasks() { + return read(KEYS.inspections, []); + } + + function saveTasks(list) { + write(KEYS.inspections, list); + } + + function getActiveTask() { + const id = localStorage.getItem(KEYS.activeId); + const tasks = getTasks(); + if (id) { + const found = tasks.find((t) => t.id === id); + if (found) return found; + } + return tasks[0] || null; + } + + function updateTask(task) { + const list = getTasks(); + const next = list.some((t) => t.id === task.id) + ? list.map((t) => (t.id === task.id ? task : t)) + : [task, ...list]; + saveTasks(next); + return task; + } + + function mockOcr(i) { + return OCR_SAMPLES[i % OCR_SAMPLES.length]; + } + + function checkItemsOf(task) { + return task.acceptanceCheckItems && task.acceptanceCheckItems.length + ? task.acceptanceCheckItems + : CHECK_ITEMS; + } + + function signoffName(task) { + return task.signoffDocName || SIGNOFF_DOC; + } + + function allMarksOk(marks, items) { + if (!marks) return false; + return items.every((it) => marks[it] === 'ok' || marks[it] === 'na'); + } + + function passLine(task, lineId, payload) { + const vinErr = vinMessage(payload.vin); + if (vinErr) throw new Error(vinErr); + const items = checkItemsOf(task); + if (!allMarksOk(payload.handoverMarks, items)) { + throw new Error('交接验收表存在异常/未检项目,不能签收通过(请全部标 √ 或改为拒收)'); + } + if (!payload.delivererSigned || !payload.receiverSigned) { + throw new Error('须完成交车方与接车方双签'); + } + if (!payload.evidences || payload.evidences.length < 2) { + throw new Error('须上传左前45°与右后45°车辆照片'); + } + const vin = normalizeVin(payload.vin); + const doc = signoffName(task); + const esign = `${task.code}-${String(payload.seq || 1).padStart(2, '0')}-${doc}-电子签章.pdf`; + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { + ...l, + vin, + vehicleModel: payload.vehicleModel || l.vehicleModel || task.vehicleModel, + deliverUnit: payload.deliverUnit || l.deliverUnit || task.defaultDeliverUnit, + receiveUnit: payload.receiveUnit || l.receiveUnit || task.defaultReceiveUnit, + handoverDate: payload.handoverDate || l.handoverDate, + odometerKm: payload.odometerKm != null ? Number(payload.odometerKm) : l.odometerKm, + motorNo: payload.motorNo || l.motorNo, + remark: payload.remark || '', + status: 'passed', + handoverMarks: payload.handoverMarks, + checkItems: items.filter((it) => payload.handoverMarks[it] === 'ok'), + evidences: payload.evidences, + delivererSigned: true, + receiverSigned: true, + esignFileName: esign, + signedAt: nowStr(), + failReason: undefined, + }; + }); + return updateTask({ + ...task, + vehicles, + status: 'in_progress', + updatedAt: nowStr(), + }); + } + + function failLine(task, lineId, payload) { + if (!payload.failReason || !String(payload.failReason).trim()) { + throw new Error('拒收须填写备注/原因'); + } + if (!payload.evidences || !payload.evidences.length) { + throw new Error('拒收须上传现场证据'); + } + const vin = payload.vin ? normalizeVin(payload.vin) : ''; + if (vin && vinMessage(vin)) throw new Error(vinMessage(vin)); + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { + ...l, + vin: vin || l.vin, + vehicleModel: payload.vehicleModel || l.vehicleModel, + deliverUnit: payload.deliverUnit || l.deliverUnit, + receiveUnit: payload.receiveUnit || l.receiveUnit, + handoverDate: payload.handoverDate || l.handoverDate, + odometerKm: payload.odometerKm != null ? Number(payload.odometerKm) : l.odometerKm, + motorNo: payload.motorNo || l.motorNo, + remark: payload.remark || payload.failReason, + status: 'failed', + failReason: String(payload.failReason).trim(), + handoverMarks: payload.handoverMarks || {}, + evidences: payload.evidences, + lastRejectedAt: nowStr(), + }; + }); + return updateTask({ + ...task, + vehicles, + status: 'in_progress', + updatedAt: nowStr(), + }); + } + + function requestRedelivery(task, lineId) { + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { + ...l, + status: 'redelivery', + redeliveryCount: (l.redeliveryCount || 0) + 1, + vin: '', + esignFileName: undefined, + signedAt: undefined, + evidences: [], + checkItems: undefined, + handoverMarks: undefined, + delivererSigned: false, + receiverSigned: false, + }; + }); + return updateTask({ ...task, vehicles, updatedAt: nowStr() }); + } + + function startRedelivery(task, lineId) { + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { ...l, status: 'awaiting' }; + }); + return updateTask({ ...task, vehicles, updatedAt: nowStr() }); + } + + function appendLine(task) { + if (task.vehicles.length >= task.expectedQty) { + throw new Error('已达到合同应验台数'); + } + const seq = task.vehicles.length + 1; + const line = { + id: `${task.id}-line-${seq}`, + seq, + vin: '', + status: 'awaiting', + evidences: [], + redeliveryCount: 0, + vehicleModel: task.vehicleModel, + deliverUnit: task.defaultDeliverUnit, + receiveUnit: task.defaultReceiveUnit, + handoverDate: task.inspectDate, + }; + return updateTask({ + ...task, + vehicles: [...task.vehicles, line], + status: 'in_progress', + updatedAt: nowStr(), + }); + } + + function stockPassed(task) { + const stocked = read(KEYS.stocked, []); + let n = 0; + const vehicles = task.vehicles.map((l) => { + if (l.status !== 'passed' || !l.vin) return l; + const id = `sv-${Date.now()}-${l.seq}`; + stocked.unshift({ + id, + vin: l.vin, + brandModel: l.vehicleModel || task.vehicleModel, + purchaseContractId: task.contractId, + purchaseContractCode: task.contractCode, + purchaseDate: nowStr().slice(0, 10), + status: '未备车', + sourceInspectionTaskId: task.id, + sourceLineId: l.id, + }); + n += 1; + return { ...l, status: 'stocked', stockedAt: nowStr(), stockedVehicleId: id }; + }); + write(KEYS.stocked, stocked); + updateTask({ ...task, vehicles, updatedAt: nowStr() }); + return n; + } + + global.VPField = { + KEYS, + CHECK_ITEMS, + CHECK_GROUPS, + MARKS, + SIGNOFF_DOC, + getActiveTask, + getTasks, + passLine, + failLine, + requestRedelivery, + startRedelivery, + appendLine, + stockPassed, + mockOcr, + vinMessage, + normalizeVin, + checkItemsOf, + signoffName, + nowStr, + }; +})(window); diff --git a/src/prototypes/vehicle-inspection-miniprogram/field-theme.css b/src/prototypes/vehicle-inspection-miniprogram/field-theme.css new file mode 100644 index 0000000..666979f --- /dev/null +++ b/src/prototypes/vehicle-inspection-miniprogram/field-theme.css @@ -0,0 +1,52 @@ +:root { + --ln-primary: #32a06e; + --ln-primary-hover: #3fb87c; + --ln-ink: #18181b; + --ln-muted: #71717a; + --ln-canvas: #f5f6f6; + --ln-surface: #ffffff; + --ln-hairline: #e5e7eb; + --ln-error: #e5484d; + --ln-success: #27a644; + --ln-radius-control: 8px; + --ln-radius-card: 12px; + /* aliases used by existing HTML */ + --bg: var(--ln-canvas); + --card: var(--ln-surface); + --text: var(--ln-ink); + --muted: var(--ln-muted); + --primary: var(--ln-primary); + --danger: var(--ln-error); + --border: var(--ln-hairline); + /* semantic tints */ + --ln-primary-soft: #e8f5ef; + --ln-success-soft: #e6f6ea; + --ln-error-soft: #fdeeee; + --ln-primary-border: rgba(50, 160, 110, 0.45); +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation: none !important; + transition: none !important; + } +} + +button { + min-height: 44px; +} + +button:focus-visible, +input:focus-visible, +textarea:focus-visible, +select:focus-visible { + outline: 2px solid var(--ln-primary); + outline-offset: 1px; +} + +button.primary:hover { + background: var(--ln-primary-hover); + border-color: var(--ln-primary-hover); +} diff --git a/src/prototypes/vehicle-inspection-miniprogram/index.html b/src/prototypes/vehicle-inspection-miniprogram/index.html new file mode 100644 index 0000000..ff7e8af --- /dev/null +++ b/src/prototypes/vehicle-inspection-miniprogram/index.html @@ -0,0 +1,156 @@ + + + + + + 验车入库 · 小程序 + + + + +
          +
          + +
          验车入库
          +
          + +
          +
          +
          + + + + diff --git a/src/prototypes/vehicle-inspection-web-legacy/LEGACY.md b/src/prototypes/vehicle-inspection-web-legacy/LEGACY.md new file mode 100644 index 0000000..94d1397 --- /dev/null +++ b/src/prototypes/vehicle-inspection-web-legacy/LEGACY.md @@ -0,0 +1,8 @@ +# 【旧版】vehicle-inspection-web + +本目录为设计规范 v2 套用前的**冻结对照副本**(2026-07-22)。 + +- 源原型:`src/prototypes/vehicle-inspection-web/` +- 请勿在此目录继续改业务;正式改造只在源目录进行 +- 已跳过复制:`.spec/acp`、`.spec/prototype-comment-assets`(批注大图/会话) +- 交叉引用已尽量改写为同套 `*-legacy`,避免引用被改版后的正式样式 diff --git a/src/prototypes/vehicle-inspection-web-legacy/app.js b/src/prototypes/vehicle-inspection-web-legacy/app.js new file mode 100644 index 0000000..1996790 --- /dev/null +++ b/src/prototypes/vehicle-inspection-web-legacy/app.js @@ -0,0 +1,333 @@ +(function () { + const app = document.getElementById('app'); + const toastEl = document.getElementById('toast'); + let view = { name: 'list' }; + let ocrIdx = 0; + + function toast(msg) { + toastEl.textContent = msg; + toastEl.style.display = 'block'; + setTimeout(() => { + toastEl.style.display = 'none'; + }, 2800); + } + + function statusBadge(s) { + if (s === 'stocked') return '已入库'; + if (s === 'passed') return '已签收'; + if (s === 'failed') return '拒收'; + if (s === 'redelivery') return '重交中'; + return '待登记'; + } + + function ensureDemoTask() { + let task = VPField.getActiveTask(); + if (task) { + if (!task.acceptanceCheckItems) task.acceptanceCheckItems = VPField.CHECK_ITEMS.slice(); + if (!task.signoffDocName) task.signoffDocName = VPField.SIGNOFF_DOC; + return task; + } + const demo = { + id: 'insp-demo-field', + code: 'YC-DEMO-0020', + contractId: 'pc-demo-20', + contractCode: 'HT-DEMO-20', + vehicleModel: '4.5T 燃料电池车', + expectedQty: 20, + inspectLocation: '指定交付场地', + inspectDate: '2026-06-16', + status: 'pending', + defaultDeliverUnit: '交车单位(卖方)', + defaultReceiveUnit: '接车单位(买方)', + acceptanceCheckItems: VPField.CHECK_ITEMS.slice(), + signoffDocName: VPField.SIGNOFF_DOC, + vehicles: [1, 2, 3].map((seq) => ({ + id: 'demo-line-' + seq, + seq, + vin: '', + status: 'awaiting', + evidences: [], + redeliveryCount: 0, + vehicleModel: '4.5T 燃料电池车', + deliverUnit: '交车单位(卖方)', + receiveUnit: '接车单位(买方)', + handoverDate: '2026-06-16', + })), + createdAt: VPField.nowStr(), + updatedAt: VPField.nowStr(), + }; + const list = VPField.getTasks(); + list.unshift(demo); + localStorage.setItem(VPField.KEYS.inspections, JSON.stringify(list)); + localStorage.setItem(VPField.KEYS.activeId, demo.id); + return demo; + } + + function renderList() { + const task = ensureDemoTask(); + const lines = task.vehicles + .map( + (l) => + `
          +
          #${l.seq} ${statusBadge(l.status)}
          +
          车架号 ${l.vin || '未登记'} · 里程 ${l.odometerKm != null ? l.odometerKm + ' km' : '—'} + ${l.delivererSigned && l.receiverSigned ? ' · 已双签' : ''}
          +
          `, + ) + .join(''); + app.innerHTML = ` +
          +

          ${task.code}

          +
          附件4 · ${VPField.signoffName(task)}
          +
          ${task.contractCode} · ${task.vehicleModel}
          +
          ${task.inspectLocation} · ${task.inspectDate}
          +
          明细 ${task.vehicles.length}/${task.expectedQty}(可追加到车)
          +
          +
          +
          车辆明细
          + ${lines} +
          + + +
          +

          图例:√正常 · N缺少 · ×损坏 · O变形。通过须全项√且交/接车双签,并上传左前45°+右后45°照片。

          +
          `; + app.querySelectorAll('.line').forEach((el) => { + el.addEventListener('click', () => { + view = { name: 'edit', lineId: el.getAttribute('data-id') }; + render(); + }); + }); + document.getElementById('btn-stock').onclick = () => { + const n = VPField.stockPassed(VPField.getActiveTask()); + toast(n ? `已入库 ${n} 台` : '没有待入库的签收车辆'); + render(); + }; + document.getElementById('btn-append').onclick = () => { + try { + VPField.appendLine(VPField.getActiveTask()); + toast('已追加到车登记位'); + render(); + } catch (e) { + toast(e.message || String(e)); + } + }; + } + + function collectMarks() { + const marks = {}; + document.querySelectorAll('[data-item]').forEach((row) => { + const item = row.getAttribute('data-item'); + const checked = row.querySelector('input[type=radio]:checked'); + if (checked) marks[item] = checked.value; + }); + return marks; + } + + function collectPayload(base) { + return { + ...base, + vin: document.getElementById('vin').value, + vehicleModel: document.getElementById('model').value, + deliverUnit: document.getElementById('deliver').value, + receiveUnit: document.getElementById('receive').value, + handoverDate: document.getElementById('hdate').value, + odometerKm: document.getElementById('odo').value, + motorNo: document.getElementById('motor').value, + remark: document.getElementById('remark').value, + delivererSigned: document.getElementById('sign-d').checked, + receiverSigned: document.getElementById('sign-r').checked, + handoverMarks: collectMarks(), + }; + } + + function renderEdit() { + const task = ensureDemoTask(); + const line = task.vehicles.find((l) => l.id === view.lineId); + if (!line) { + view = { name: 'list' }; + return render(); + } + + if (line.status === 'failed') { + app.innerHTML = ` +
          + +

          第 ${line.seq} 台 · 已拒收

          +

          ${line.failReason || ''}

          +
          +
          `; + document.getElementById('back').onclick = () => { + view = { name: 'list' }; + render(); + }; + document.getElementById('rede').onclick = () => { + VPField.requestRedelivery(task, line.id); + toast('已发起重交'); + view = { name: 'list' }; + render(); + }; + return; + } + if (line.status === 'redelivery') { + app.innerHTML = ` +
          + +

          第 ${line.seq} 台 · 重交中

          +
          +
          `; + document.getElementById('back').onclick = () => { + view = { name: 'list' }; + render(); + }; + document.getElementById('start').onclick = () => { + VPField.startRedelivery(task, line.id); + toast('已进入待登记'); + render(); + }; + return; + } + if (line.status === 'passed' || line.status === 'stocked') { + app.innerHTML = ` +
          + +

          第 ${line.seq} 台

          +

          ${statusBadge(line.status)} · 车架号 ${line.vin}

          +

          里程 ${line.odometerKm != null ? line.odometerKm : '—'} km · 电机号 ${line.motorNo || '—'}

          +
          ${line.esignFileName || ''}
          +
          `; + document.getElementById('back').onclick = () => { + view = { name: 'list' }; + render(); + }; + return; + } + + const groupsHtml = VPField.CHECK_GROUPS.map((g) => { + const rows = g.items + .map((item) => { + const radios = VPField.MARKS.map( + (m) => + ``, + ).join(''); + return `
          +
          ${item}
          +
          ${radios}
          +
          `; + }) + .join(''); + return `
          ${g.title}
          ${rows}
          `; + }).join(''); + + app.innerHTML = ` +
          + +

          附件4 · 第 ${line.seq} 台

          +
          ${VPField.signoffName(task)}
          + + + + + + + + + + + +
          + + + + + +
          交车及接车时待检验项目
          +
          点选 √ / N / × / O(演示可「一键全 √」)
          +
          + ${groupsHtml} + + + + +
          + + +
          +
            + +
            双方确认
            + + + +
            + + +
            +
            `; + + const evs = []; + document.getElementById('back').onclick = () => { + view = { name: 'list' }; + render(); + }; + document.getElementById('ocr').onclick = () => { + document.getElementById('vin').value = VPField.mockOcr(ocrIdx++); + toast('车架号 OCR 回填(模拟)'); + }; + document.getElementById('all-ok').onclick = () => { + document.querySelectorAll('[data-item]').forEach((row) => { + const ok = row.querySelector('input[value=ok]'); + if (ok) ok.checked = true; + }); + toast('已全部标记 √'); + }; + document.getElementById('ev-l').onclick = () => { + evs.push({ uid: 'ev-l-' + Date.now(), name: '左前45度.jpg', kind: 'photo', photoAngle: 'left45' }); + document.getElementById('ev-list').innerHTML = evs.map((e) => `
          • ${e.name}
          • `).join(''); + }; + document.getElementById('ev-r').onclick = () => { + evs.push({ uid: 'ev-r-' + Date.now(), name: '右后45度.jpg', kind: 'photo', photoAngle: 'right45' }); + document.getElementById('ev-list').innerHTML = evs.map((e) => `
          • ${e.name}
          • `).join(''); + }; + document.getElementById('pass').onclick = () => { + try { + const payload = collectPayload({ seq: line.seq, evidences: evs }); + const t = VPField.passLine(task, line.id, payload); + document.getElementById('esign-box').hidden = false; + document.getElementById('esign-box').textContent = + 'E签宝已生成:' + (t.vehicles.find((v) => v.id === line.id) || {}).esignFileName; + toast('交接验收通过,双签完成(模拟)'); + setTimeout(() => { + view = { name: 'list' }; + render(); + }, 800); + } catch (e) { + toast(e.message || String(e)); + } + }; + document.getElementById('failbtn').onclick = () => { + try { + const payload = collectPayload({ + seq: line.seq, + evidences: evs, + failReason: document.getElementById('remark').value || '交接验收不合格', + }); + VPField.failLine(task, line.id, payload); + toast('已拒收并留痕,可发起重交'); + view = { name: 'list' }; + render(); + } catch (e) { + toast(e.message || String(e)); + } + }; + } + + function render() { + if (view.name === 'edit') renderEdit(); + else renderList(); + } + + render(); +})(); diff --git a/src/prototypes/vehicle-inspection-web-legacy/field-core.js b/src/prototypes/vehicle-inspection-web-legacy/field-core.js new file mode 100644 index 0000000..f8e0939 --- /dev/null +++ b/src/prototypes/vehicle-inspection-web-legacy/field-core.js @@ -0,0 +1,364 @@ +/** + * 外勤验车 · 对齐《附件4:车辆交接验收表》 + */ +(function (global) { + const KEYS = { + inspections: 'oneos.vp.inspections', + stocked: 'oneos.vp.stockedVehicles', + activeId: 'oneos.vp.activeInspectionId', + }; + + const VIN_REGEX = /^[A-HJ-NPR-Z0-9]{17}$/; + const OCR_SAMPLES = ['LB9A32A28P0512347', 'LB9A32A20R0512238', 'LZZ1CLWB0PA123456']; + + const CHECK_GROUPS = [ + { + title: '外观与标识', + items: [ + '整车外观状况(与公告目录样车相同)', + '漆面状况', + '喇叭状况', + '车辆照片(左前45°+右后45°)', + '汽车标识LOGO', + '车辆底盘状况', + '整车配置表', + ], + }, + { + title: '动力与续航', + items: [ + '冷却风扇转动状况', + '供氢量/续航电量(≥95%)', + '钥匙(2把)', + '动力舱状况', + '车辆启动状况', + '档位变换(前进档、倒档)', + ], + }, + { + title: '驾驶室与电器', + items: [ + '车辆内饰状况', + '灯光状况', + '前后灯总成状况', + '风挡玻璃状况', + '仪表盘功能状况', + '雨刮器状况', + '座椅状况', + '安全带状况', + '左右后视镜状况', + '车内反光镜状况', + '空调状况', + '刹车系统状况', + '制动器助力性能状况', + '方向盘助力性能状况', + ], + }, + { + title: '随车工具与备件', + items: [ + '灭火器(1个)', + '备胎(1只)', + '点烟器(1个)', + '反光衣', + '千斤顶', + '轮胎撬棍', + '车辆反光贴', + '三角警示牌', + '钳子/轮胎扳手', + '备胎扬撬', + '轴头保养扳手', + '挡车器', + '胎压检测值', + '标准量去离子水', + '标准量冷却液', + ], + }, + { + title: '文件资料', + items: ['用户操作手册', '质量保修手册', '保修IC卡'], + }, + { + title: '车身结构', + items: [ + '四轮轮胎状况', + '挡泥板状况', + '侧防护围杠/后部护围杠', + '左右中后门状况', + '货箱箱体状况', + ], + }, + ]; + + const CHECK_ITEMS = CHECK_GROUPS.flatMap((g) => g.items); + const SIGNOFF_DOC = '车辆交接验收表'; + const MARKS = [ + { value: 'ok', label: '√' }, + { value: 'missing', label: 'N' }, + { value: 'damage', label: '×' }, + { value: 'deform', label: 'O' }, + ]; + + function read(key, fallback) { + try { + const raw = localStorage.getItem(key); + return raw ? JSON.parse(raw) : fallback; + } catch { + return fallback; + } + } + + function write(key, val) { + localStorage.setItem(key, JSON.stringify(val)); + } + + function nowStr() { + const d = new Date(); + const p = (n) => String(n).padStart(2, '0'); + return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`; + } + + function normalizeVin(raw) { + return String(raw || '') + .trim() + .toUpperCase() + .replace(/\s+/g, ''); + } + + function vinMessage(raw) { + const v = normalizeVin(raw); + if (!v) return '请填写车架号'; + if (v.length !== 17) return '车架号须为 17 位'; + if (!VIN_REGEX.test(v)) return '车架号格式不正确(不可含 I/O/Q)'; + return null; + } + + function getTasks() { + return read(KEYS.inspections, []); + } + + function saveTasks(list) { + write(KEYS.inspections, list); + } + + function getActiveTask() { + const id = localStorage.getItem(KEYS.activeId); + const tasks = getTasks(); + if (id) { + const found = tasks.find((t) => t.id === id); + if (found) return found; + } + return tasks[0] || null; + } + + function updateTask(task) { + const list = getTasks(); + const next = list.some((t) => t.id === task.id) + ? list.map((t) => (t.id === task.id ? task : t)) + : [task, ...list]; + saveTasks(next); + return task; + } + + function mockOcr(i) { + return OCR_SAMPLES[i % OCR_SAMPLES.length]; + } + + function checkItemsOf(task) { + return task.acceptanceCheckItems && task.acceptanceCheckItems.length + ? task.acceptanceCheckItems + : CHECK_ITEMS; + } + + function signoffName(task) { + return task.signoffDocName || SIGNOFF_DOC; + } + + function allMarksOk(marks, items) { + if (!marks) return false; + return items.every((it) => marks[it] === 'ok' || marks[it] === 'na'); + } + + function passLine(task, lineId, payload) { + const vinErr = vinMessage(payload.vin); + if (vinErr) throw new Error(vinErr); + const items = checkItemsOf(task); + if (!allMarksOk(payload.handoverMarks, items)) { + throw new Error('交接验收表存在异常/未检项目,不能签收通过(请全部标 √ 或改为拒收)'); + } + if (!payload.delivererSigned || !payload.receiverSigned) { + throw new Error('须完成交车方与接车方双签'); + } + if (!payload.evidences || payload.evidences.length < 2) { + throw new Error('须上传左前45°与右后45°车辆照片'); + } + const vin = normalizeVin(payload.vin); + const doc = signoffName(task); + const esign = `${task.code}-${String(payload.seq || 1).padStart(2, '0')}-${doc}-电子签章.pdf`; + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { + ...l, + vin, + vehicleModel: payload.vehicleModel || l.vehicleModel || task.vehicleModel, + deliverUnit: payload.deliverUnit || l.deliverUnit || task.defaultDeliverUnit, + receiveUnit: payload.receiveUnit || l.receiveUnit || task.defaultReceiveUnit, + handoverDate: payload.handoverDate || l.handoverDate, + odometerKm: payload.odometerKm != null ? Number(payload.odometerKm) : l.odometerKm, + motorNo: payload.motorNo || l.motorNo, + remark: payload.remark || '', + status: 'passed', + handoverMarks: payload.handoverMarks, + checkItems: items.filter((it) => payload.handoverMarks[it] === 'ok'), + evidences: payload.evidences, + delivererSigned: true, + receiverSigned: true, + esignFileName: esign, + signedAt: nowStr(), + failReason: undefined, + }; + }); + return updateTask({ + ...task, + vehicles, + status: 'in_progress', + updatedAt: nowStr(), + }); + } + + function failLine(task, lineId, payload) { + if (!payload.failReason || !String(payload.failReason).trim()) { + throw new Error('拒收须填写备注/原因'); + } + if (!payload.evidences || !payload.evidences.length) { + throw new Error('拒收须上传现场证据'); + } + const vin = payload.vin ? normalizeVin(payload.vin) : ''; + if (vin && vinMessage(vin)) throw new Error(vinMessage(vin)); + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { + ...l, + vin: vin || l.vin, + vehicleModel: payload.vehicleModel || l.vehicleModel, + deliverUnit: payload.deliverUnit || l.deliverUnit, + receiveUnit: payload.receiveUnit || l.receiveUnit, + handoverDate: payload.handoverDate || l.handoverDate, + odometerKm: payload.odometerKm != null ? Number(payload.odometerKm) : l.odometerKm, + motorNo: payload.motorNo || l.motorNo, + remark: payload.remark || payload.failReason, + status: 'failed', + failReason: String(payload.failReason).trim(), + handoverMarks: payload.handoverMarks || {}, + evidences: payload.evidences, + lastRejectedAt: nowStr(), + }; + }); + return updateTask({ + ...task, + vehicles, + status: 'in_progress', + updatedAt: nowStr(), + }); + } + + function requestRedelivery(task, lineId) { + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { + ...l, + status: 'redelivery', + redeliveryCount: (l.redeliveryCount || 0) + 1, + vin: '', + esignFileName: undefined, + signedAt: undefined, + evidences: [], + checkItems: undefined, + handoverMarks: undefined, + delivererSigned: false, + receiverSigned: false, + }; + }); + return updateTask({ ...task, vehicles, updatedAt: nowStr() }); + } + + function startRedelivery(task, lineId) { + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { ...l, status: 'awaiting' }; + }); + return updateTask({ ...task, vehicles, updatedAt: nowStr() }); + } + + function appendLine(task) { + if (task.vehicles.length >= task.expectedQty) { + throw new Error('已达到合同应验台数'); + } + const seq = task.vehicles.length + 1; + const line = { + id: `${task.id}-line-${seq}`, + seq, + vin: '', + status: 'awaiting', + evidences: [], + redeliveryCount: 0, + vehicleModel: task.vehicleModel, + deliverUnit: task.defaultDeliverUnit, + receiveUnit: task.defaultReceiveUnit, + handoverDate: task.inspectDate, + }; + return updateTask({ + ...task, + vehicles: [...task.vehicles, line], + status: 'in_progress', + updatedAt: nowStr(), + }); + } + + function stockPassed(task) { + const stocked = read(KEYS.stocked, []); + let n = 0; + const vehicles = task.vehicles.map((l) => { + if (l.status !== 'passed' || !l.vin) return l; + const id = `sv-${Date.now()}-${l.seq}`; + stocked.unshift({ + id, + vin: l.vin, + brandModel: l.vehicleModel || task.vehicleModel, + purchaseContractId: task.contractId, + purchaseContractCode: task.contractCode, + purchaseDate: nowStr().slice(0, 10), + status: '未备车', + sourceInspectionTaskId: task.id, + sourceLineId: l.id, + }); + n += 1; + return { ...l, status: 'stocked', stockedAt: nowStr(), stockedVehicleId: id }; + }); + write(KEYS.stocked, stocked); + updateTask({ ...task, vehicles, updatedAt: nowStr() }); + return n; + } + + global.VPField = { + KEYS, + CHECK_ITEMS, + CHECK_GROUPS, + MARKS, + SIGNOFF_DOC, + getActiveTask, + getTasks, + passLine, + failLine, + requestRedelivery, + startRedelivery, + appendLine, + stockPassed, + mockOcr, + vinMessage, + normalizeVin, + checkItemsOf, + signoffName, + nowStr, + }; +})(window); diff --git a/src/prototypes/vehicle-inspection-web-legacy/field-theme.css b/src/prototypes/vehicle-inspection-web-legacy/field-theme.css new file mode 100644 index 0000000..666979f --- /dev/null +++ b/src/prototypes/vehicle-inspection-web-legacy/field-theme.css @@ -0,0 +1,52 @@ +:root { + --ln-primary: #32a06e; + --ln-primary-hover: #3fb87c; + --ln-ink: #18181b; + --ln-muted: #71717a; + --ln-canvas: #f5f6f6; + --ln-surface: #ffffff; + --ln-hairline: #e5e7eb; + --ln-error: #e5484d; + --ln-success: #27a644; + --ln-radius-control: 8px; + --ln-radius-card: 12px; + /* aliases used by existing HTML */ + --bg: var(--ln-canvas); + --card: var(--ln-surface); + --text: var(--ln-ink); + --muted: var(--ln-muted); + --primary: var(--ln-primary); + --danger: var(--ln-error); + --border: var(--ln-hairline); + /* semantic tints */ + --ln-primary-soft: #e8f5ef; + --ln-success-soft: #e6f6ea; + --ln-error-soft: #fdeeee; + --ln-primary-border: rgba(50, 160, 110, 0.45); +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation: none !important; + transition: none !important; + } +} + +button { + min-height: 44px; +} + +button:focus-visible, +input:focus-visible, +textarea:focus-visible, +select:focus-visible { + outline: 2px solid var(--ln-primary); + outline-offset: 1px; +} + +button.primary:hover { + background: var(--ln-primary-hover); + border-color: var(--ln-primary-hover); +} diff --git a/src/prototypes/vehicle-inspection-web-legacy/index.html b/src/prototypes/vehicle-inspection-web-legacy/index.html new file mode 100644 index 0000000..dd1223f --- /dev/null +++ b/src/prototypes/vehicle-inspection-web-legacy/index.html @@ -0,0 +1,114 @@ + + + + + + 验车入库 · 外勤 Web + + + + +
            +
            OneOS 验车 · 外勤 Web
            +
            Web 端
            +
            +
            +
            + + + + diff --git a/src/prototypes/vehicle-inspection-web/app.js b/src/prototypes/vehicle-inspection-web/app.js new file mode 100644 index 0000000..1996790 --- /dev/null +++ b/src/prototypes/vehicle-inspection-web/app.js @@ -0,0 +1,333 @@ +(function () { + const app = document.getElementById('app'); + const toastEl = document.getElementById('toast'); + let view = { name: 'list' }; + let ocrIdx = 0; + + function toast(msg) { + toastEl.textContent = msg; + toastEl.style.display = 'block'; + setTimeout(() => { + toastEl.style.display = 'none'; + }, 2800); + } + + function statusBadge(s) { + if (s === 'stocked') return '已入库'; + if (s === 'passed') return '已签收'; + if (s === 'failed') return '拒收'; + if (s === 'redelivery') return '重交中'; + return '待登记'; + } + + function ensureDemoTask() { + let task = VPField.getActiveTask(); + if (task) { + if (!task.acceptanceCheckItems) task.acceptanceCheckItems = VPField.CHECK_ITEMS.slice(); + if (!task.signoffDocName) task.signoffDocName = VPField.SIGNOFF_DOC; + return task; + } + const demo = { + id: 'insp-demo-field', + code: 'YC-DEMO-0020', + contractId: 'pc-demo-20', + contractCode: 'HT-DEMO-20', + vehicleModel: '4.5T 燃料电池车', + expectedQty: 20, + inspectLocation: '指定交付场地', + inspectDate: '2026-06-16', + status: 'pending', + defaultDeliverUnit: '交车单位(卖方)', + defaultReceiveUnit: '接车单位(买方)', + acceptanceCheckItems: VPField.CHECK_ITEMS.slice(), + signoffDocName: VPField.SIGNOFF_DOC, + vehicles: [1, 2, 3].map((seq) => ({ + id: 'demo-line-' + seq, + seq, + vin: '', + status: 'awaiting', + evidences: [], + redeliveryCount: 0, + vehicleModel: '4.5T 燃料电池车', + deliverUnit: '交车单位(卖方)', + receiveUnit: '接车单位(买方)', + handoverDate: '2026-06-16', + })), + createdAt: VPField.nowStr(), + updatedAt: VPField.nowStr(), + }; + const list = VPField.getTasks(); + list.unshift(demo); + localStorage.setItem(VPField.KEYS.inspections, JSON.stringify(list)); + localStorage.setItem(VPField.KEYS.activeId, demo.id); + return demo; + } + + function renderList() { + const task = ensureDemoTask(); + const lines = task.vehicles + .map( + (l) => + `
            +
            #${l.seq} ${statusBadge(l.status)}
            +
            车架号 ${l.vin || '未登记'} · 里程 ${l.odometerKm != null ? l.odometerKm + ' km' : '—'} + ${l.delivererSigned && l.receiverSigned ? ' · 已双签' : ''}
            +
            `, + ) + .join(''); + app.innerHTML = ` +
            +

            ${task.code}

            +
            附件4 · ${VPField.signoffName(task)}
            +
            ${task.contractCode} · ${task.vehicleModel}
            +
            ${task.inspectLocation} · ${task.inspectDate}
            +
            明细 ${task.vehicles.length}/${task.expectedQty}(可追加到车)
            +
            +
            +
            车辆明细
            + ${lines} +
            + + +
            +

            图例:√正常 · N缺少 · ×损坏 · O变形。通过须全项√且交/接车双签,并上传左前45°+右后45°照片。

            +
            `; + app.querySelectorAll('.line').forEach((el) => { + el.addEventListener('click', () => { + view = { name: 'edit', lineId: el.getAttribute('data-id') }; + render(); + }); + }); + document.getElementById('btn-stock').onclick = () => { + const n = VPField.stockPassed(VPField.getActiveTask()); + toast(n ? `已入库 ${n} 台` : '没有待入库的签收车辆'); + render(); + }; + document.getElementById('btn-append').onclick = () => { + try { + VPField.appendLine(VPField.getActiveTask()); + toast('已追加到车登记位'); + render(); + } catch (e) { + toast(e.message || String(e)); + } + }; + } + + function collectMarks() { + const marks = {}; + document.querySelectorAll('[data-item]').forEach((row) => { + const item = row.getAttribute('data-item'); + const checked = row.querySelector('input[type=radio]:checked'); + if (checked) marks[item] = checked.value; + }); + return marks; + } + + function collectPayload(base) { + return { + ...base, + vin: document.getElementById('vin').value, + vehicleModel: document.getElementById('model').value, + deliverUnit: document.getElementById('deliver').value, + receiveUnit: document.getElementById('receive').value, + handoverDate: document.getElementById('hdate').value, + odometerKm: document.getElementById('odo').value, + motorNo: document.getElementById('motor').value, + remark: document.getElementById('remark').value, + delivererSigned: document.getElementById('sign-d').checked, + receiverSigned: document.getElementById('sign-r').checked, + handoverMarks: collectMarks(), + }; + } + + function renderEdit() { + const task = ensureDemoTask(); + const line = task.vehicles.find((l) => l.id === view.lineId); + if (!line) { + view = { name: 'list' }; + return render(); + } + + if (line.status === 'failed') { + app.innerHTML = ` +
            + +

            第 ${line.seq} 台 · 已拒收

            +

            ${line.failReason || ''}

            +
            +
            `; + document.getElementById('back').onclick = () => { + view = { name: 'list' }; + render(); + }; + document.getElementById('rede').onclick = () => { + VPField.requestRedelivery(task, line.id); + toast('已发起重交'); + view = { name: 'list' }; + render(); + }; + return; + } + if (line.status === 'redelivery') { + app.innerHTML = ` +
            + +

            第 ${line.seq} 台 · 重交中

            +
            +
            `; + document.getElementById('back').onclick = () => { + view = { name: 'list' }; + render(); + }; + document.getElementById('start').onclick = () => { + VPField.startRedelivery(task, line.id); + toast('已进入待登记'); + render(); + }; + return; + } + if (line.status === 'passed' || line.status === 'stocked') { + app.innerHTML = ` +
            + +

            第 ${line.seq} 台

            +

            ${statusBadge(line.status)} · 车架号 ${line.vin}

            +

            里程 ${line.odometerKm != null ? line.odometerKm : '—'} km · 电机号 ${line.motorNo || '—'}

            +
            ${line.esignFileName || ''}
            +
            `; + document.getElementById('back').onclick = () => { + view = { name: 'list' }; + render(); + }; + return; + } + + const groupsHtml = VPField.CHECK_GROUPS.map((g) => { + const rows = g.items + .map((item) => { + const radios = VPField.MARKS.map( + (m) => + ``, + ).join(''); + return `
            +
            ${item}
            +
            ${radios}
            +
            `; + }) + .join(''); + return `
            ${g.title}
            ${rows}
            `; + }).join(''); + + app.innerHTML = ` +
            + +

            附件4 · 第 ${line.seq} 台

            +
            ${VPField.signoffName(task)}
            + + + + + + + + + + + +
            + + + + + +
            交车及接车时待检验项目
            +
            点选 √ / N / × / O(演示可「一键全 √」)
            +
            + ${groupsHtml} + + + + +
            + + +
            +
              + +
              双方确认
              + + + +
              + + +
              +
              `; + + const evs = []; + document.getElementById('back').onclick = () => { + view = { name: 'list' }; + render(); + }; + document.getElementById('ocr').onclick = () => { + document.getElementById('vin').value = VPField.mockOcr(ocrIdx++); + toast('车架号 OCR 回填(模拟)'); + }; + document.getElementById('all-ok').onclick = () => { + document.querySelectorAll('[data-item]').forEach((row) => { + const ok = row.querySelector('input[value=ok]'); + if (ok) ok.checked = true; + }); + toast('已全部标记 √'); + }; + document.getElementById('ev-l').onclick = () => { + evs.push({ uid: 'ev-l-' + Date.now(), name: '左前45度.jpg', kind: 'photo', photoAngle: 'left45' }); + document.getElementById('ev-list').innerHTML = evs.map((e) => `
            • ${e.name}
            • `).join(''); + }; + document.getElementById('ev-r').onclick = () => { + evs.push({ uid: 'ev-r-' + Date.now(), name: '右后45度.jpg', kind: 'photo', photoAngle: 'right45' }); + document.getElementById('ev-list').innerHTML = evs.map((e) => `
            • ${e.name}
            • `).join(''); + }; + document.getElementById('pass').onclick = () => { + try { + const payload = collectPayload({ seq: line.seq, evidences: evs }); + const t = VPField.passLine(task, line.id, payload); + document.getElementById('esign-box').hidden = false; + document.getElementById('esign-box').textContent = + 'E签宝已生成:' + (t.vehicles.find((v) => v.id === line.id) || {}).esignFileName; + toast('交接验收通过,双签完成(模拟)'); + setTimeout(() => { + view = { name: 'list' }; + render(); + }, 800); + } catch (e) { + toast(e.message || String(e)); + } + }; + document.getElementById('failbtn').onclick = () => { + try { + const payload = collectPayload({ + seq: line.seq, + evidences: evs, + failReason: document.getElementById('remark').value || '交接验收不合格', + }); + VPField.failLine(task, line.id, payload); + toast('已拒收并留痕,可发起重交'); + view = { name: 'list' }; + render(); + } catch (e) { + toast(e.message || String(e)); + } + }; + } + + function render() { + if (view.name === 'edit') renderEdit(); + else renderList(); + } + + render(); +})(); diff --git a/src/prototypes/vehicle-inspection-web/field-core.js b/src/prototypes/vehicle-inspection-web/field-core.js new file mode 100644 index 0000000..f8e0939 --- /dev/null +++ b/src/prototypes/vehicle-inspection-web/field-core.js @@ -0,0 +1,364 @@ +/** + * 外勤验车 · 对齐《附件4:车辆交接验收表》 + */ +(function (global) { + const KEYS = { + inspections: 'oneos.vp.inspections', + stocked: 'oneos.vp.stockedVehicles', + activeId: 'oneos.vp.activeInspectionId', + }; + + const VIN_REGEX = /^[A-HJ-NPR-Z0-9]{17}$/; + const OCR_SAMPLES = ['LB9A32A28P0512347', 'LB9A32A20R0512238', 'LZZ1CLWB0PA123456']; + + const CHECK_GROUPS = [ + { + title: '外观与标识', + items: [ + '整车外观状况(与公告目录样车相同)', + '漆面状况', + '喇叭状况', + '车辆照片(左前45°+右后45°)', + '汽车标识LOGO', + '车辆底盘状况', + '整车配置表', + ], + }, + { + title: '动力与续航', + items: [ + '冷却风扇转动状况', + '供氢量/续航电量(≥95%)', + '钥匙(2把)', + '动力舱状况', + '车辆启动状况', + '档位变换(前进档、倒档)', + ], + }, + { + title: '驾驶室与电器', + items: [ + '车辆内饰状况', + '灯光状况', + '前后灯总成状况', + '风挡玻璃状况', + '仪表盘功能状况', + '雨刮器状况', + '座椅状况', + '安全带状况', + '左右后视镜状况', + '车内反光镜状况', + '空调状况', + '刹车系统状况', + '制动器助力性能状况', + '方向盘助力性能状况', + ], + }, + { + title: '随车工具与备件', + items: [ + '灭火器(1个)', + '备胎(1只)', + '点烟器(1个)', + '反光衣', + '千斤顶', + '轮胎撬棍', + '车辆反光贴', + '三角警示牌', + '钳子/轮胎扳手', + '备胎扬撬', + '轴头保养扳手', + '挡车器', + '胎压检测值', + '标准量去离子水', + '标准量冷却液', + ], + }, + { + title: '文件资料', + items: ['用户操作手册', '质量保修手册', '保修IC卡'], + }, + { + title: '车身结构', + items: [ + '四轮轮胎状况', + '挡泥板状况', + '侧防护围杠/后部护围杠', + '左右中后门状况', + '货箱箱体状况', + ], + }, + ]; + + const CHECK_ITEMS = CHECK_GROUPS.flatMap((g) => g.items); + const SIGNOFF_DOC = '车辆交接验收表'; + const MARKS = [ + { value: 'ok', label: '√' }, + { value: 'missing', label: 'N' }, + { value: 'damage', label: '×' }, + { value: 'deform', label: 'O' }, + ]; + + function read(key, fallback) { + try { + const raw = localStorage.getItem(key); + return raw ? JSON.parse(raw) : fallback; + } catch { + return fallback; + } + } + + function write(key, val) { + localStorage.setItem(key, JSON.stringify(val)); + } + + function nowStr() { + const d = new Date(); + const p = (n) => String(n).padStart(2, '0'); + return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`; + } + + function normalizeVin(raw) { + return String(raw || '') + .trim() + .toUpperCase() + .replace(/\s+/g, ''); + } + + function vinMessage(raw) { + const v = normalizeVin(raw); + if (!v) return '请填写车架号'; + if (v.length !== 17) return '车架号须为 17 位'; + if (!VIN_REGEX.test(v)) return '车架号格式不正确(不可含 I/O/Q)'; + return null; + } + + function getTasks() { + return read(KEYS.inspections, []); + } + + function saveTasks(list) { + write(KEYS.inspections, list); + } + + function getActiveTask() { + const id = localStorage.getItem(KEYS.activeId); + const tasks = getTasks(); + if (id) { + const found = tasks.find((t) => t.id === id); + if (found) return found; + } + return tasks[0] || null; + } + + function updateTask(task) { + const list = getTasks(); + const next = list.some((t) => t.id === task.id) + ? list.map((t) => (t.id === task.id ? task : t)) + : [task, ...list]; + saveTasks(next); + return task; + } + + function mockOcr(i) { + return OCR_SAMPLES[i % OCR_SAMPLES.length]; + } + + function checkItemsOf(task) { + return task.acceptanceCheckItems && task.acceptanceCheckItems.length + ? task.acceptanceCheckItems + : CHECK_ITEMS; + } + + function signoffName(task) { + return task.signoffDocName || SIGNOFF_DOC; + } + + function allMarksOk(marks, items) { + if (!marks) return false; + return items.every((it) => marks[it] === 'ok' || marks[it] === 'na'); + } + + function passLine(task, lineId, payload) { + const vinErr = vinMessage(payload.vin); + if (vinErr) throw new Error(vinErr); + const items = checkItemsOf(task); + if (!allMarksOk(payload.handoverMarks, items)) { + throw new Error('交接验收表存在异常/未检项目,不能签收通过(请全部标 √ 或改为拒收)'); + } + if (!payload.delivererSigned || !payload.receiverSigned) { + throw new Error('须完成交车方与接车方双签'); + } + if (!payload.evidences || payload.evidences.length < 2) { + throw new Error('须上传左前45°与右后45°车辆照片'); + } + const vin = normalizeVin(payload.vin); + const doc = signoffName(task); + const esign = `${task.code}-${String(payload.seq || 1).padStart(2, '0')}-${doc}-电子签章.pdf`; + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { + ...l, + vin, + vehicleModel: payload.vehicleModel || l.vehicleModel || task.vehicleModel, + deliverUnit: payload.deliverUnit || l.deliverUnit || task.defaultDeliverUnit, + receiveUnit: payload.receiveUnit || l.receiveUnit || task.defaultReceiveUnit, + handoverDate: payload.handoverDate || l.handoverDate, + odometerKm: payload.odometerKm != null ? Number(payload.odometerKm) : l.odometerKm, + motorNo: payload.motorNo || l.motorNo, + remark: payload.remark || '', + status: 'passed', + handoverMarks: payload.handoverMarks, + checkItems: items.filter((it) => payload.handoverMarks[it] === 'ok'), + evidences: payload.evidences, + delivererSigned: true, + receiverSigned: true, + esignFileName: esign, + signedAt: nowStr(), + failReason: undefined, + }; + }); + return updateTask({ + ...task, + vehicles, + status: 'in_progress', + updatedAt: nowStr(), + }); + } + + function failLine(task, lineId, payload) { + if (!payload.failReason || !String(payload.failReason).trim()) { + throw new Error('拒收须填写备注/原因'); + } + if (!payload.evidences || !payload.evidences.length) { + throw new Error('拒收须上传现场证据'); + } + const vin = payload.vin ? normalizeVin(payload.vin) : ''; + if (vin && vinMessage(vin)) throw new Error(vinMessage(vin)); + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { + ...l, + vin: vin || l.vin, + vehicleModel: payload.vehicleModel || l.vehicleModel, + deliverUnit: payload.deliverUnit || l.deliverUnit, + receiveUnit: payload.receiveUnit || l.receiveUnit, + handoverDate: payload.handoverDate || l.handoverDate, + odometerKm: payload.odometerKm != null ? Number(payload.odometerKm) : l.odometerKm, + motorNo: payload.motorNo || l.motorNo, + remark: payload.remark || payload.failReason, + status: 'failed', + failReason: String(payload.failReason).trim(), + handoverMarks: payload.handoverMarks || {}, + evidences: payload.evidences, + lastRejectedAt: nowStr(), + }; + }); + return updateTask({ + ...task, + vehicles, + status: 'in_progress', + updatedAt: nowStr(), + }); + } + + function requestRedelivery(task, lineId) { + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { + ...l, + status: 'redelivery', + redeliveryCount: (l.redeliveryCount || 0) + 1, + vin: '', + esignFileName: undefined, + signedAt: undefined, + evidences: [], + checkItems: undefined, + handoverMarks: undefined, + delivererSigned: false, + receiverSigned: false, + }; + }); + return updateTask({ ...task, vehicles, updatedAt: nowStr() }); + } + + function startRedelivery(task, lineId) { + const vehicles = task.vehicles.map((l) => { + if (l.id !== lineId) return l; + return { ...l, status: 'awaiting' }; + }); + return updateTask({ ...task, vehicles, updatedAt: nowStr() }); + } + + function appendLine(task) { + if (task.vehicles.length >= task.expectedQty) { + throw new Error('已达到合同应验台数'); + } + const seq = task.vehicles.length + 1; + const line = { + id: `${task.id}-line-${seq}`, + seq, + vin: '', + status: 'awaiting', + evidences: [], + redeliveryCount: 0, + vehicleModel: task.vehicleModel, + deliverUnit: task.defaultDeliverUnit, + receiveUnit: task.defaultReceiveUnit, + handoverDate: task.inspectDate, + }; + return updateTask({ + ...task, + vehicles: [...task.vehicles, line], + status: 'in_progress', + updatedAt: nowStr(), + }); + } + + function stockPassed(task) { + const stocked = read(KEYS.stocked, []); + let n = 0; + const vehicles = task.vehicles.map((l) => { + if (l.status !== 'passed' || !l.vin) return l; + const id = `sv-${Date.now()}-${l.seq}`; + stocked.unshift({ + id, + vin: l.vin, + brandModel: l.vehicleModel || task.vehicleModel, + purchaseContractId: task.contractId, + purchaseContractCode: task.contractCode, + purchaseDate: nowStr().slice(0, 10), + status: '未备车', + sourceInspectionTaskId: task.id, + sourceLineId: l.id, + }); + n += 1; + return { ...l, status: 'stocked', stockedAt: nowStr(), stockedVehicleId: id }; + }); + write(KEYS.stocked, stocked); + updateTask({ ...task, vehicles, updatedAt: nowStr() }); + return n; + } + + global.VPField = { + KEYS, + CHECK_ITEMS, + CHECK_GROUPS, + MARKS, + SIGNOFF_DOC, + getActiveTask, + getTasks, + passLine, + failLine, + requestRedelivery, + startRedelivery, + appendLine, + stockPassed, + mockOcr, + vinMessage, + normalizeVin, + checkItemsOf, + signoffName, + nowStr, + }; +})(window); diff --git a/src/prototypes/vehicle-inspection-web/field-theme.css b/src/prototypes/vehicle-inspection-web/field-theme.css new file mode 100644 index 0000000..666979f --- /dev/null +++ b/src/prototypes/vehicle-inspection-web/field-theme.css @@ -0,0 +1,52 @@ +:root { + --ln-primary: #32a06e; + --ln-primary-hover: #3fb87c; + --ln-ink: #18181b; + --ln-muted: #71717a; + --ln-canvas: #f5f6f6; + --ln-surface: #ffffff; + --ln-hairline: #e5e7eb; + --ln-error: #e5484d; + --ln-success: #27a644; + --ln-radius-control: 8px; + --ln-radius-card: 12px; + /* aliases used by existing HTML */ + --bg: var(--ln-canvas); + --card: var(--ln-surface); + --text: var(--ln-ink); + --muted: var(--ln-muted); + --primary: var(--ln-primary); + --danger: var(--ln-error); + --border: var(--ln-hairline); + /* semantic tints */ + --ln-primary-soft: #e8f5ef; + --ln-success-soft: #e6f6ea; + --ln-error-soft: #fdeeee; + --ln-primary-border: rgba(50, 160, 110, 0.45); +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation: none !important; + transition: none !important; + } +} + +button { + min-height: 44px; +} + +button:focus-visible, +input:focus-visible, +textarea:focus-visible, +select:focus-visible { + outline: 2px solid var(--ln-primary); + outline-offset: 1px; +} + +button.primary:hover { + background: var(--ln-primary-hover); + border-color: var(--ln-primary-hover); +} diff --git a/src/prototypes/vehicle-inspection-web/index.html b/src/prototypes/vehicle-inspection-web/index.html new file mode 100644 index 0000000..dd1223f --- /dev/null +++ b/src/prototypes/vehicle-inspection-web/index.html @@ -0,0 +1,114 @@ + + + + + + 验车入库 · 外勤 Web + + + + +
              +
              OneOS 验车 · 外勤 Web
              +
              Web 端
              +
              +
              +
              + + + + diff --git a/src/prototypes/vehicle-inspection/.spec/requirements-prd.md b/src/prototypes/vehicle-inspection/.spec/requirements-prd.md new file mode 100644 index 0000000..7e8c82e --- /dev/null +++ b/src/prototypes/vehicle-inspection/.spec/requirements-prd.md @@ -0,0 +1,57 @@ +# 验车入库 · 产品需求说明 + +| 项 | 内容 | +|---|---| +| 模块名称 | 验车入库 | +| 所属系统 | ONE-OS(PC)+ 外勤 Web/小程序/APP | +| 交互原型 | `/prototypes/vehicle-inspection` | +| 业务条线 | 运维管理条线 · 入库入口 | +| 文档状态 | AutoPRD · 首版 | + +--- + +## 1. 一句话与目标 + +**一句话** +采购合同审批通过后自动生成验车任务;运维在现场登记 VIN、记录通过/不通过与证据,通过后经 E签宝签署验收报告并入库。 + +**本期目标** +1. PC 验车任务列表与详情 +2. 部分通过可结案;拒收留痕并可发起重新交付 +3. 外勤三端对齐《附件4:车辆交接验收表》:基本信息、分组检验项(√/N/×/O)、双签、45°照片、车架号 OCR +4. 入库回写车辆资产(原型 localStorage 桥) +5. 大数量合同按到车追加明细 + +**非目标** +- 真实 OCR / 真实 E签宝 API +- 纸质表扫描自动结构化入库(仅人工/外勤填报) + +--- + +## 2. 用户故事 + +| 角色 | 起点 → 运作 → 闭环 | +|------|-------------------| +| 运维(PC) | 打开验车任务 → 查看应验台数与现场结果 → 通过车辆入库 / 结案 | +| 运维(外勤) | 打开绑定任务 → OCR/手输 VIN → 通过并 E签 或不通过+证据 → 回写 PC | +| 采购 | 从采购合同跳转查看关联验车进度 | + +--- + +## 3. 关键逻辑 + +详见 `.spec/vin-and-closure.md`。 + +--- + +## 4. 验收重点 + +1. 合同通过后任务自动出现 +2. VIN 非法不可通过 +3. 不通过必须原因+证据;通过须检查项+现场照片+E签模拟文件 +4. 部分通过可结案;入库车辆可在车辆资产列表看到(同源浏览器) + +## 5. 假设 + +- 外勤三端暂放仓库根目录,由产品自行移动后再挂导航 +- 与 PC 共用 localStorage,需同域访问 diff --git a/src/prototypes/vehicle-inspection/.spec/vin-and-closure.md b/src/prototypes/vehicle-inspection/.spec/vin-and-closure.md new file mode 100644 index 0000000..06a21ed --- /dev/null +++ b/src/prototypes/vehicle-inspection/.spec/vin-and-closure.md @@ -0,0 +1,41 @@ +# VIN / 车辆交接验收表 / 拒收重交 + +对齐样例:`20台车.pdf`(附件4:车辆交接验收表,20 台 × 每台约 4 页)。 + +## 主单据 + +现场验车主单据为 **《车辆交接验收表》**(附件4)。 +合同商务侧《发送签收单》仍可在采购合同语境使用;入库闭环以交接验收表双签为准。 + +## 基本信息字段 + +| 字段 | 说明 | +|------|------| +| 车辆交接日期 | 现场填写 | +| 交车单位 / 接车单位 | 默认可带出合同卖方/买方 | +| 车辆型号 | 默认可带出合同车型 | +| 车架号 | 17 位,OCR 模拟回填 + 正则校验 | +| 交车时仪表里程(公里) | 数字 | +| 发动机号/电机号 | 文本 | + +## 检验项目与图例 + +分组:外观与标识、动力与续航、驾驶室与电器、随车工具与备件、文件资料、车身结构。 + +标记:`√` 正常 · `N` 缺少 · `×` 损坏 · `O` 变形。 + +**通过条件**:全部项目为 √(或不适用)、交车方+接车方双签、左前45°+右后45°照片齐套,并生成交接验收表电子签章文件(原型模拟)。 + +## 拒收与重交 + +拒收须备注原因 + 证据 → 可发起重新交付 → 到车后重验。 + +## 大数量 + +应验台数=合同数量;明细按到车追加(如 20 台样例不一次铺满)。 + +## 代码 + +- `acceptance.ts`(分组检查项) +- 外勤 `field-core.js` / `app.js` +- PC `VehicleInspectionApp.tsx` diff --git a/src/prototypes/vehicle-inspection/DESIGN.md b/src/prototypes/vehicle-inspection/DESIGN.md new file mode 100644 index 0000000..8bd3302 --- /dev/null +++ b/src/prototypes/vehicle-inspection/DESIGN.md @@ -0,0 +1,6 @@ +# 验车入库 · 设计基底 + +- **主题:** Linear 产品浅色(`src/themes/linear` 落地 via `vehicle-management/style.css`) +- **Token:** `--ln-*`(主色 `#32a06e`)、`--vm-*` +- **列表壳:** `vm-shared` — `vm-page`、筛选卡、KPI、`vm-table-section`、`OperationActions`、`TablePagination` +- **样式入口:** `../vehicle-management/style.css` + `./styles/index.css` diff --git a/src/prototypes/vehicle-inspection/VehicleInspectionApp.tsx b/src/prototypes/vehicle-inspection/VehicleInspectionApp.tsx new file mode 100644 index 0000000..ba4ceac --- /dev/null +++ b/src/prototypes/vehicle-inspection/VehicleInspectionApp.tsx @@ -0,0 +1,570 @@ +/** + * @name 验车入库 + */ +import '../vehicle-management/style.css'; +import './styles/index.css'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { + Button, + ConfigProvider, + Descriptions, + Input, + Modal, + Select, + Space, + Table, + Tag, + message, +} from 'antd'; +import type { ColumnsType } from 'antd/es/table'; +import { ArrowLeft } from 'lucide-react'; +import { + type AnnotationSourceDocument, + type AnnotationViewerOptions, +} from '@axhub/annotation'; +import { OperationActions } from '../../common/OperationActions'; +import { PrototypeAnnotationHost } from '../../common/prototype-annotation-host'; +import { TablePagination } from '../../common/TablePagination'; +import annotationSourceDocument from './annotation-source.json'; +import { + type InspectionTask, + type InspectionVehicleLine, + type InspectionTaskStatus, + buildSeedInspections, + loadInspections, + saveInspections, + loadStocked, + saveStocked, + stockVehicleFromLine, + formatNow, + appendInspectionVehicleLine, + requestRedelivery, + startRedeliveryInspection, + DEFAULT_ACCEPTANCE_CHECK_ITEMS, + DEFAULT_SIGNOFF_DOC_NAME, +} from '../../common/vehicle-purchase'; + +const vmTheme = { + token: { + colorPrimary: '#32a06e', + colorLink: '#32a06e', + borderRadius: 8, + fontFamily: + 'Inter, -apple-system, BlinkMacSystemFont, "SF Pro Display", "PingFang SC", "Microsoft YaHei", sans-serif', + }, +}; + +const TASK_STATUS: Record = { + pending: { label: '待验车', color: 'default' }, + in_progress: { label: '验车中', color: 'processing' }, + completed: { label: '已完成', color: 'success' }, +}; + +const LINE_STATUS: Record = { + awaiting: { label: '待登记', color: 'default' }, + passed: { label: '已签收待入库', color: 'blue' }, + failed: { label: '拒收·待重交', color: 'error' }, + redelivery: { label: '重交中', color: 'orange' }, + stocked: { label: '已入库', color: 'success' }, +}; + +function countBy(task: InspectionTask) { + const passed = task.vehicles.filter((v) => v.status === 'passed' || v.status === 'stocked').length; + const failed = task.vehicles.filter( + (v) => v.status === 'failed' || v.status === 'redelivery', + ).length; + const stocked = task.vehicles.filter((v) => v.status === 'stocked').length; + return { passed, failed, stocked }; +} + +export default function VehicleInspectionApp() { + const [tasks, setTasks] = useState([]); + const [keyword, setKeyword] = useState(''); + const [statusFilter, setStatusFilter] = useState('all'); + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [active, setActive] = useState(null); + + useEffect(() => { + setTasks(loadInspections(buildSeedInspections())); + }, []); + + const persist = useCallback((next: InspectionTask[]) => { + setTasks(next); + saveInspections(next); + if (active) { + const refreshed = next.find((t) => t.id === active.id) || null; + setActive(refreshed); + } + }, [active]); + + const filtered = useMemo(() => { + return tasks.filter((t) => { + if (statusFilter !== 'all' && t.status !== statusFilter) return false; + if (keyword.trim()) { + const k = keyword.trim(); + if (!`${t.code}${t.contractCode}${t.vehicleModel}`.includes(k)) return false; + } + return true; + }); + }, [tasks, statusFilter, keyword]); + + const pageRows = useMemo(() => { + const start = (page - 1) * pageSize; + return filtered.slice(start, start + pageSize); + }, [filtered, page, pageSize]); + + const kpi = useMemo( + () => ({ + pending: tasks.filter((t) => t.status === 'pending').length, + inProgress: tasks.filter((t) => t.status === 'in_progress').length, + completed: tasks.filter((t) => t.status === 'completed').length, + }), + [tasks], + ); + + const handleResetFilters = () => { + setKeyword(''); + setStatusFilter('all'); + setPage(1); + }; + + const completeTask = (task: InspectionTask) => { + const { passed, failed } = countBy(task); + if (passed === 0 && failed === 0) { + message.warning('尚无验车结果,无法结案'); + return; + } + Modal.confirm({ + title: '确认结案?', + content: `通过 ${passed} 台,不通过 ${failed} 台。部分通过可结案;不通过车辆保留待处理留痕。`, + onOk: () => { + const next = tasks.map((t) => + t.id === task.id + ? { + ...t, + status: 'completed' as const, + completedAt: formatNow(), + updatedAt: formatNow(), + } + : t, + ); + persist(next); + message.success('任务已结案'); + }, + }); + }; + + const stockPassedLines = (task: InspectionTask) => { + const stocked = loadStocked(); + let added = 0; + const nextVehicles = task.vehicles.map((line) => { + if (line.status !== 'passed' || !line.vin) return line; + const rec = stockVehicleFromLine(task, line); + stocked.unshift(rec); + added += 1; + return { + ...line, + status: 'stocked' as const, + stockedAt: formatNow(), + stockedVehicleId: rec.id, + }; + }); + if (!added) { + message.info('没有待入库的通过车辆'); + return; + } + saveStocked(stocked); + const next = tasks.map((t) => + t.id === task.id + ? { ...t, vehicles: nextVehicles, status: 'in_progress' as const, updatedAt: formatNow() } + : t, + ); + persist(next); + message.success(`已入库 ${added} 台,可在车辆资产核对(原型 localStorage)`); + }; + + const columns: ColumnsType = [ + { + title: '任务编号', + dataIndex: 'code', + width: 140, + render: (v, row) => ( + + ), + }, + { title: '来源合同', dataIndex: 'contractCode', width: 150 }, + { title: '车型', dataIndex: 'vehicleModel', width: 160, ellipsis: true }, + { + title: '应验/签收/拒收', + key: 'counts', + width: 150, + render: (_, row) => { + const c = countBy(row); + return ( + + {row.expectedQty} / {c.passed} / {c.failed} + + ); + }, + }, + { title: '约定地点', dataIndex: 'inspectLocation', width: 160, ellipsis: true }, + { title: '约定日期', dataIndex: 'inspectDate', width: 120 }, + { + title: '状态', + dataIndex: 'status', + width: 100, + render: (s: InspectionTaskStatus) => ( + {TASK_STATUS[s].label} + ), + }, + { + title: '操作', + key: 'op', + width: 100, + fixed: 'right', + render: (_, row) => ( + setActive(row) }} /> + ), + }, + ]; + + const lineColumns: ColumnsType = [ + { title: '序号', dataIndex: 'seq', width: 64, className: 'tabular-nums' }, + { + title: '车架号', + dataIndex: 'vin', + width: 180, + render: (v) => v || 待现场登记, + }, + { + title: '里程(km)', + dataIndex: 'odometerKm', + width: 90, + className: 'tabular-nums', + render: (v) => (v != null ? v : '—'), + }, + { + title: '电机号', + dataIndex: 'motorNo', + width: 120, + ellipsis: true, + render: (v) => v || '—', + }, + { + title: '双签', + key: 'signs', + width: 90, + render: (_, row) => + row.delivererSigned && row.receiverSigned ? ( + 已双签 + ) : ( + 未齐 + ), + }, + { + title: '状态', + dataIndex: 'status', + width: 120, + render: (s: InspectionVehicleLine['status']) => ( + {LINE_STATUS[s].label} + ), + }, + { + title: '拒收原因', + dataIndex: 'failReason', + ellipsis: true, + render: (v) => v || '—', + }, + { + title: '交接验收表', + dataIndex: 'esignFileName', + width: 200, + ellipsis: true, + render: (v) => v || '—', + }, + { + title: '操作', + key: 'lineOp', + width: 160, + render: (_, row) => { + if (!active) return null; + if (row.status === 'failed') { + return ( + + ); + } + if (row.status === 'redelivery') { + return ( + + ); + } + return ; + }, + }, + ]; + + const annotationOptions = useMemo( + () => ({ + showToolbar: true, + showThemeToggle: true, + showColorFilter: true, + emptyWhenNoData: false, + toolbarEdge: 'right', + currentPageId: active ? 'detail' : 'list', + }), + [active], + ); + + return ( + +
              + {!active && ( + <> +
              +
              +

              筛选条件

              +
              +
              +
              + +
              +
              + +
              +
              +
              + ); +} diff --git a/src/prototypes/vehicle-management-legacy/components/DetailAnnualReviewRecordsTab.tsx b/src/prototypes/vehicle-management-legacy/components/DetailAnnualReviewRecordsTab.tsx new file mode 100644 index 0000000..fe69c80 --- /dev/null +++ b/src/prototypes/vehicle-management-legacy/components/DetailAnnualReviewRecordsTab.tsx @@ -0,0 +1,193 @@ +import React, { useMemo, useState } from 'react'; +import { Search } from 'lucide-react'; +import type { VehicleRecord } from '../types'; +import { DateRangeFilterField } from './DateRangeFilterField'; +import { FilterPickerField } from './FilterPickerField'; +import { TablePagination } from './TablePagination'; +import { + applyAnnualReviewRecordFilters, + buildAnnualReviewRecordOptions, + EMPTY_ANNUAL_REVIEW_RECORD_FILTERS, + formatAnnualReviewCell, + formatAnnualReviewMoney, + resolveVehicleAnnualReviewRecords, + type AnnualReviewRecordFilters, + type AnnualReviewRecordRow, +} from '../utils/annualReviewRecords'; + +interface DetailAnnualReviewRecordsTabProps { + record: VehicleRecord; + onOpenAnnualReviewDetail?: (record: VehicleRecord, row: AnnualReviewRecordRow) => void; +} + +const DEFAULT_PAGE_SIZE = 10; + +export function DetailAnnualReviewRecordsTab({ + record, + onOpenAnnualReviewDetail, +}: DetailAnnualReviewRecordsTabProps) { + const allRows = useMemo(() => resolveVehicleAnnualReviewRecords(record), [record]); + const [pendingFilters, setPendingFilters] = useState( + EMPTY_ANNUAL_REVIEW_RECORD_FILTERS, + ); + const [appliedFilters, setAppliedFilters] = useState( + EMPTY_ANNUAL_REVIEW_RECORD_FILTERS, + ); + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); + + const executorOptions = useMemo( + () => buildAnnualReviewRecordOptions(allRows, 'executor'), + [allRows], + ); + + const filtered = useMemo( + () => applyAnnualReviewRecordFilters(allRows, appliedFilters), + [allRows, appliedFilters], + ); + + const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize)); + const currentPage = Math.min(page, totalPages); + const paged = useMemo(() => { + const start = (currentPage - 1) * pageSize; + return filtered.slice(start, start + pageSize); + }, [filtered, currentPage, pageSize]); + + const updatePending = (patch: Partial) => { + setPendingFilters((prev) => ({ ...prev, ...patch })); + }; + + const handleReset = () => { + setPendingFilters(EMPTY_ANNUAL_REVIEW_RECORD_FILTERS); + setAppliedFilters(EMPTY_ANNUAL_REVIEW_RECORD_FILTERS); + setPage(1); + }; + + const handleSearch = () => { + setAppliedFilters(pendingFilters); + setPage(1); + }; + + const handlePageSizeChange = (size: number) => { + setPageSize(size); + setPage(1); + }; + + return ( +
              +
              +
              + + + +
              +
              + + +
              +
              + +
              +
              + + + + + + + + + + + + + + + + {paged.length === 0 ? ( + + + + ) : ( + paged.map((row) => ( + + + + + + + + + + + + )) + )} + +
              检验有效期至检测服务站名称检测费用二保服务站名称二保费用整备服务站名称整备费用办理人完成时间
              暂无数据
              + + + {formatAnnualReviewCell(row.inspectionStation)} + {formatAnnualReviewMoney(row.inspectionCost)} + {formatAnnualReviewCell(row.m2Station)} + {formatAnnualReviewMoney(row.m2Cost)} + {formatAnnualReviewCell(row.zbStation)} + {formatAnnualReviewMoney(row.zbCost)}{row.executor}{row.executeTime}
              +
              +
              + +
              +
              +
              + ); +} diff --git a/src/prototypes/vehicle-management-legacy/components/DetailBasicInfoTab.tsx b/src/prototypes/vehicle-management-legacy/components/DetailBasicInfoTab.tsx new file mode 100644 index 0000000..c682d7b --- /dev/null +++ b/src/prototypes/vehicle-management-legacy/components/DetailBasicInfoTab.tsx @@ -0,0 +1,184 @@ +import React from 'react'; +import type { VehicleRecord } from '../types'; +import { displayUILabel, isShanghaiPlate, resolveProjectDelivery } from '../utils/vehicle'; +import { DetailExpireField } from './DetailExpireField'; +import { DetailInlineEditField } from './DetailInlineEditField'; +import { DetailParamField, DetailParamSection } from './DetailParamLayout'; +import { InspectExpireCell } from './InspectExpireCell'; +import { StatusTag } from './StatusTag'; + +interface DetailBasicInfoTabProps { + record: VehicleRecord; + onOpenLicenseEdit?: (record: VehicleRecord) => void; + onSaveField?: (recordId: string, patch: Partial) => void; + onOpenLeaseContract?: (record: VehicleRecord, contractNo: string) => void; +} + +function isClickableProjectField(value: string): boolean { + return Boolean(value && value !== '无'); +} + +export function DetailBasicInfoTab({ + record, + onOpenLicenseEdit, + onSaveField, + onOpenLeaseContract, +}: DetailBasicInfoTabProps) { + const project = resolveProjectDelivery(record, '无'); + const shanghaiPlate = isShanghaiPlate(record.plateNo); + const handleLicenseEdit = () => onOpenLicenseEdit?.(record); + const openContract = () => { + if (!isClickableProjectField(project.contractNo)) return; + onOpenLeaseContract?.(record, project.contractNo); + }; + + return ( +
              + +
              + onSaveField?.(record.id, { year })} + /> + )} + /> + onSaveField?.(record.id, { purchaseDate })} + /> + )} + /> + } + /> + {shanghaiPlate && ( + } + /> + )} +
              +
              + + +
              + } + /> + + {project.projectName} + + ) : project.projectName} + /> + + {project.contractNo} + + ) : project.contractNo} + /> + +
              +
              + + +
              + } + /> + + )} + /> + + )} + /> +
              +
              + + +
              + + )} + /> + +
              +
              + + +
              + } + /> + + + +
              +
              +
              + ); +} diff --git a/src/prototypes/vehicle-management-legacy/components/DetailExpireField.tsx b/src/prototypes/vehicle-management-legacy/components/DetailExpireField.tsx new file mode 100644 index 0000000..756a85d --- /dev/null +++ b/src/prototypes/vehicle-management-legacy/components/DetailExpireField.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { isEmpty } from '../utils/vehicle'; +import { ExpireDate } from './ExpireDate'; + +interface DetailExpireFieldProps { + date: string; + onClickNotUploaded?: () => void; +} + +/** 详情证照类日期:空为可点「未上传」,有值展示日期与剩余/已过期天数 */ +export function DetailExpireField({ date, onClickNotUploaded }: DetailExpireFieldProps) { + if (isEmpty(date)) { + if (onClickNotUploaded) { + return ( + + ); + } + return 未上传; + } + return ; +} diff --git a/src/prototypes/vehicle-management-legacy/components/DetailFaultRecordsTab.tsx b/src/prototypes/vehicle-management-legacy/components/DetailFaultRecordsTab.tsx new file mode 100644 index 0000000..c8ec03a --- /dev/null +++ b/src/prototypes/vehicle-management-legacy/components/DetailFaultRecordsTab.tsx @@ -0,0 +1,262 @@ +import React, { useMemo, useState } from 'react'; +import { Search } from 'lucide-react'; +import type { VehicleRecord } from '../types'; +import { DateRangeFilterField } from './DateRangeFilterField'; +import { FilterPickerField } from './FilterPickerField'; +import { StatusTag } from './StatusTag'; +import { TablePagination } from './TablePagination'; +import { + FAULT_AI_MATCH_OPTIONS, + FAULT_LEVEL_OPTIONS, + FAULT_RESOLUTION_OPTIONS, + applyFaultRecordFilters, + buildFaultRecordOptions, + EMPTY_FAULT_RECORD_FILTERS, + faultLevelTone, + formatAiMatched, + formatFaultCell, + resolveVehicleFaultRecords, + type FaultRecordFilters, + type FaultRecordRow, +} from '../utils/faultRecords'; + +interface DetailFaultRecordsTabProps { + record: VehicleRecord; + onViewFaultDetail?: (record: VehicleRecord, row: FaultRecordRow) => void; + onEditFaultRecord?: (record: VehicleRecord, row: FaultRecordRow) => void; +} + +const DEFAULT_PAGE_SIZE = 10; + +export function DetailFaultRecordsTab({ + record, + onViewFaultDetail, + onEditFaultRecord, +}: DetailFaultRecordsTabProps) { + const allRows = useMemo(() => resolveVehicleFaultRecords(record), [record]); + const [pendingFilters, setPendingFilters] = useState(EMPTY_FAULT_RECORD_FILTERS); + const [appliedFilters, setAppliedFilters] = useState(EMPTY_FAULT_RECORD_FILTERS); + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); + + const faultNoOptions = useMemo( + () => buildFaultRecordOptions(allRows, 'faultNo'), + [allRows], + ); + const levelOptions = useMemo(() => { + const fromData = buildFaultRecordOptions(allRows, 'faultLevel'); + return fromData.length ? fromData : [...FAULT_LEVEL_OPTIONS]; + }, [allRows]); + const typeOptions = useMemo( + () => buildFaultRecordOptions(allRows, 'faultType'), + [allRows], + ); + + const filtered = useMemo( + () => applyFaultRecordFilters(allRows, appliedFilters), + [allRows, appliedFilters], + ); + + const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize)); + const currentPage = Math.min(page, totalPages); + const paged = useMemo(() => { + const start = (currentPage - 1) * pageSize; + return filtered.slice(start, start + pageSize); + }, [filtered, currentPage, pageSize]); + + const updatePending = (patch: Partial) => { + setPendingFilters((prev) => ({ ...prev, ...patch })); + }; + + const handleReset = () => { + setPendingFilters(EMPTY_FAULT_RECORD_FILTERS); + setAppliedFilters(EMPTY_FAULT_RECORD_FILTERS); + setPage(1); + }; + + const handleSearch = () => { + setAppliedFilters(pendingFilters); + setPage(1); + }; + + const handlePageSizeChange = (size: number) => { + setPageSize(size); + setPage(1); + }; + + return ( +
              +
              +
              + + + + + + +
              +
              + + +
              +
              + +
              +
              + + + + + + + + + + + + + + + + + + + + + {paged.length === 0 ? ( + + + + ) : ( + paged.map((row) => ( + + + + + + + + + + + + + + + + + )) + )} + +
              故障编号解决情况车牌号车辆品牌车辆型号运营公司故障等级故障类型故障描述上报时间解决时间AI匹配状态最后操作人操作
              暂无数据
              + + + {row.resolutionStatus === '已解决' ? ( + + ) : ( + '—' + )} + {row.plateNo}{row.brand}{row.model} + {formatFaultCell(row.operateCompany)} + + + {row.faultType}{row.faultDescription}{row.reportedAt}{formatFaultCell(row.resolvedAt)} + + {formatAiMatched(row.aiMatched)} + + {row.lastOperator} +
              + + +
              +
              +
              +
              + +
              +
              +
              + ); +} diff --git a/src/prototypes/vehicle-management-legacy/components/DetailInlineEditField.tsx b/src/prototypes/vehicle-management-legacy/components/DetailInlineEditField.tsx new file mode 100644 index 0000000..b9f1458 --- /dev/null +++ b/src/prototypes/vehicle-management-legacy/components/DetailInlineEditField.tsx @@ -0,0 +1,99 @@ +import React, { useCallback, useId, useMemo, useRef, useState } from 'react'; +import { Pencil } from 'lucide-react'; +import { SingleDateCalendarPanel } from './SingleDateCalendarPanel'; +import { SingleYearPickerPanel } from './SingleYearPickerPanel'; +import { useDismissOnFocusOutside } from './useDismissOnFocusOutside'; +import { buildYearOptions, formatDateYmd, isEmpty } from '../utils/vehicle'; + +interface DetailInlineEditFieldProps { + value: string; + editType: 'year' | 'date'; + onSave: (value: string) => void; +} + +/** 详情内联编辑:空值显示「未上传」,编辑图标由字段卡片右侧展示 */ +export function DetailInlineEditField({ value, editType, onSave }: DetailInlineEditFieldProps) { + const [editing, setEditing] = useState(false); + const wrapRef = useRef(null); + const labelId = useId(); + const yearOptions = useMemo(() => buildYearOptions(), []); + + const display = editType === 'date' ? formatDateYmd(value) || value : value; + const empty = isEmpty(value); + const editLabel = editType === 'year' ? '选择出厂年份' : '选择采购入库日期'; + + const dismiss = useCallback(() => { + setEditing(false); + }, []); + + useDismissOnFocusOutside(editing, wrapRef, dismiss); + + const startEdit = () => { + setEditing(true); + }; + + const commit = (next: string) => { + setEditing(false); + if (!isEmpty(next) && next !== value) onSave(next); + }; + + const editButton = ( + + ); + + if (editing) { + const draft = editType === 'date' + ? (isEmpty(value) ? '' : formatDateYmd(value)) + : (isEmpty(value) ? '' : value); + + return ( +
              + + {empty ? '未上传' : display} + + {editButton} +
              { + if (event.key === 'Escape') dismiss(); + }} + > + {editType === 'year' ? ( + + ) : ( + + )} +
              +
              + ); + } + + return ( +
              + + {empty ? '未上传' : display} + + {editButton} +
              + ); +} diff --git a/src/prototypes/vehicle-management-legacy/components/DetailInsuranceRecordsTab.tsx b/src/prototypes/vehicle-management-legacy/components/DetailInsuranceRecordsTab.tsx new file mode 100644 index 0000000..0ca2333 --- /dev/null +++ b/src/prototypes/vehicle-management-legacy/components/DetailInsuranceRecordsTab.tsx @@ -0,0 +1,232 @@ +import React, { useMemo, useState } from 'react'; +import type { VehicleRecord } from '../types'; +import { ExpireDate } from './ExpireDate'; +import { StatusTag } from './StatusTag'; +import { + insuranceStatusTone, + purchaseTypeTone, + resolveVehicleInsuranceOverview, + type InsuranceHistoryRow, + type InsuranceTypeCoverage, + type VehicleInsuranceTypeKey, +} from '../utils/vehicleInsuranceRecords'; + +interface DetailInsuranceRecordsTabProps { + record: VehicleRecord; + onOpenInsurancePurchase?: (record: VehicleRecord, insuranceLabel: string) => void; + onPreviewPolicyFile?: (fileName: string) => void; + onDownloadPolicyFile?: (fileName: string) => void; +} + +function InsuranceTypeSummaryCard({ + coverage, + active, + count, + onClick, +}: { + coverage: InsuranceTypeCoverage; + active: boolean; + count: number; + onClick: () => void; +}) { + const isNotPurchased = coverage.status === 'not_purchased'; + const showDateMeta = !isNotPurchased && Boolean(coverage.expireDate); + + return ( + + ); +} + +function PolicyFileCell({ + row, + onPreview, + onDownload, +}: { + row: InsuranceHistoryRow; + onPreview?: (fileName: string) => void; + onDownload?: (fileName: string) => void; +}) { + if (!row.policyFileName) { + return 无附件; + } + + return ( +
              + + · + +
              + ); +} + +export function DetailInsuranceRecordsTab({ + record, + onOpenInsurancePurchase, + onPreviewPolicyFile, + onDownloadPolicyFile, +}: DetailInsuranceRecordsTabProps) { + const overview = useMemo(() => resolveVehicleInsuranceOverview(record), [record]); + const [activeType, setActiveType] = useState('compulsory'); + + const activeCoverage = overview.types.find((item) => item.key === activeType) ?? overview.types[0]; + const allInsuranceNotPurchased = overview.types.every((item) => item.status === 'not_purchased'); + + return ( +
              +
              +
              +
              车辆保险档案
              +
              + +
              + +
              + {overview.types.map((coverage) => ( + setActiveType(coverage.key)} + /> + ))} +
              + +
              +
              +

              {activeCoverage.label}

              +
              + + {activeCoverage.history.length === 0 ? ( +
              +
              {activeCoverage.label}暂无记录
              + {!allInsuranceNotPurchased ? ( + + ) : null} +
              + ) : ( + <> +
              +
              + 保单号 +
              {activeCoverage.policyNo || '—'}
              +
              +
              + 保险公司 +
              {activeCoverage.company || '—'}
              +
              +
              + 到期日期 +
              + {activeCoverage.expireDate + ? + : '—'} +
              +
              +
              + 保费(元) +
              {activeCoverage.premium || '—'}
              +
              +
              + +
              +
              + + + + + + + + + + + + + + + + {activeCoverage.history.map((row) => ( + + + + + + + + + + + + ))} + +
              保险导入时间操作人类型保单号保险状态保险公司付款时间到期日期保单文件
              {row.purchaseTime || '—'}{row.operator || '—'} + + {row.policyNo || '—'} + + + {row.company || '—'} + {row.payTime || '—'} + {row.expireDate ? ( + + ) : '—'} + + +
              +
              +
              + + )} +
              +
              + ); +} diff --git a/src/prototypes/vehicle-management-legacy/components/DetailLeaseRecordsTab.tsx b/src/prototypes/vehicle-management-legacy/components/DetailLeaseRecordsTab.tsx new file mode 100644 index 0000000..9406ccb --- /dev/null +++ b/src/prototypes/vehicle-management-legacy/components/DetailLeaseRecordsTab.tsx @@ -0,0 +1,245 @@ +import React, { useMemo, useState } from 'react'; +import { Search } from 'lucide-react'; +import type { VehicleRecord } from '../types'; +import { FilterPickerField } from './FilterPickerField'; +import { TablePagination } from './TablePagination'; +import { + applyLeaseRecordFilters, + buildLeaseRecordOptions, + EMPTY_LEASE_RECORD_FILTERS, + formatLeaseRecordCell, + isLeaseRecordLinkable, + resolveVehicleLeaseRecords, + type LeaseRecordFilters, + type LeaseRecordRow, +} from '../utils/leaseRecords'; + +interface DetailLeaseRecordsTabProps { + record: VehicleRecord; + onOpenLeaseContract?: (record: VehicleRecord, contractNo: string) => void; + onOpenDeliveryRecord?: (record: VehicleRecord, row: LeaseRecordRow) => void; + onOpenReturnRecord?: (record: VehicleRecord, row: LeaseRecordRow) => void; +} + +const DEFAULT_PAGE_SIZE = 10; + +export function DetailLeaseRecordsTab({ + record, + onOpenLeaseContract, + onOpenDeliveryRecord, + onOpenReturnRecord, +}: DetailLeaseRecordsTabProps) { + const allRows = useMemo(() => resolveVehicleLeaseRecords(record), [record]); + const [pendingFilters, setPendingFilters] = useState(EMPTY_LEASE_RECORD_FILTERS); + const [appliedFilters, setAppliedFilters] = useState(EMPTY_LEASE_RECORD_FILTERS); + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); + + const contractOptions = useMemo( + () => buildLeaseRecordOptions(allRows, 'contractNo'), + [allRows], + ); + const projectOptions = useMemo( + () => buildLeaseRecordOptions(allRows, 'projectName'), + [allRows], + ); + const customerOptions = useMemo( + () => buildLeaseRecordOptions(allRows, 'customerName'), + [allRows], + ); + const businessTypeOptions = useMemo( + () => buildLeaseRecordOptions(allRows, 'businessType'), + [allRows], + ); + + const filtered = useMemo( + () => applyLeaseRecordFilters(allRows, appliedFilters), + [allRows, appliedFilters], + ); + + const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize)); + const currentPage = Math.min(page, totalPages); + const paged = useMemo(() => { + const start = (currentPage - 1) * pageSize; + return filtered.slice(start, start + pageSize); + }, [filtered, currentPage, pageSize]); + + const updatePending = (patch: Partial) => { + setPendingFilters((prev) => ({ ...prev, ...patch })); + }; + + const handleReset = () => { + setPendingFilters(EMPTY_LEASE_RECORD_FILTERS); + setAppliedFilters(EMPTY_LEASE_RECORD_FILTERS); + setPage(1); + }; + + const handleSearch = () => { + setAppliedFilters(pendingFilters); + setPage(1); + }; + + const handlePageSizeChange = (size: number) => { + setPageSize(size); + setPage(1); + }; + + const openContract = (contractNo: string) => { + if (!isLeaseRecordLinkable(contractNo)) return; + onOpenLeaseContract?.(record, contractNo); + }; + + return ( +
              +
              +
              + + + + +
              +
              + + +
              +
              + +
              +
              + + + + + + + + + + + + + + + {paged.length === 0 ? ( + + + + ) : ( + paged.map((row) => ( + + + + + + + + + + + )) + )} + +
              合同编码项目名称客户名称业务类型交车日期交车人还车日期还车人
              暂无租赁记录
              + {isLeaseRecordLinkable(row.contractNo) ? ( + + ) : ( + {formatLeaseRecordCell(row.contractNo)} + )} + + {isLeaseRecordLinkable(row.projectName) ? ( + + ) : ( + formatLeaseRecordCell(row.projectName) + )} + + {formatLeaseRecordCell(row.customerName)} + {row.businessType} + {isLeaseRecordLinkable(row.deliveryDate) ? ( + + ) : ( + formatLeaseRecordCell(row.deliveryDate) + )} + {formatLeaseRecordCell(row.deliveryPerson)} + {isLeaseRecordLinkable(row.returnDate) ? ( + + ) : ( + formatLeaseRecordCell(row.returnDate) + )} + {formatLeaseRecordCell(row.returnPerson)}
              +
              +
              + +
              +
              +
              + ); +} diff --git a/src/prototypes/vehicle-management-legacy/components/DetailLicenseInfoTab.tsx b/src/prototypes/vehicle-management-legacy/components/DetailLicenseInfoTab.tsx new file mode 100644 index 0000000..76af947 --- /dev/null +++ b/src/prototypes/vehicle-management-legacy/components/DetailLicenseInfoTab.tsx @@ -0,0 +1,385 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import type { VehicleRecord } from '../types'; +import { DetailParamField, DetailParamSection } from './DetailParamLayout'; +import { isShanghaiPlate } from '../utils/vehicle'; +import { + certPhotoStatus, + formatLicenseBalance, + formatLicenseDisplay, + getLicenseCertBadgeStatus, + LICENSE_CERT_NAV, + LICENSE_CERT_STATUS_LEGEND, + resolveVehicleLicenseBundle, + type LicenseCertBadgeStatus, + type VehicleLicenseBundle, +} from '../utils/vehicleLicenses'; + +interface DetailLicenseInfoTabProps { + record: VehicleRecord; +} + +interface LicensePhotoPreviewState { + url: string; + alt: string; +} + +const LicensePhotoPreviewContext = React.createContext<((url: string, alt: string) => void) | null>(null); + +function useLicensePhotoPreview() { + return React.useContext(LicensePhotoPreviewContext); +} + +function LicenseStatusDot({ status }: { status: LicenseCertBadgeStatus }) { + return