M6(4/n): 工作台五要素齐活 —— 审批 / 消息 / 纪要 / 汇报
四个 desk-* feature 自 workbench 原型 ModuleWorkspace.tsx 平移(jc-* 类名原样),
共用件落 shared/desk/module-kit;mock 数据换成服务端投影:
· desk-approval:审批点卡到审批人工位;依据快照、驳回走向(声明式)、驳回必附意见
· desk-message:通知 + 跨任务征答**就地作答**;卡面表单由 card.ts schema 驱动,
与后端回传校验同 schema;跨人卡不占任务清单,不开放任务对话流
· desk-minute:纪要维护 + 决议登记起单;正文可续、决议只增
· desk-report:四周期草案 + 度量八指标 + 确认即提交
左轨角标常驻(待批 / 未读),责任面要一眼看见什么在等我。
修一个真 bug:process.start 从不校验流程包的 callInterface,
先插工作流再由 task.start 撞活动契约 —— 失败就留下一条永远推不动的在途单。
改为**先校验后建单**(CallInterfaceViolationError),一条工作流都不留;
路由回 400 并带上缺哪个字段。已清掉这次触发出的两条孤儿单。
判据 ⑩ 纪要一环跑通:决议 → 登记触发 → 工作流 → A1 落到 BD 冯兵,
登记人即来源已记在单上;同一决议重复登记被 412 挡下。
gates: tsc / eslint / depcruise(316 模块)/ api-report / 380 测试 全绿
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -49,6 +49,12 @@ export type ProcessService = {
|
||||
refreshState(workflowId: string): Promise<WorkflowState>;
|
||||
terminate(workflowId: string, reason: string): Promise<WorkflowState>;
|
||||
};
|
||||
/** 起单入参不合调用接口契约 —— 在建工作流之前判掉,不留半条在途单 */
|
||||
export declare class CallInterfaceViolationError extends Error {
|
||||
readonly packageId: string;
|
||||
readonly errors: readonly string[];
|
||||
constructor(packageId: string, errors: readonly string[]);
|
||||
}
|
||||
export declare class NotPublishableError extends Error {
|
||||
readonly report: ClosureReport;
|
||||
constructor(report: ClosureReport);
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
* module: process —— 唯一出口。只做 re-export,不写逻辑(编码规范 §10.1)。
|
||||
*/
|
||||
export type { ActivityContractFace, AssembleScope, Candidate, ClosureInput, ClosureReport, ClosureViolation, NodeInstance, OwnerSpec, ProcessPackage, ProcessService, ResolveContext, Resolution, StartWorkflowInput, WorkflowRecord, WorkflowState, } from './contract.ts';
|
||||
export { NotPublishableError, PositionVacantError } from './contract.ts';
|
||||
export { CallInterfaceViolationError, NotPublishableError, PositionVacantError } from './contract.ts';
|
||||
export type { ActivityDirectory, ApprovalGateway, Clock, FlowEventPublisher, IdGenerator, OrgDirectory, ProcessStore, ReceiptNotifier, Sfeel, TaskGateway, } from './port.ts';
|
||||
export type { ProcessServiceDeps } from './internal/usecase/process-service.ts';
|
||||
export { createProcessService } from './internal/usecase/process-service.ts';
|
||||
|
||||
@@ -95,6 +95,17 @@ export type ApprovalGateway = {
|
||||
id: string;
|
||||
}>;
|
||||
};
|
||||
/**
|
||||
* 调用接口的入参校验 —— app 注入 ajv。
|
||||
* 「凡单必有来源」的另一半是「凡单必合契约」:不合契约**不得起单**,
|
||||
* 且必须在**建工作流之前**判掉,否则会留下一条永远推不动的在途单。
|
||||
*/
|
||||
export type SchemaValidator = {
|
||||
validate(schema: Record<string, unknown>, value: unknown): {
|
||||
ok: boolean;
|
||||
errors: string[];
|
||||
};
|
||||
};
|
||||
/** S-FEEL —— app 注入 `@sfeel`:流转零 LLM */
|
||||
export type Sfeel = {
|
||||
condition(expression: string, scope: Readonly<Record<string, unknown>>): boolean;
|
||||
|
||||
@@ -2010,6 +2010,51 @@ export declare const sayRoute: {
|
||||
}, z.core.$strip>], "k">;
|
||||
};
|
||||
/** 消息要素 */
|
||||
/** 消息是**信息**,不产生责任;跨任务征答卡以系统消息送达,**就地作答** */
|
||||
export declare const systemMessageSchema: z.ZodObject<{
|
||||
id: z.ZodString;
|
||||
kind: z.ZodString;
|
||||
title: z.ZodString;
|
||||
body: z.ZodString;
|
||||
at: z.ZodString;
|
||||
read: z.ZodBoolean;
|
||||
card: z.ZodOptional<z.ZodObject<{
|
||||
cardId: z.ZodString;
|
||||
sourceTaskId: z.ZodString;
|
||||
fields: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
||||
key: z.ZodString;
|
||||
label: z.ZodString;
|
||||
kind: z.ZodEnum<{
|
||||
number: "number";
|
||||
file: "file";
|
||||
text: "text";
|
||||
check: "check";
|
||||
date: "date";
|
||||
textarea: "textarea";
|
||||
radio: "radio";
|
||||
select: "select";
|
||||
money: "money";
|
||||
}>;
|
||||
placeholder: z.ZodOptional<z.ZodString>;
|
||||
options: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
||||
value: z.ZodString;
|
||||
label: z.ZodString;
|
||||
negative: z.ZodOptional<z.ZodBoolean>;
|
||||
}, z.core.$strip>>>;
|
||||
group: z.ZodOptional<z.ZodString>;
|
||||
hint: z.ZodOptional<z.ZodString>;
|
||||
ai: z.ZodOptional<z.ZodString>;
|
||||
unit: z.ZodOptional<z.ZodString>;
|
||||
required: z.ZodOptional<z.ZodBoolean>;
|
||||
cols: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>]>>;
|
||||
}, z.core.$strip>>>;
|
||||
ask: z.ZodDefault<z.ZodString>;
|
||||
answered: z.ZodBoolean;
|
||||
rejectedReason: z.ZodOptional<z.ZodString>;
|
||||
supersededBy: z.ZodOptional<z.ZodString>;
|
||||
}, z.core.$strip>>;
|
||||
}, z.core.$strip>;
|
||||
export type SystemMessageView = z.infer<typeof systemMessageSchema>;
|
||||
export declare const listMessagesRoute: {
|
||||
readonly method: "GET";
|
||||
readonly path: "/api/desk/messages";
|
||||
@@ -2021,9 +2066,69 @@ export declare const listMessagesRoute: {
|
||||
body: z.ZodString;
|
||||
at: z.ZodString;
|
||||
read: z.ZodBoolean;
|
||||
card: z.ZodOptional<z.ZodObject<{
|
||||
cardId: z.ZodString;
|
||||
sourceTaskId: z.ZodString;
|
||||
fields: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
||||
key: z.ZodString;
|
||||
label: z.ZodString;
|
||||
kind: z.ZodEnum<{
|
||||
number: "number";
|
||||
file: "file";
|
||||
text: "text";
|
||||
check: "check";
|
||||
date: "date";
|
||||
textarea: "textarea";
|
||||
radio: "radio";
|
||||
select: "select";
|
||||
money: "money";
|
||||
}>;
|
||||
placeholder: z.ZodOptional<z.ZodString>;
|
||||
options: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
||||
value: z.ZodString;
|
||||
label: z.ZodString;
|
||||
negative: z.ZodOptional<z.ZodBoolean>;
|
||||
}, z.core.$strip>>>;
|
||||
group: z.ZodOptional<z.ZodString>;
|
||||
hint: z.ZodOptional<z.ZodString>;
|
||||
ai: z.ZodOptional<z.ZodString>;
|
||||
unit: z.ZodOptional<z.ZodString>;
|
||||
required: z.ZodOptional<z.ZodBoolean>;
|
||||
cols: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>]>>;
|
||||
}, z.core.$strip>>>;
|
||||
ask: z.ZodDefault<z.ZodString>;
|
||||
answered: z.ZodBoolean;
|
||||
rejectedReason: z.ZodOptional<z.ZodString>;
|
||||
supersededBy: z.ZodOptional<z.ZodString>;
|
||||
}, z.core.$strip>>;
|
||||
}, z.core.$strip>>;
|
||||
}, z.core.$strip>;
|
||||
};
|
||||
export declare const markMessageReadRoute: {
|
||||
readonly method: "POST";
|
||||
readonly path: "/api/desk/messages/:messageId/read";
|
||||
readonly params: z.ZodObject<{
|
||||
messageId: z.ZodString;
|
||||
}, z.core.$strip>;
|
||||
readonly output: z.ZodObject<{
|
||||
ok: z.ZodBoolean;
|
||||
}, z.core.$strip>;
|
||||
};
|
||||
/** 就地作答 —— 回传经统一入口写回来源任务,调用与回传全程在任务侧留痕 */
|
||||
export declare const answerMessageRoute: {
|
||||
readonly method: "POST";
|
||||
readonly path: "/api/desk/messages/:messageId/answer";
|
||||
readonly params: z.ZodObject<{
|
||||
messageId: z.ZodString;
|
||||
}, z.core.$strip>;
|
||||
readonly body: z.ZodObject<{
|
||||
answer: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
||||
}, z.core.$strip>;
|
||||
readonly output: z.ZodObject<{
|
||||
ok: z.ZodBoolean;
|
||||
}, z.core.$strip>;
|
||||
};
|
||||
export type ListMessagesOutput = z.infer<typeof listMessagesRoute.output>;
|
||||
export declare const minuteResolutionSchema: z.ZodObject<{
|
||||
id: z.ZodString;
|
||||
text: z.ZodString;
|
||||
@@ -2403,3 +2508,75 @@ export declare const submitReportRoute: {
|
||||
};
|
||||
export type ListMinutesOutput = z.infer<typeof listMinutesRoute.output>;
|
||||
export type ListReportsOutput = z.infer<typeof listReportsRoute.output>;
|
||||
export declare const approvalItemSchema: z.ZodObject<{
|
||||
id: z.ZodString;
|
||||
workflowId: z.ZodString;
|
||||
workflowSubject: z.ZodString;
|
||||
nodeId: z.ZodString;
|
||||
title: z.ZodString;
|
||||
basis: z.ZodArray<z.ZodObject<{
|
||||
k: z.ZodString;
|
||||
v: z.ZodString;
|
||||
}, z.core.$strip>>;
|
||||
ask: z.ZodString;
|
||||
state: z.ZodEnum<{
|
||||
pending: "pending";
|
||||
rejected: "rejected";
|
||||
passed: "passed";
|
||||
}>;
|
||||
rejectRoute: z.ZodString;
|
||||
raisedAt: z.ZodString;
|
||||
}, z.core.$strip>;
|
||||
export type ApprovalItem = z.infer<typeof approvalItemSchema>;
|
||||
export declare const listApprovalsForMeRoute: {
|
||||
readonly method: "GET";
|
||||
readonly path: "/api/approvals";
|
||||
readonly output: z.ZodObject<{
|
||||
approvals: z.ZodArray<z.ZodObject<{
|
||||
id: z.ZodString;
|
||||
workflowId: z.ZodString;
|
||||
workflowSubject: z.ZodString;
|
||||
nodeId: z.ZodString;
|
||||
title: z.ZodString;
|
||||
basis: z.ZodArray<z.ZodObject<{
|
||||
k: z.ZodString;
|
||||
v: z.ZodString;
|
||||
}, z.core.$strip>>;
|
||||
ask: z.ZodString;
|
||||
state: z.ZodEnum<{
|
||||
pending: "pending";
|
||||
rejected: "rejected";
|
||||
passed: "passed";
|
||||
}>;
|
||||
rejectRoute: z.ZodString;
|
||||
raisedAt: z.ZodString;
|
||||
}, z.core.$strip>>;
|
||||
}, z.core.$strip>;
|
||||
};
|
||||
/** 通过 / 驳回,附依据 —— 驳回必附意见 */
|
||||
export declare const decideApprovalRoute: {
|
||||
readonly method: "POST";
|
||||
readonly path: "/api/approvals/:workflowId/:nodeId/decide";
|
||||
readonly params: z.ZodObject<{
|
||||
workflowId: z.ZodString;
|
||||
nodeId: z.ZodString;
|
||||
}, z.core.$strip>;
|
||||
readonly body: z.ZodObject<{
|
||||
verdict: z.ZodEnum<{
|
||||
pass: "pass";
|
||||
reject: "reject";
|
||||
}>;
|
||||
comment: z.ZodOptional<z.ZodString>;
|
||||
}, z.core.$strip>;
|
||||
readonly output: z.ZodObject<{
|
||||
ok: z.ZodBoolean;
|
||||
workflowState: z.ZodEnum<{
|
||||
terminated: "terminated";
|
||||
running: "running";
|
||||
waiting: "waiting";
|
||||
completed: "completed";
|
||||
abnormal: "abnormal";
|
||||
}>;
|
||||
}, z.core.$strip>;
|
||||
};
|
||||
export type ListApprovalsForMeOutput = z.infer<typeof listApprovalsForMeRoute.output>;
|
||||
|
||||
Reference in New Issue
Block a user