版本号自己定:点编辑,整列版本进编辑态,取消/保存收尾

用户的目的是**自己改版本号**,不是让系统替他递增;顺带把上一轮那些
指示性交互全撤了 —— 这软件给专业业务设计人员用,不需要「点我改」的提示条。

界面:
  · 顶栏 编辑 → 取消 / 保存(与流程图卡「编辑 → 保存」同一套手势);
  · 行内两个字段:号(自己编)与名(各版不同),行尾 删除;
  · 改动攒在草稿里,按保存一趟落库;取消原样退出;
  · **删掉**:改名指示条、「点我改」虚下划线、`删掉 v4`/`激活 v4` 这类复述钮名的浮字。
    只留下禁用态那一句(生效版先停用 / 还不闭合),那不是指示,是钮为什么点不动。

后端 —— 号从「系统递增」放开为「设计人员自己编」,两道闸是数据本身要求的:
  · 撞号拒(VersionTakenError,409);
  · 在途钉版拒改号(那些单正按这个号找定义,搬号等于抽它们脚下的地板);
  · 已完结的单**跟着改指**:号在三处(包的列、definition、workflow.packageVersion)
    一起改 —— 号只是这一版的名牌,搬号连引用一起搬,留痕仍指同一份定义。
    先前写成「跑过单就不许改号」,实测等于任何试跑过的包永远编不了号,是过闸。
  · 名不设闸:标签不参与流转,在途钉版也能改;改名只动这一版(推翻前一日的
    「及于整条流」)—— 各版本正是靠名字互相区分,全同名等于没名字。

路由 POST /api/gov/process/:id/:version/edit 取代 …/:id/rename(单一消费者是本仓前端);
CONTRACT.md 与产品设计.md 同步,api-report 快照随本次提交。

实测(丁子乔账号,测后已 reset-runs 清运行数据):v4→v2→v4 往返,列表、画布图签、
库里三处同步;在途单存在时改号如实报 409 并原样退回。五闸绿。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
2026-07-29 22:15:19 +08:00
co-authored by Claude Opus 5
parent 05c0a57cad
commit f631b5b8bb
18 changed files with 405 additions and 179 deletions
+26 -7
View File
@@ -36,7 +36,7 @@ import {
staffIdSchema,
taskIdSchema,
deleteProcessVersionRoute,
renameProcessRoute,
editProcessVersionRoute,
setNodeOwnerRoute,
setTriggerConfirmRoute,
listNodeOwnersRoute,
@@ -49,7 +49,13 @@ import { extractFields } from '@modules/channel';
import { assembleInput, scopeOf } from '@modules/trigger';
import { InstallRejectedError } from '@modules/activity-registry';
import type { InstallVerdict } from '@modules/activity-registry';
import { ActiveVersionError, NotClosedError, PinnedVersionError, StaleVersionError } from '@modules/process';
import {
ActiveVersionError,
NotClosedError,
PinnedVersionError,
StaleVersionError,
VersionTakenError,
} from '@modules/process';
import type { Container } from '../container.ts';
import type { AppEnv } from '../http-env.ts';
@@ -505,18 +511,31 @@ export function registerGovernanceOpsRoutes(app: Hono<AppEnv>, container: Contai
}
});
/** 改名:名字是这条流的身份 —— 一次改及于所有版本;在途单不受影响(名字不参与流转) */
app.post(renameProcessRoute.path, async (c) => {
/** 改版本记录:号自己定、名随之 —— 撞号与「跑过单改号」409,别的版一字不动 */
app.post(editProcessVersionRoute.path, async (c) => {
const denied = await governProcess(c.get('account'));
if (denied) return c.json(denied, denyStatus(denied));
const parsed = renameProcessRoute.body.safeParse(await c.req.json());
const parsed = editProcessVersionRoute.body.safeParse(await c.req.json());
if (!parsed.success) {
const body: ApiError = { code: 'validation-failed', message: '流程名不合协议(1–40 字)' };
const body: ApiError = { code: 'validation-failed', message: '版本号要 1–9999 的整数,名字 1–40 字' };
return c.json(body, 400);
}
const version = Number(c.req.param('version'));
if (!Number.isInteger(version)) {
const body: ApiError = { code: 'validation-failed', message: '版本号要是整数' };
return c.json(body, 400);
}
const patch = {
...(parsed.data.version === undefined ? {} : { version: parsed.data.version }),
...(parsed.data.name === undefined ? {} : { name: parsed.data.name.trim() }),
};
try {
return c.json({ packages: await container.process.rename(c.req.param('id'), parsed.data.name.trim()) });
return c.json({ packages: await container.process.editVersion(c.req.param('id'), version, patch) });
} catch (error) {
if (error instanceof VersionTakenError || error instanceof PinnedVersionError) {
const body: ApiError = { code: 'conflict', message: error.message };
return c.json(body, 409);
}
const body: ApiError = { code: 'not-found', message: error instanceof Error ? error.message : String(error) };
return c.json(body, 404);
}