Commit 4a3d6a2805f9c95836c8417d3839e21959dca568

Authored by zichun
1 parent 07f72f6c

docs: en wiki — document xly's three workflow mechanisms

User asked: if Activiti isn't used, what implements workflow?
Investigated and replaced the original two-path table with a
proper three-path explainer.

Findings:
- Path 1 (single-step approval via stored proc + bCheck flag) is
  the dominant pattern. Each business table carries bCheck (426
  tables), tCheckDate (400), sCheckPerson (398) — virtually
  universal. The 审核 button POSTs to /business/doExamine which
  calls the proc named in gdsmodule.sProcName; the proc owns the
  business validation and flips bCheck=1 directly. No engine, no
  state machine, no queue.
- Path 2 (document chaining) is how multi-document business
  processes work: separate gdsmodule entries arranged as parent +
  ordered children, each with its own form and approval button.
  A "convert-to-next-doc" proc on form A creates the document for
  form B. The "01/04, 02/04…" KPI Work Center step numbering is
  just iOrder of children under a parent gdsmodule. No state
  machine; the workflow is the chain of procs wired by buttons.
- Path 3 (Activiti BPMN) is GATED by ConstantUtils.bCheckflowCheck
  which is hard-coded false. So even if a tenant configured
  gdsmoduleflow rows and deployed BPMN, the gate in
  ExamineServiceImpl.doExcuExamine would short-circuit. To
  activate Path 3 a maintainer would have to recompile xly with
  the constant flipped, OR runtime-patch it.

The page now leads with all three paths and a comparison table
covering state storage, step transitions, reassignment/delegation,
parallel branches, current activation status, and tooling.

Concluding section explains why this design fits xly's audience
(printing-industry ERP, where each business step is naturally its
own document) and the trade-off (workflow logic scattered across
stored procs rather than visualised in BPMN).
en/docs/api-reference/internal.md
... ... @@ -26,7 +26,7 @@ page is the catalog of HTTP entry points.
26 26 | `/business/getSelectDataBysControlId/{sId}` | POST | Dropdown population for a single control, by control `sId`. |
27 27 | `/business/getSelectLimit/{sId}` | POST | Paginated variant of the dropdown call. |
28 28 | `/business/addSysLocking` | POST | Optimistic-lock acquisition when a user starts editing a document — inserts a row in the system lock table keyed by `(sFormGuid, sUserId)`. The SPA fires this when entering edit mode so concurrent editors get a conflict warning. Handler: `BusinessBaseController.java:400-407`. |
29   -| `/business/doExamine` | POST | Simple "approve" — flips `bCheck = 1` on the named row via SQL. **Does NOT invoke Activiti**; this is xly's lightweight approval path used by every module that doesn't need multi-step workflow. Handler: `BusinessBaseController.java:384-391` → `BusinessBaseServiceImpl.doExamine` → `ExamineServiceImpl`. See [Two approval paths](../reference/maintainer/activiti.md#two-approval-paths) for when Activiti is used instead. |
  29 +| `/business/doExamine` | POST | Simple "approve" — flips `bCheck = 1` on the named row via SQL. **Does NOT invoke Activiti**; this is xly's lightweight approval path used by every module that doesn't need multi-step workflow. Handler: `BusinessBaseController.java:384-391` → `BusinessBaseServiceImpl.doExamine` → `ExamineServiceImpl`. See [How xly handles workflow without Activiti](../reference/maintainer/activiti.md#how-xly-handles-workflow-without-activiti) for when Activiti is used instead. |
30 30 | `/business/getProData` | POST | Generic stored-procedure invocation for a module — alternate path to `/procedureCall/doGenericProcedureCall`. Handler: `BusinessBaseController.java:350-358` → `BusinessBaseServiceImpl.getProData`. Used by FROUNT for module-level proc reads (the home dashboard `/getProData?sModelsId=...&sName=` pattern). |
31 31  
32 32 These endpoints are documented in detail by [Slice 1](../slices/01-hello-world.md)
... ...
en/docs/reference/maintainer/activiti.md
... ... @@ -7,32 +7,175 @@
7 7 > `gdsmodule.bCheck=1`. No `gdsmoduleflow` link. **The approval
8 8 > button users see (`/business/doExamine`) bypasses Activiti
9 9 > entirely** and just UPDATEs `bCheck=1` via SQL — see
10   -> [Two approval paths](#two-approval-paths) below.
  10 +> [How xly handles workflow without Activiti](#how-xly-handles-workflow-without-activiti) below.
11 11  
12 12 This page documents what's actually wired (concrete classes, URLs,
13 13 engine state) and what would have to be true for it to do anything.
14 14  
15   -## Two approval paths
16   -
17   -xly ships two parallel approval mechanisms. They look similar from
18   -the UI but go through completely different code:
19   -
20   -| Path | Triggered by | Backend | Activiti involvement |
  15 +## How xly handles workflow without Activiti
  16 +
  17 +xly has **three workflow-like mechanisms**, in decreasing order of how
  18 +much they're actually used:
  19 +
  20 +### Path 1 — Single-step approval via stored proc + `bCheck` flag (the dominant pattern)
  21 +
  22 +This is what 99% of approvals in xly look like. There is **no engine
  23 +and no state machine** — the workflow is the procedure call itself.
  24 +
  25 +The mechanics:
  26 +
  27 +1. Each business table carries the same three audit columns:
  28 + `bCheck` (the approval boolean), `sCheckPerson`, `tCheckDate`.
  29 + Empirical reach in this dev DB: `bCheck` on **426 tables**,
  30 + `tCheckDate` on 400, `sCheckPerson` on 398. So nearly every
  31 + business document has the audit trail built into its own row.
  32 +2. Each module declares a *single* approval procedure name in
  33 + `gdsmodule.sProcName` (column comment: "存储过程(审核)名称" —
  34 + "stored-procedure (approval) name"). For example
  35 + `Sp_Quo_QuotationCheck` for the quotation module,
  36 + `Sp_SalSalesCheck` for sales orders.
  37 +3. When the user clicks the 审核 button:
  38 + - `POST /business/doExamine` → `BusinessBaseController.java:384-391`
  39 + → `BusinessBaseServiceImpl.doExamine()` → `ExamineServiceImpl.doExcuExamine()`
  40 + - The service acquires a Redis lock keyed on the row id
  41 + (`ws_update_*_{sGuid}_*`) so two users can't approve concurrently.
  42 + - It dispatches to the proc named in `sProcName` via the generic
  43 + procedure-call machinery
  44 + ([proc-dispatch.md](proc-dispatch.md)).
  45 + - **The proc itself owns the business logic**: validate that all
  46 + required fields are populated, that child rows balance with
  47 + parent totals, that no related document is locked, etc.
  48 + If all checks pass, the proc UPDATEs the row to set
  49 + `bCheck = 1`, `sCheckPerson = <user>`, `tCheckDate = NOW()`.
  50 + - The proc returns `OUT sCode INT` (1 = success, ≤0 = error)
  51 + and `OUT sReturn LONGTEXT` (error message).
  52 +4. Cancel-approval is the same call with `iFlag = 0` instead of `1`;
  53 + the proc handles both directions.
  54 +5. `Sp_System_CheckSave` is a generic post-approval hook that
  55 + `BusinessBaseServiceImpl.java:1828` calls after every save of an
  56 + `bCheck`-tracked row — it writes a `sFormId` audit field and
  57 + contains placeholder slots for cross-document validation
  58 + (currently mostly commented out).
  59 +
  60 +There's no notion of "next assignee" or "approval queue" in this
  61 +path — once a single user with permission has clicked 审核 and the
  62 +proc has succeeded, the row is approved. Period.
  63 +
  64 +### Path 2 — Document-chaining as implicit multi-step workflow
  65 +
  66 +Multi-document business processes (quote → customer-confirm →
  67 +sales-order → delivery → invoice) are implemented as **separate
  68 +modules with their own forms**, not as a single document advancing
  69 +through states. The user's workflow:
  70 +
  71 +1. Module A (e.g., `quoQuotationMaster`) — fill it in, click 审核,
  72 + row gets `bCheck = 1`.
  73 +2. Module A has a button (configured via `gdsconfigformslave.sButtonParam`
  74 + pointing at `Sp_Quo_ToSalesOrder` or similar) that, when clicked,
  75 + creates a row in module B (e.g., `salSalesOrderMaster`)
  76 + pre-populated from module A.
  77 +3. The user navigates to module B, fills any extra fields, clicks
  78 + approve there too, etc.
  79 +
  80 +The "01/04, 02/04, 03/04, 04/04" step numbering on the FROUNT
  81 +[KPI Work Center](runtime.md#the-kpi-work-center-front-end-home-dashboard)
  82 +reflects this: each "flow" is a parent module containing N ordered
  83 +child modules; the steps are *just the children of a parent
  84 +gdsmodule entry*. There's no engine tracking "you're at step 2 of 4";
  85 +the user is always working on whichever document is currently
  86 +in front of them, and the framework displays `iOrder` of the
  87 +parent's children for context.
  88 +
  89 +So multi-step "workflow" emerges from:
  90 +
  91 +- A parent gdsmodule grouping the steps thematically (`估价管理流程` =
  92 + "pricing management flow" with 4 children).
  93 +- Each child module's `sProcName` (single-step approval).
  94 +- Each child module's `sButtonParam` proc that, when fired, creates
  95 + the next document.
  96 +- Each business document's `bCheck` flag tracking its own row's state.
  97 +
  98 +No state machine, no FSM library — just a **chain of stored procs
  99 +wired together by buttons on forms**. This is the mechanism you'll
  100 +see in the dev DB.
  101 +
  102 +### Path 3 — Activiti BPMN workflow (gated, currently disabled in code)
  103 +
  104 +Path 3 exists in the codebase as a **third channel** that *would*
  105 +route through Activiti when activated. It is not running in this
  106 +dev DB and currently can't be activated without recompiling.
  107 +
  108 +The gate is hard-coded in `xlyPersist/.../utils/ConstantUtils.java`:
  109 +
  110 +```java
  111 +public static Boolean bCheckflowCheck = false;
  112 +```
  113 +
  114 +Inside `ExamineServiceImpl.doExcuExamine()`, the dispatch is:
  115 +
  116 +```java
  117 +if (ConstantUtils.bCheckflowCheck) {
  118 + Map<String,Object> reMap = checkExamineFlowService
  119 + .doSendCheckFolw(sGuid, sUserName, sBrandsId, sSubsidiaryId,
  120 + sFormId, map, searMap, sBtnName, request);
  121 + if (MapUtil.isNotEmpty(reMap)) { return reMap; }
  122 +}
  123 +```
  124 +
  125 +So even if a tenant deployed BPMN and linked modules via
  126 +`gdsmoduleflow`, the `if` would short-circuit because
  127 +`bCheckflowCheck` is a Java constant `false`. To activate Path 3
  128 +you have to:
  129 +
  130 +1. Change `ConstantUtils.bCheckflowCheck = true` in source and
  131 + rebuild the WAR, **or** patch the constant at runtime.
  132 +2. Insert a `gdsmoduleflow` row keyed on `(sFormId, sBtnName)` —
  133 + this maps the form's approval button to a deployed BPMN.
  134 +3. Deploy the BPMN through the modeler so `act_re_procdef` is populated.
  135 +4. Make sure modules involved have `bCheck` semantics that align with
  136 + the BPMN's start/end events.
  137 +
  138 +Once activated, `CheckExamineFlowServiceImpl.doSendCheckFolw` reads
  139 +the `gdsmoduleflow` row, calls
  140 +`checkExamineFlowDataService.doSendCheckFolwData` to pre-stage the
  141 +data, then `doSendFlowUrl` which kicks the request to xlyFlow's
  142 +controllers. From there `ProcessServiceImpl.submitApply()` calls
  143 +`runtimeService.startProcessInstanceByKey(...)` and Activiti is in
  144 +charge — `biz_flow` + `biz_todo_item` populate, approvers see tasks
  145 +in their inbox, and `CurrencyFlowController.complete(...)` advances
  146 +the instance.
  147 +
  148 +### Comparison
  149 +
  150 +| Aspect | Path 1 (proc + bCheck) | Path 2 (doc chain) | Path 3 (Activiti) |
21 151 |---|---|---|---|
22   -| **Simple "approve" toggle** | The "审核" button on a single-row form | `POST /business/doExamine` → `BusinessBaseController.java:384-391` → `BusinessBaseServiceImpl.doExamine()` → `ExamineServiceImpl` (xlyBusinessService). The service runs an UPDATE that flips `bCheck = 1` (and writes audit fields). | **None.** `grep` confirms `ExamineServiceImpl` calls neither `runtimeService`, `taskService`, `processService`, nor any other Activiti API. The `bCheck` flag is xly's own boolean, owned by `BusinessBaseServiceImpl`. Used widely as a list filter (`SalesOrderServiceImpl` and friends do `WHERE bCheck = 1` on every "show approved-only" query). |
23   -| **Multi-step BPMN workflow** | A user submits a row whose module has `bCheck = 1` AND a populated `gdsmoduleflow` row pointing at a deployed `act_re_procdef` | `ProcessServiceImpl.submitApply()` → `runtimeService.startProcessInstanceByKey(...)`. Subsequent approvers act through `CurrencyFlowController.complete(...)` → `taskService.complete()`. xly mirrors state in `biz_flow` + `biz_todo_item`. | **Full Activiti.** Requires a deployed BPMN (in `act_re_procdef`), an active process instance (in `act_ru_*`), and an assigned task (`act_ru_task`). |
24   -
25   -In this dev DB, **path 1 is the only path that runs**. Path 2 has zero
26   -configuration anywhere — no BPMN deployed, no `gdsmoduleflow` row, no
27   -`gdsmodule.bCheck=1` modules. So when a user clicks 审核 today, the
28   -record gets approved via the simple-toggle path and Activiti is not
29   -involved.
30   -
31   -Why the second path even exists: a customer that *does* need a real
32   -multi-step approval (warehouse → finance → GM, with reassignment and
33   -delegation) gets it by deploying a BPMN through the modeler and
34   -linking modules via `gdsmoduleflow`. That activates path 2 for those
35   -modules; path 1 still handles everything else.
  152 +| State storage | `bCheck` column on the document row | None (state = which document the user opened) | `act_ru_task`, `act_hi_*`, `biz_flow`, `biz_todo_item` |
  153 +| Step transitions | Single step per document | One button click per chain link, fires a "convert-to-next-doc" proc | Engine drives transitions per BPMN graph |
  154 +| Reassignment / delegation | Not supported | Not supported | Supported via Activiti |
  155 +| Parallel branches | Not supported | Not supported | Supported via BPMN gateways |
  156 +| Currently active | Yes, on every 审核 click | Yes, in every multi-document business flow | No — `bCheckflowCheck = false` in code |
  157 +| Tooling | Just a stored proc | Stored procs + module-tree configuration | BPMN modeler at `/modeler/*` |
  158 +
  159 +### Why this design works for xly's audience
  160 +
  161 +The printing-industry ERP customers run rule-driven business
  162 +processes (quote → order → production → delivery → invoice → payment)
  163 +where each step is **its own document with its own form** by
  164 +convention. A user expects "Now I open the next form and fill it in"
  165 +rather than "the system tells me a task is waiting for me." For
  166 +that audience:
  167 +
  168 +- Path 1 + Path 2 cover every observed scenario in this dev DB.
  169 +- Path 3's value (BPMN modeling, reassignment, parallel gateways) is
  170 + reserved for the rare tenant whose approval graph genuinely needs
  171 + it.
  172 +
  173 +The trade-off: workflow logic is **scattered across stored procedures**
  174 +rather than declarable in one place. Adding a new step to a flow
  175 +means writing or editing one or more procs, not editing a BPMN
  176 +diagram. For complex, frequently-changing flows, this is brittle.
  177 +For the printing-shop reality (quote-to-cash chain that doesn't
  178 +change much per customer), it's pragmatic.
36 179  
37 180 ## Activiti is wired — engine ON
38 181  
... ...