forked from ChaiHao/BPOS-MVP
三面拆分⑰:编辑即其身 —— 保存写回本版,新版本只出自「新建」
用户问:为什么编辑一个版本、保存就自动多一个副本?那不是我要的。
旧机制「保存恒新增版本号」改为:
- savePackage:pkg.version 已存在 → **就地写回该版**(号不变、状态不变);
`asNew: true`(新建按钮)或新流程首版才铸新版本号(服务端算,不生效)
- 底线三道,一道不少:
① **在途钉版不可变** —— 该版有未完结工作流(按 (包,版本) 钉定义)→ 409,
一字不落库;「就地覆盖会改掉在途正在读的编排」由这道闸守,不再靠只插不改一刀切
② **生效版必须始终可运行** —— 写回生效版跑闭合闸,不闭合 412 带整份报告;
未生效版画图不检查、随手存
③ **陈旧编辑不复活** —— 流程存在而版本号已被清理 → 409(实测:停在已删旧版上的
标签页发来保存,把老内容静默复活成了新版本;StaleVersionError 拦死)
- store:新增 updatePackageDefinition(只许命中现存版本)+ countPinnedInFlight
- 前端:保存按钮「保存 v{n}」;新建走 asNew;文案全线「新建才长版本」
- 测试:save-package.test 六例(写回/钉版/热改闭合/asNew/防复活/首版);
过程中揪出双 API 进程并存(旧进程霸端口跑旧代码)——已重启归一
实测:改名保存 → 版本列表仍唯一 v4 生效中,名字已写回;八例端到端全绿;
五闸退出码全过(api:check 提交后转绿)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
import type { ProcessNode, ProcessPackage, StaffId, TerminalState, WorkflowState } from '@contracts';
|
||||
import type { ProcessService, StartWorkflowInput } from '../../contract.ts';
|
||||
import { CallInterfaceViolationError, NotClosedError, PositionVacantError } from '../../contract.ts';
|
||||
import { CallInterfaceViolationError, NotClosedError, PinnedVersionError, PositionVacantError, StaleVersionError } from '../../contract.ts';
|
||||
import type {
|
||||
ActivityDirectory,
|
||||
ApprovalGateway,
|
||||
@@ -61,7 +61,7 @@ export function createProcessService(deps: ProcessServiceDeps): ProcessService {
|
||||
return hit;
|
||||
}
|
||||
|
||||
/** 下一个版本号 —— 保存恒新增,不覆盖任何在途任务正在读的定义 */
|
||||
/** 下一个版本号 —— 只在显式新建 / 新流程首版时用(编辑即其身,写回不长号) */
|
||||
async function nextVersion(id: string): Promise<number> {
|
||||
const all = await deps.store.listPackages();
|
||||
return all.filter((pkg) => pkg.id === id).reduce((max, pkg) => Math.max(max, pkg.version), 0) + 1;
|
||||
@@ -298,9 +298,40 @@ export function createProcessService(deps: ProcessServiceDeps): ProcessService {
|
||||
* 拿闭合校验拦保存,画到一半就存不了(实测 412 卡死编辑)。
|
||||
* 存下来的是**未生效**的新版本 —— 完整性闸在激活那一步。
|
||||
*/
|
||||
async savePackage(pkg) {
|
||||
const saved: ProcessPackage = { ...pkg, version: await nextVersion(pkg.id), state: 'retired' };
|
||||
await deps.store.savePackage(saved);
|
||||
async savePackage(pkg, opts) {
|
||||
const existing = opts?.asNew === true ? undefined : await deps.store.getPackage(pkg.id, pkg.version);
|
||||
|
||||
if (existing === undefined) {
|
||||
/*
|
||||
* 流程已存在而这个版本号查无此版 = 陈旧编辑器(实测:停在已清理旧版上的
|
||||
* 标签页发来保存,把老内容静默复活成了新版本)—— 拒掉,让人刷新后再改。
|
||||
* 只有整条流都不存在(新流程首版)或显式 asNew 才走插入。
|
||||
*/
|
||||
if (opts?.asNew !== true) {
|
||||
const newest = await deps.store.getPackage(pkg.id);
|
||||
if (newest !== undefined) throw new StaleVersionError(pkg.id, pkg.version);
|
||||
}
|
||||
// 显式新建 / 新流程首版:插入下一个版本号,不生效 —— 新版本只出自这条路
|
||||
const saved: ProcessPackage = { ...pkg, version: await nextVersion(pkg.id), state: 'retired' };
|
||||
await deps.store.savePackage(saved);
|
||||
return saved;
|
||||
}
|
||||
|
||||
/*
|
||||
* **编辑即其身**(产品决策 2026-07-28 第四轮):保存写回当前版本,号不变,
|
||||
* 不再每存一次长一版。两道底线:
|
||||
* ① 在途钉版不可变 —— 工作流按 (包, 版本) 钉定义,有未完结的就不许改字
|
||||
* ② 生效版必须始终可运行 —— 写回生效版要过闭合闸(未生效版画图不检查)
|
||||
*/
|
||||
const pinned = await deps.store.countPinnedInFlight(pkg.id, pkg.version);
|
||||
if (pinned > 0) throw new PinnedVersionError(pkg.id, pkg.version, pinned);
|
||||
|
||||
const saved: ProcessPackage = { ...pkg, version: existing.version, state: existing.state };
|
||||
if (existing.state === 'active') {
|
||||
const report = await this.validate(saved);
|
||||
if (!report.closed) throw new NotClosedError(report);
|
||||
}
|
||||
await deps.store.updatePackageDefinition(pkg.id, pkg.version, saved);
|
||||
return saved;
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user