Commit 7a8b9c734bb994e6515d2e58512e1b460f0cd1ba
1 parent
4a3d6a28
docs: en wiki — document 万昌's schema-extending workflow customisation
User asked: are there examples of customising workflow other than
the standard hardcoded one? Investigated and found a substantial
customer example.
Findings:
- script/客户/万昌/计件工资/日报审核/领班驳回.sql is a 185-line
customer-side workflow customisation defining
Sp_mftproductionreportmaster_check1_0 ("Foreman Rejection",
state 1 → state 0 transition).
- Naming convention: Sp_<table>_check<currentState>_<nextState>.
No procs in standard DB use this pattern; it's 万昌's own
convention.
- The proc resets SIX approval flags simultaneously (bManager,
bIPQC, bDeputy, bSubmit, bWorkshopManager, bCheck) — 万昌 has
ALTER TABLEd mftproductionreportmaster to add 5 columns that
don't exist in the standard schema.
- Adds sRejectMemo rejection-reason history (also custom column),
appends each rejection's reason + timestamp + foreman name.
- Calls sp_add_flow_log — a custom audit-log proc that doesn't
exist in the standard DB.
- Includes customer-specific business rules (e.g., block rejection
if any slave row has bSAPCheck=1 — SAP-sync guard).
So the answer to "any examples of customising workflow":
- Yes — 万昌 has built their OWN multi-level approval workflow on
top of xly's button-dispatch primitive: schema extension +
custom transition procs + custom audit log. The framework
provides only the button-press dispatch (via /business/
genericProcedureCall* or sButtonParam); everything else is
customer-defined.
- This is fundamentally different from Activiti — no BPMN, no FSM
library, no engine. Just SQL + ALTER TABLE.
- Other customer dirs (千彩, 重庆展印, 朝阳, etc.) mostly customise
*calculations and reports*, not workflow. 万昌's pattern is
rare but real.
Slice 5 gets a "Worked-example 2" section walking through
领班驳回.sql in detail, plus a customisation-patterns-at-a-glance
breakdown of what each of the 18 customer dirs actually contains.
Activiti.md gets a back-reference: "A real Path-1 customisation
example" pointing at the new slice 5 worked example, with the
empirical observation that Activiti deployment is NOT seen in any
script/客户/ directory.
Showing
2 changed files
with
147 additions
and
0 deletions
en/docs/reference/maintainer/activiti.md
| @@ -156,6 +156,19 @@ the instance. | @@ -156,6 +156,19 @@ the instance. | ||
| 156 | | Currently active | Yes, on every 审核 click | Yes, in every multi-document business flow | No — `bCheckflowCheck = false` in code | | 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/*` | | 157 | | Tooling | Just a stored proc | Stored procs + module-tree configuration | BPMN modeler at `/modeler/*` | |
| 158 | 158 | ||
| 159 | +### A real Path-1 customisation example | ||
| 160 | + | ||
| 161 | +[Slice 5](../../slices/05-customer-sql-override.md#worked-example-2-builds-a-multi-level-approval-workflow) | ||
| 162 | +walks through 万昌's `领班驳回.sql` — a customer-side multi-level | ||
| 163 | +approval rejection. It's the canonical example of how customers | ||
| 164 | +extend Path 1 when the single-`bCheck` flag isn't enough: they | ||
| 165 | +`ALTER TABLE` to add multiple approval flags (`bManager`, `bIPQC`, | ||
| 166 | +`bDeputy`, …), write transition procs following the | ||
| 167 | +`Sp_<table>_check<currentState>_<nextState>` naming convention, and | ||
| 168 | +emit audit entries via a custom `sp_add_flow_log`. This is the | ||
| 169 | +empirically-observed customisation channel — Activiti deployment | ||
| 170 | +is not seen in any `script/客户/` directory. | ||
| 171 | + | ||
| 159 | ### Why this design works for xly's audience | 172 | ### Why this design works for xly's audience |
| 160 | 173 | ||
| 161 | The printing-industry ERP customers run rule-driven business | 174 | The printing-industry ERP customers run rule-driven business |
en/docs/slices/05-customer-sql-override.md
| @@ -143,6 +143,140 @@ mysql --defaults-file=$HOME/.my.cnf xlyweberp_saas_ai \ | @@ -143,6 +143,140 @@ mysql --defaults-file=$HOME/.my.cnf xlyweberp_saas_ai \ | ||
| 143 | diff /tmp/std.sql script/客户/重庆展印/Sp_SalSalesCheck.sql | head -200 | 143 | diff /tmp/std.sql script/客户/重庆展印/Sp_SalSalesCheck.sql | head -200 |
| 144 | ``` | 144 | ``` |
| 145 | 145 | ||
| 146 | +## Worked-example 2: 万昌 builds a multi-level approval workflow | ||
| 147 | + | ||
| 148 | +The 重庆展印 example above replaces *one* proc body. The | ||
| 149 | +**`script/客户/万昌/`** directory shows a more ambitious pattern: the | ||
| 150 | +customer extends the schema and builds a multi-level approval | ||
| 151 | +workflow that the standard framework doesn't ship. | ||
| 152 | + | ||
| 153 | +The customisation tree (excerpt): | ||
| 154 | + | ||
| 155 | +``` | ||
| 156 | +script/客户/万昌/ | ||
| 157 | +├── 计件工资/ | ||
| 158 | +│ ├── 日报审核/ | ||
| 159 | +│ │ └── 领班驳回.sql ← this slice's anchor | ||
| 160 | +│ ├── 报表/ | ||
| 161 | +│ │ ├── 包装补时.sql | ||
| 162 | +│ │ ├── 员工大废品.sql | ||
| 163 | +│ │ ├── 班组大废品率查询报表.sql | ||
| 164 | +│ │ ├── 手工质检组返工.sql | ||
| 165 | +│ │ └── Sp_Manual_quality_inspection_rework.sql | ||
| 166 | +│ └── 计件工资核算/ | ||
| 167 | +│ ├── 计件工资/ | ||
| 168 | +│ │ ├── sp_piece_rate_j.sql | ||
| 169 | +│ │ ├── sp_piece_rate_JZ.sql | ||
| 170 | +│ │ ├── sp_piece_rate_other.sql | ||
| 171 | +│ │ └── sp_piece_rate_w.sql | ||
| 172 | +│ ├── 员工工资汇总查询/员工工资汇总查询.sql | ||
| 173 | +│ ├── Sp_BtnEven_CalcJsHs.sql | ||
| 174 | +│ └── sp_btn_WorkOrderAssessmentPassRate.sql | ||
| 175 | +├── Sp_getworkorder_calc_cb.sql | ||
| 176 | +└── … | ||
| 177 | +``` | ||
| 178 | + | ||
| 179 | +The Chinese-named subdirectories (`计件工资`/`日报审核` = "piece-rate | ||
| 180 | +wages / daily-report approval") encode the customer's organisational | ||
| 181 | +flow into the file system itself. A maintainer reading `ls` knows | ||
| 182 | +which business process each script belongs to. | ||
| 183 | + | ||
| 184 | +### What the rejection script actually does | ||
| 185 | + | ||
| 186 | +`领班驳回.sql` ("Foreman Rejection") is 185 lines defining | ||
| 187 | +`Sp_mftproductionreportmaster_check1_0`. The naming is xly's | ||
| 188 | +state-transition convention: `Sp_<table>_check<currentState>_<nextState>`, | ||
| 189 | +so `check1_0` means "transition from state 1 (approved) back to | ||
| 190 | +state 0 (draft)" — i.e., a rejection. | ||
| 191 | + | ||
| 192 | +Trimmed body of the central UPDATE: | ||
| 193 | + | ||
| 194 | +```sql | ||
| 195 | +SET p_setSql = CONCAT('bManager = 0, | ||
| 196 | + bIPQC = 0, | ||
| 197 | + bDeputy = 0, | ||
| 198 | + bSubmit = 0, | ||
| 199 | + bWorkshopManager = 0, | ||
| 200 | + bCheck = 0, | ||
| 201 | + sRejectMemo = ''', p_sRejectMemo, ''', | ||
| 202 | + sMReserve1 = ', p_textareaValue); | ||
| 203 | + | ||
| 204 | +Set @sSqlStmt = CONCAT('Update mftproductionreportmaster | ||
| 205 | + Set ', p_setSql, ' | ||
| 206 | + Where sId = ''', p_sTmpId, ''' | ||
| 207 | + AND sBrandsId = ''', sBrId, ''' | ||
| 208 | + AND sSubsidiaryId = ''', sSuId, ''''); | ||
| 209 | +PREPARE sSqlStmt FROM @sSqlStmt; | ||
| 210 | +EXECUTE sSqlStmt; | ||
| 211 | + | ||
| 212 | +CALL sp_add_flow_log(p_sTmpId, p_sTmpId, '驳回', '驳回', '驳回', | ||
| 213 | + sMakePerson, p_sRejectMemo, @sReturn, @sCode); | ||
| 214 | +``` | ||
| 215 | + | ||
| 216 | +So one button click resets **six** approval flags simultaneously, | ||
| 217 | +appends to a per-row rejection-reason history, and writes to a | ||
| 218 | +custom audit log. | ||
| 219 | + | ||
| 220 | +### What's customer-side and not in standard | ||
| 221 | + | ||
| 222 | +Verified against the dev DB recon target (`xlyweberp_saas_ai`): | ||
| 223 | + | ||
| 224 | +| Customisation | Standard schema? | 万昌 needs to add it | | ||
| 225 | +|---|---|---| | ||
| 226 | +| Multi-level approval columns: `bManager`, `bIPQC`, `bDeputy`, `bSubmit`, `bWorkshopManager` on `mftproductionreportmaster` | **No** — only `bCheck`, `sCheckPerson`, `tCheckDate` exist. | Yes — `ALTER TABLE` to add 5 boolean columns. | | ||
| 227 | +| `sRejectMemo` rejection-reason history column | **No** | Yes — `ALTER TABLE` to add a longtext. | | ||
| 228 | +| `sp_add_flow_log` audit-log proc | **No** — does not exist in standard. | Yes — wholly customer-defined. | | ||
| 229 | +| Naming convention `Sp_<table>_check<n>_<m>` | **No** — no procs in DB use this pattern. | Yes — 万昌's convention. | | ||
| 230 | +| Hook into the framework's button machinery | Yes — `gdsconfigformslave.sButtonParam` points at the proc name. | (configuration only) | | ||
| 231 | + | ||
| 232 | +So 万昌's "Foreman Rejection" workflow is **a customer-built | ||
| 233 | +state-machine atop xly's button primitive**: schema extension + | ||
| 234 | +custom procs + custom audit log. The framework provides only the | ||
| 235 | +button-press dispatch (via `/business/genericProcedureCall*` or the | ||
| 236 | +button-param hook on the form-slave). Everything else — what state | ||
| 237 | +the document is in, what flags toggle, what audit text gets logged — | ||
| 238 | +is customer-side. | ||
| 239 | + | ||
| 240 | +This is fundamentally different from how Activiti would solve the | ||
| 241 | +same problem (BPMN graph + assignee model + Activiti's task table). | ||
| 242 | +xly's framework lets the customer choose either model: | ||
| 243 | +- **Activiti style**: deploy a BPMN, link via `gdsmoduleflow`, | ||
| 244 | + flip `ConstantUtils.bCheckflowCheck = true` (see | ||
| 245 | + [activiti.md](../reference/maintainer/activiti.md#path-3-activiti-bpmn-workflow-gated-currently-disabled-in-code)). | ||
| 246 | +- **万昌 style**: extend the schema, write transition procs, drop | ||
| 247 | + them under `script/客户/<name>/<flow-name>/`, apply manually. | ||
| 248 | + | ||
| 249 | +The 万昌 style is what the codebase actually shows in production- | ||
| 250 | +adjacent customisations — Activiti is wired but no customer | ||
| 251 | +directory under `script/客户/` deploys a BPMN, while 万昌-style | ||
| 252 | +schema-extending workflows DO show up. That's the empirical answer | ||
| 253 | +to "how is workflow customised in this repo?": **schema-extending | ||
| 254 | +stored procs delivered via per-customer override scripts**. | ||
| 255 | + | ||
| 256 | +### Customer customisation patterns at a glance | ||
| 257 | + | ||
| 258 | +Of the 18 customer override directories, most don't customise | ||
| 259 | +*workflow* per se — they customise **calculations and reports**. | ||
| 260 | +The breakdown of what each directory contains: | ||
| 261 | + | ||
| 262 | +- `万昌` (14 files): includes the `领班驳回.sql` workflow extension, | ||
| 263 | + plus piece-rate wage calculation procs. | ||
| 264 | +- `千彩` (50 files): the most heavily customised customer. Mostly | ||
| 265 | + per-tenant calculation overrides (`Sp_Calc_*`, `Sp_Inventory_*`, | ||
| 266 | + `Sp_Manufacture_*`) and one workflow-list view | ||
| 267 | + (`viw_NoSalSalesChecking`). | ||
| 268 | +- `重庆展印` (2 files): replacement of one sales-check proc + a | ||
| 269 | + companion view, as documented above. | ||
| 270 | +- `朝阳` (8), `金宣发` (8), `无锡中江` (8), `亚明威` (6), `福雅` (5), | ||
| 271 | + `金九` (5), `快马` (4), and others: smaller calc / report | ||
| 272 | + overrides. | ||
| 273 | + | ||
| 274 | +So the workflow customisation pattern (schema extension + transition | ||
| 275 | +procs + custom audit) is **rare** — it's worth doing only when the | ||
| 276 | +customer's process genuinely doesn't fit a single-step approval and | ||
| 277 | +the standard framework's `bCheck` toggle isn't enough. Most customer | ||
| 278 | +divergence is calculation logic, not workflow shape. | ||
| 279 | + | ||
| 146 | The companion view `viw_salsaleschecking_pro.sql` exists for the same | 280 | The companion view `viw_salsaleschecking_pro.sql` exists for the same |
| 147 | reason — when the override needs a join shape the standard doesn't | 281 | reason — when the override needs a join shape the standard doesn't |
| 148 | provide, the engineer authors a customer-specific view, drops it into | 282 | provide, the engineer authors a customer-specific view, drops it into |