mirror of
http://git.pig4cloud.com/pig/pigx.git
synced 2026-11-08 12:39:05 +08:00
docs(flow): add starter withdraw design
This commit is contained in:
1 parent
9d6515d8fa
commit
2c70387ace
1 file changed
+431
@@ -0,0 +1,431 @@
|
||||
# Flow Starter Withdraw Design
|
||||
|
||||
## Scope
|
||||
|
||||
This design covers the starter-side "withdraw process" capability across:
|
||||
|
||||
- `pigx/pigx-flow`
|
||||
- `pigx-ui-pro/src/views/flow`
|
||||
|
||||
The goal is to add a clear "withdraw" action for starters without conflating it with the existing "terminate process" capability.
|
||||
|
||||
## Problem Statement
|
||||
|
||||
The current starter-facing page [started.vue](/Users/lengleng/work/dev1227/pigx-ui-pro/src/views/flow/task/started.vue) exposes `终止流程`, but the backend implementation is a force-stop semantic:
|
||||
|
||||
- API: `POST /task/task/stopProcessInstance`
|
||||
- Service: `TaskServiceImpl.stopProcessInstance`
|
||||
- End reason: `finishReason=9`
|
||||
|
||||
This is not the same as business-level "withdraw":
|
||||
|
||||
- `withdraw` means the starter takes back a just-submitted process before the first approval has been consumed
|
||||
- `terminate` means a stronger administrative stop on an in-flight process
|
||||
|
||||
If the project keeps reusing terminate for starter withdraw, UI labels, audit semantics, business status sync, and future permission rules will remain mixed.
|
||||
|
||||
## Confirmed Product Decisions
|
||||
|
||||
### Withdraw Semantics
|
||||
|
||||
Use product option `B`:
|
||||
|
||||
- the starter can withdraw only when the first approval stage has not been processed yet
|
||||
|
||||
### Starter-Side Operation Set
|
||||
|
||||
On the starter side:
|
||||
|
||||
- remove the generic `终止流程` button
|
||||
- keep `撤回` as the only starter-side stop action
|
||||
|
||||
### Business Visibility
|
||||
|
||||
Business pages such as leave requests must explicitly show `已撤回`, not collapse it into `已结束`.
|
||||
|
||||
## Goals
|
||||
|
||||
1. Add a clear starter-only `撤回` action
|
||||
2. Keep `撤回` and `终止` as separate backend semantics
|
||||
3. Compute withdraw availability server-side and expose it to the UI
|
||||
4. Sync withdraw result to business tables through the existing flow status event chain
|
||||
5. Avoid duplicating withdraw logic in each business form page
|
||||
|
||||
## Non-Goals
|
||||
|
||||
1. Do not redesign admin-side force-stop permissions in this iteration
|
||||
2. Do not add withdraw-reason input in V1
|
||||
3. Do not add business-form-specific withdraw buttons in every detail page in V1
|
||||
|
||||
## Recommended Placement
|
||||
|
||||
### Primary Entry
|
||||
|
||||
Use the starter list page:
|
||||
|
||||
- [started.vue](/Users/lengleng/work/dev1227/pigx-ui-pro/src/views/flow/task/started.vue)
|
||||
|
||||
Reason:
|
||||
|
||||
- it already represents the "我发起的" mental model
|
||||
- it already groups starter-only actions such as `重新提交`
|
||||
- it keeps withdraw as a platform capability instead of pushing it into each business page
|
||||
|
||||
### Secondary Entry
|
||||
|
||||
Add the same withdraw action in the detail drawer header opened from `started.vue`.
|
||||
|
||||
Reason:
|
||||
|
||||
- users often inspect details before acting
|
||||
- it avoids forcing the user back to the list page
|
||||
|
||||
### Not Recommended for V1
|
||||
|
||||
Do not make business detail pages such as [detail.vue](/Users/lengleng/work/dev1227/pigx-ui-pro/src/views/flow/oaLeave/detail.vue) the primary entry.
|
||||
|
||||
Reason:
|
||||
|
||||
- this would duplicate logic across each business form
|
||||
- withdraw is a workflow-center capability, not a business-form-specific capability
|
||||
|
||||
## User Interaction Design
|
||||
|
||||
### Starter List Display Rules
|
||||
|
||||
On [started.vue](/Users/lengleng/work/dev1227/pigx-ui-pro/src/views/flow/task/started.vue):
|
||||
|
||||
- show `撤回` only when `canWithdraw === true`
|
||||
- show `重新提交` when `rejectToStarter === true`
|
||||
- do not show the old `终止流程` button for normal starters
|
||||
- show `已撤回` when `finishReason === '3'`
|
||||
|
||||
### Detail Drawer Display Rules
|
||||
|
||||
In the detail drawer header:
|
||||
|
||||
- show `撤回` when `canWithdraw === true`
|
||||
- optionally show disabled state plus `withdrawDisabledReason` when not allowed
|
||||
|
||||
### Confirmation Copy
|
||||
|
||||
Use a confirmation dialog before submit:
|
||||
|
||||
- title: `确认撤回流程?`
|
||||
- content: `仅首个审批节点尚未处理时可撤回。撤回后流程将结束,且不可恢复。`
|
||||
|
||||
## Approach Comparison
|
||||
|
||||
### Option 1: Reuse Existing Stop API
|
||||
|
||||
Description:
|
||||
|
||||
- keep calling `stopProcessInstance`
|
||||
- only change button label from `终止流程` to `撤回`
|
||||
|
||||
Pros:
|
||||
|
||||
- lowest implementation cost
|
||||
|
||||
Cons:
|
||||
|
||||
- backend semantic remains wrong
|
||||
- audit and business sync still treat starter withdraw as terminate
|
||||
- impossible to distinguish `已撤回` from `已终止`
|
||||
|
||||
Conclusion:
|
||||
|
||||
- reject
|
||||
|
||||
### Option 2: New Withdraw API with Shared Internal Termination Path
|
||||
|
||||
Description:
|
||||
|
||||
- add a new starter-facing withdraw API
|
||||
- keep terminate as a separate API
|
||||
- internally share low-level engine shutdown logic
|
||||
|
||||
Pros:
|
||||
|
||||
- clear product semantics
|
||||
- supports distinct UI labels and business status mapping
|
||||
- keeps future admin permission expansion clean
|
||||
|
||||
Cons:
|
||||
|
||||
- needs small backend refactor
|
||||
|
||||
Conclusion:
|
||||
|
||||
- recommend
|
||||
|
||||
### Option 3: Business-Form-Specific Withdraw Entry First
|
||||
|
||||
Description:
|
||||
|
||||
- add withdraw directly into each business detail page
|
||||
|
||||
Pros:
|
||||
|
||||
- strong local context
|
||||
|
||||
Cons:
|
||||
|
||||
- duplicated logic across forms
|
||||
- weak platform consistency
|
||||
|
||||
Conclusion:
|
||||
|
||||
- reject for V1
|
||||
|
||||
## Backend Design
|
||||
|
||||
### New API
|
||||
|
||||
Add a new endpoint in [ProcessTaskController.java](/Users/lengleng/work/dev1227/pigx/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/controller/ProcessTaskController.java):
|
||||
|
||||
```http
|
||||
POST /task/task/withdrawProcessInstance
|
||||
```
|
||||
|
||||
Request body:
|
||||
|
||||
```json
|
||||
{
|
||||
"processInstanceId": "xxx"
|
||||
}
|
||||
```
|
||||
|
||||
Use existing DTO [TaskParamDto.java](/Users/lengleng/work/dev1227/pigx/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/dto/TaskParamDto.java) in V1.
|
||||
|
||||
### Service Contract
|
||||
|
||||
Add method to [ITaskService.java](/Users/lengleng/work/dev1227/pigx/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/ITaskService.java):
|
||||
|
||||
```java
|
||||
R withdrawProcessInstance(TaskParamDto taskParamDto);
|
||||
```
|
||||
|
||||
Implement in [TaskServiceImpl.java](/Users/lengleng/work/dev1227/pigx/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/TaskServiceImpl.java).
|
||||
|
||||
### Withdraw Eligibility Rule
|
||||
|
||||
Do not try to hard-parse "first approval node" from the process graph for V1.
|
||||
|
||||
Instead, use the current persistence model:
|
||||
|
||||
1. query `process_instance_record`
|
||||
2. require:
|
||||
- current user is starter
|
||||
- `status == 1`
|
||||
- no final `finishReason`
|
||||
3. reject when runtime variable `rejectToStarter == true`
|
||||
4. inspect `process_node_record_assign_user`
|
||||
5. if any record exists for the same `processInstanceId` where:
|
||||
- `nodeId != 'root'`
|
||||
- and the task has already been processed
|
||||
then withdraw is not allowed
|
||||
|
||||
Suggested definition of "already processed":
|
||||
|
||||
- `end_time is not null`, or
|
||||
- `status != JXZ`
|
||||
|
||||
This rule aligns with the current audit model because completed-task reads already rely on [ProcessNodeRecordAssignUser.java](/Users/lengleng/work/dev1227/pigx/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/entity/ProcessNodeRecordAssignUser.java).
|
||||
|
||||
### Shared Internal Termination Logic
|
||||
|
||||
Refactor the current terminate implementation into a shared internal method, for example:
|
||||
|
||||
```java
|
||||
private R terminateProcessInstance(String processInstanceId, String finishReason)
|
||||
```
|
||||
|
||||
Use it from:
|
||||
|
||||
- `stopProcessInstance(..., finishReason='9')`
|
||||
- `withdrawProcessInstance(..., finishReason='3')`
|
||||
|
||||
This keeps engine-level stop logic shared while preserving product-level semantics.
|
||||
|
||||
### Persistence Update Rules
|
||||
|
||||
On successful withdraw:
|
||||
|
||||
1. end the Flowable runtime instance
|
||||
2. update `process_instance_record`
|
||||
- `status = 2`
|
||||
- `finish_reason = '3'`
|
||||
- `end_time = now`
|
||||
3. close in-progress `process_node_record`
|
||||
4. close in-progress `process_node_record_assign_user`
|
||||
|
||||
The main requirement is that no runtime or local audit record remains in a "进行中" state after withdraw.
|
||||
|
||||
## Frontend Design
|
||||
|
||||
### API Layer
|
||||
|
||||
Add a new API in [src/api/flow/task/index.ts](/Users/lengleng/work/dev1227/pigx-ui-pro/src/api/flow/task/index.ts):
|
||||
|
||||
```ts
|
||||
export function withdrawProcessInstance(param: { processInstanceId: string }) { ... }
|
||||
```
|
||||
|
||||
### Starter List Data
|
||||
|
||||
Extend [ProcessInstanceRecordVo.java](/Users/lengleng/work/dev1227/pigx/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/vo/ProcessInstanceRecordVo.java) with:
|
||||
|
||||
- `Boolean canWithdraw`
|
||||
- `String withdrawDisabledReason`
|
||||
|
||||
Populate both in:
|
||||
|
||||
- `queryMineStarted`
|
||||
- `detail(processInstanceId)`
|
||||
|
||||
This keeps all action availability server-driven.
|
||||
|
||||
### Starter Page Behavior
|
||||
|
||||
Update [started.vue](/Users/lengleng/work/dev1227/pigx-ui-pro/src/views/flow/task/started.vue):
|
||||
|
||||
1. remove starter-facing `终止流程`
|
||||
2. add `撤回`
|
||||
3. keep `重新提交` unchanged for `rejectToStarter`
|
||||
4. add `已撤回` tag for `finishReason === '3'`
|
||||
5. add withdraw action in drawer header as secondary entry
|
||||
|
||||
## Flow Status and Business Status Design
|
||||
|
||||
### Flow-Level Final States
|
||||
|
||||
Keep `process_instance_record.status` as coarse state:
|
||||
|
||||
- `1` 进行中
|
||||
- `2` 已结束
|
||||
|
||||
Use `finish_reason` for final meaning:
|
||||
|
||||
- `1` 通过
|
||||
- `0` 拒绝
|
||||
- `3` 撤回
|
||||
- `9` 终止
|
||||
|
||||
### Business Table Mapping
|
||||
|
||||
For leave requests in [BpmOaLeaveEntity.java](/Users/lengleng/work/dev1227/pigx/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/entity/BpmOaLeaveEntity.java), the current `leaveStatus` values are too coarse.
|
||||
|
||||
Recommended mapping:
|
||||
|
||||
- `0` 待审批
|
||||
- `1` 进行中
|
||||
- `2` 已通过
|
||||
- `3` 已撤回
|
||||
- `4` 已拒绝
|
||||
- `5` 已终止
|
||||
|
||||
If schema change must stay minimal, a fallback is adding a separate finish-reason field. That is not recommended for V1 because UI filtering and display would become more fragmented.
|
||||
|
||||
### Event-Driven Sync
|
||||
|
||||
Continue using the existing status event chain:
|
||||
|
||||
- [FlowProcessEventListener.java](/Users/lengleng/work/dev1227/pigx/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/listeners/FlowProcessEventListener.java)
|
||||
- [NodeTaskCompleteNotify.java](/Users/lengleng/work/dev1227/pigx/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/listeners/NodeTaskCompleteNotify.java)
|
||||
- [IProcessInstanceStatusEventService.java](/Users/lengleng/work/dev1227/pigx/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/event/IProcessInstanceStatusEventService.java)
|
||||
|
||||
Update [BpmOaLeaveServiceImpl.java](/Users/lengleng/work/dev1227/pigx/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/BpmOaLeaveServiceImpl.java) so `handleStatusEvent` maps based on:
|
||||
|
||||
- `status`
|
||||
- `finishReason`
|
||||
|
||||
not just `status`.
|
||||
|
||||
This design should become the standard pattern for other business tables too.
|
||||
|
||||
## Detail Response Contract
|
||||
|
||||
The current [ProcessInstanceServiceImpl.java](/Users/lengleng/work/dev1227/pigx/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessInstanceServiceImpl.java) `detail(processInstanceId)` response should be extended with:
|
||||
|
||||
- `canWithdraw`
|
||||
- `withdrawDisabledReason`
|
||||
- current `finishReason`
|
||||
|
||||
This lets the drawer header and later business detail integrations share one source of truth.
|
||||
|
||||
## Validation and Error Handling
|
||||
|
||||
### Backend Errors
|
||||
|
||||
Suggested failure messages:
|
||||
|
||||
- `流程不存在或已结束`
|
||||
- `仅发起人可撤回流程`
|
||||
- `驳回待提交状态不可撤回`
|
||||
- `首个审批节点已处理,无法撤回`
|
||||
|
||||
### Frontend Behavior
|
||||
|
||||
On withdraw failure:
|
||||
|
||||
- show backend error message directly when available
|
||||
- refresh list after success
|
||||
- close detail drawer after success if triggered there
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
### UI Acceptance
|
||||
|
||||
1. `我发起的` page shows `撤回` only for eligible in-progress instances
|
||||
2. `重新提交` remains available only for reject-to-starter instances
|
||||
3. starter-facing `终止流程` no longer appears
|
||||
4. withdrawn instances show `已撤回`
|
||||
5. detail drawer header stays consistent with list action availability
|
||||
|
||||
### Backend Acceptance
|
||||
|
||||
1. withdraw is rejected once any non-root approval record has been processed
|
||||
2. withdraw ends the runtime instance cleanly
|
||||
3. `process_instance_record.finish_reason='3'` after withdraw
|
||||
4. no in-progress node or assign-user record remains after withdraw
|
||||
5. existing terminate path still yields `finishReason='9'`
|
||||
|
||||
### Business Acceptance
|
||||
|
||||
1. leave request business status explicitly displays `已撤回`
|
||||
2. leave request no longer collapses withdraw into generic `已结束`
|
||||
|
||||
## Risks
|
||||
|
||||
1. `ProcessNodeRecordAssignUser.status` comments and actual usage are not fully consistent, so withdraw implementation should rely on current persisted behavior, not comments alone
|
||||
2. some business modules may currently map only `status` and ignore `finishReason`, causing partial UI mismatch until each listener implementation is updated
|
||||
3. parallel first-approval scenarios must use "any processed non-root approval record" as the blocking condition
|
||||
|
||||
## Rollout Order
|
||||
|
||||
1. add backend withdraw API and shared termination helper
|
||||
2. add `finishReason='3'` support in flow list/detail responses
|
||||
3. update starter page actions and status tags
|
||||
4. update leave business status mapping and display
|
||||
5. verify happy path and blocked path with real process instances
|
||||
|
||||
## Implementation Outline
|
||||
|
||||
The `writing-plans` skill referenced by the brainstorming workflow is not available in this workspace. As a fallback, use this execution order:
|
||||
|
||||
1. Backend
|
||||
- add `withdrawProcessInstance` controller/service method
|
||||
- extract shared terminate helper
|
||||
- return `canWithdraw` and `withdrawDisabledReason` from starter list and detail
|
||||
2. Frontend
|
||||
- add withdraw API client
|
||||
- replace starter-side terminate button with withdraw
|
||||
- add withdrawn status tag and detail-header action
|
||||
3. Business sync
|
||||
- extend leave status mapping for withdrawn
|
||||
- update leave list/detail labels and filters if needed
|
||||
4. Verification
|
||||
- test starter can withdraw before first approval action
|
||||
- test withdraw blocked after any first-stage approval processing
|
||||
- test business page shows `已撤回`
|
||||
Reference in new issue
Block a user