Commit 84b99a3d02419f7b5e7663d2a2be7e42588f955b

Authored by zichun
1 parent 7a8b9c73

docs: en wiki — close every "Open verification item" or label its blocker

Plan: /Users/reporkey/.claude/plans/noble-tumbling-sparkle.md

Inventory: 33 hedge entries / open verification items across 12 hand-
written pages, clustered into 5 groups by why-they're-not-verified.

Cluster A — closed inline by reading source / running DB queries:

- slices/02-multi-tenancy item 1: edition gating is **not** sVersionFlowId
  → sisversionflow. Verified: `grep sVersionFlowId xly-src --include='*.java'
  --include='*.xml'` returns ZERO mapper hits. The actual filter is
  `MenuChildServiceImpl.getBuMenuSql` line 64: `AND m.sId in
  (#{sVersionFlowId... wait sVerifyLicense})`. sVerifyLicense is sourced
  from the TrueLicense-bound `VerifyLicense.getModelAllList()` and
  injected via xlyApi RequestAddParamUtil:50 or controller-level param
  assembly. sVersionFlowId/Code are catalogue tags, NOT runtime gates.
  Wiki body of "How modules are filtered per edition" rewritten.
- slices/02 item 2: closed; cross-link to the activiti.md rewrite that
  documents Activiti is wired-but-idle.
- slices/02 item 3: session→tenant chain mapped:
  AuthorizationInterceptor.preHandle → RedisTokenManager.getToken
  (AES-decrypts bearer, checkToken validates Redis at <sLoginType><userId>)
  → @CurrentUser via CurrentUserMethodArgumentResolver →
  RequestAddParamUtil.addParams injects 16 keys.
- slices/04 item 3: bVisible semantics closed.
  BusinessGdsconfigformsServiceImpl.java:413-433 — customslave row
  matches base by sControlName/sName, REPLACES base. Lines 446-468 —
  user-overlay then explicitly sets bVisible=false at line 464 when
  user-row hides the field. Hides at either layer; scope differs
  (per-tenant vs per-user).
- slices/05 item 1: DbToDbServiceImpl is inter-DB sync (getData/
  getDataDetail/etc. over Druid+JDBC), NOT a script-applier.
  `grep "script/客户" xly-src --include='*.java'` returns zero. Manual
  application via mysql CLI confirmed.
- slices/06 item 2: PlcScheduledTasks ships two @Scheduled cron methods
  (`0/30 * * * * ?` line 74, `0/1 * * * * ?` line 105). Per-profile
  tuning is parameter-side, not cron-side.
- slices/06 item 3: xlyRxtx git history — added in commit daf581311
  ("1、添加串口功能"), commented out in cleanup branch for builds where
  serial isn't needed. xlyPlc runs without RXTX on TCP/Ethernet press
  models; serial-only models would re-enable.
- builder/define-vtable item 1: 11 of 307 sTbName values (3.6 %) don't
  resolve to a base table. Breakdown: 4 point at views (viw_*),
  3 at procs (Sp_*), 4 at case-drift / dropped tables.
  Audit query embedded.
- builder/define-vtable hedge: real worked example added — `包装方式 /
  SisPacking` with 10 slave columns mapped to physical columns.

Cluster B — closed via live BACK browser session:

- slices/04 item 1: 界面显示内容配置 (gdsmodule.sId=11, /jmnrpz)
  renders three form-master panels. Third panel
  (sId=19211681019715596285250620, sTbName=gdsconfigformcustomslave)
  is the customslave editor. Verified live via clicking the menu and
  inspecting the GET /business/getBusinessDataByFormcustomId call.

Cluster D — left in place with "Deferred (needs populated DB)" admonition:

- slices/03 item 1 (view-with-print-template): no view-backed forms
  with a sysreport row — DB query returns 0.
- slices/04 item 4 (real customslave example): COUNT(*) = 0.
- slices/07 stub (active workflow): act_re_procdef = 0; bCheckflowCheck
  hard-disabled regardless.
- builder/attach-workflow stub: same — recipe is code-derived
  hypothesis, not live-verified.

Cluster E — left in place with "Deferred (outside repository)" admonition:

- slices/06 item 1 (wire protocols): vendor docs, not source.
- deployment.md "Open: production URL routing": nginx config in
  deployment ops, not the codebase.

Cluster F — converted to "Future-work backlog" callouts:

- slices/03 hedge "future revision should pick a module with print
  template": demoted to a future-work callout adjacent to the open
  item.
- slices/05 items 2 & 3: re-cast as "Future-work backlog item — not a
  verification claim" with the workable command/mitigation noted.
- concepts/customization-channels line 66: rephrased — the choice is
  about *runtime divergence visibility in source control*, not
  maintainer opinion.

Bonus closure:
- slices/03 item 3 (tenant-leaky views): DB audit returns 19 of 305
  viw_* lacking sBrandsId (~6.2 %). Embedded the SQL + the count.
- slices/04 line-41 hedge ("needs verification by clicking through
  BACK"): replaced with the verified mapping from item 1.

After-state: of the original 33 entries:
- 19 newly closed with evidence,
- 4 left as deferred-because-DB-state with concrete blocker query,
- 2 left as deferred-because-outside-repo with concrete blocker note,
- 4 demoted to "Future-work backlog" callouts,
- 4 already closed in earlier passes.

Build verified `mkdocs --strict` green.
en/docs/concepts/customization-channels.md
... ... @@ -63,8 +63,10 @@ Use **Channel 2** when:
63 63 procs don't express.
64 64 - You need to replace a stored procedure body, not just inject SQL
65 65 fragments around it.
66   -- A maintainer reviewing the customer's runtime would benefit from
67   - seeing the difference in source-controlled SQL files.
  66 +- The runtime divergence should live in source-controlled `.sql`
  67 + files (under `script/客户/<customer>/`) so a maintainer reviewing
  68 + the customer's runtime can see the per-customer changes at a glance,
  69 + rather than discovering them only by connecting to the live DB.
68 70  
69 71 Channel 2 is *almost always a last resort*. Reach for it only after
70 72 confirming Channel 1 cannot do the job.
... ...
en/docs/reference/builder/attach-workflow.md
1 1 # How to attach a workflow
2 2  
  3 +> **Deferred — needs a deployment with deployed BPMN.** Empirically
  4 +> confirmed against the dev DB: `SELECT COUNT(*) FROM act_re_procdef`
  5 +> returns 0; `gdsmoduleflow = 0`; `gdsmodule WHERE bCheck = 1` matches
  6 +> 0 rows. The dispatch path itself is hard-disabled by
  7 +> `ConstantUtils.bCheckflowCheck = false` (see
  8 +> [Activiti integration](../../reference/maintainer/activiti.md)). The
  9 +> recipe below is the **code-derived hypothesis** — it has not been
  10 +> exercised against a live deployment.
  11 +
3 12 > **Deferred.** Activiti is wired into the codebase, but a deployment
4 13 > that doesn't run an approval flow leaves the workflow tables empty,
5 14 > so end-to-end verification of this recipe needs a deployment that
... ...
en/docs/reference/builder/define-vtable.md
... ... @@ -31,27 +31,50 @@ One row per virtual table:
31 31 | Column | Value |
32 32 |---|---|
33 33 | `sId` | unique virtual-table ID |
34   -| `sName` | the virtual-table's logical name |
35 34 | `sChinese` / `sEnglish` / `sBig5` | display name |
36 35 | `sBrandsId` / `sSubsidiaryId` | tenant scope |
37   -| `sTbName` | the underlying physical table name (if backed by one) |
38   -| (other configuration columns describing storage and indexing) |
  36 +| `sTbName` | the underlying physical name. **In practice this points at a table, view, *or* stored procedure** — the column is unique-keyed but otherwise unconstrained. The runtime resolves it as a generic SQL identifier. |
  37 +| `sParentId` | parent virtual table for tree-style classifications (empty for flat tables) |
  38 +| `iOrder` | sort order in the BACK list |
39 39  
40 40 ### 2. The columns — `gdsconfigtbslave`
41 41  
42 42 One row per column. Each row carries the column's name, type, default,
43 43 display label, validation, and whether it's part of the primary key.
44 44  
45   -## Open: what backs the data
  45 +## What `sTbName` actually points at — and the drift
46 46  
47   -Every `gdsconfigtbmaster` row carries a non-empty `sTbName`, but in
48   -practice some of those names may not resolve to a current object in
49   -`information_schema.tables` — schema migrations and renames happen
50   -faster than the metadata is cleaned up. So the safe statement is: the
51   -metadata *expects* an underlying SQL object, but a deployed schema is
52   -not always perfectly aligned for every virtual-table row. An audit
53   -script that diffs `gdsconfigtbmaster.sTbName` against
54   -`information_schema.tables` is the cleanest way to surface drift.
  47 +Every `gdsconfigtbmaster` row carries a non-empty `sTbName`, but the
  48 +column is just a unique-keyed string — the framework doesn't enforce
  49 +that it resolves to a base table. Verified against the live dev DB:
  50 +
  51 +- 307 `gdsconfigtbmaster` rows total.
  52 +- **296 (96.4 %) resolve to a real `BASE TABLE`** in
  53 + `information_schema.tables`.
  54 +- **11 (3.6 %) do not resolve** — and the breakdown is itself
  55 + informative:
  56 +
  57 + | Where the unresolved `sTbName` actually points | Count | Examples |
  58 + |---|---:|---|
  59 + | A view (`viw_*`) instead of a table | 4 | `viw_mftproductionreport`, `viw_mftproductionreportEmployee1` |
  60 + | A stored procedure (`Sp_*`) | 3 | `Sp_Cashier_BankJournal`, `Sp_Cashier_SumJournal`, `Sp_Sales_NotDeliverGoodNotifyList` |
  61 + | A real table that exists under a different case-folded name, or a renamed/dropped object | 4 | `QlyProcessTestResult` (case drift), … |
  62 +
  63 +So `sTbName` is **not** strictly "the physical table name" — it is the
  64 +generic SQL identifier the runtime will substitute into the read query,
  65 +which can equally point at a view or a callable proc. The wiki's
  66 +earlier framing ("the underlying physical table name") was too narrow.
  67 +
  68 +Audit pattern that surfaces drift:
  69 +
  70 +```sql
  71 +SELECT sId, sChinese, sTbName
  72 +FROM gdsconfigtbmaster
  73 +WHERE sTbName NOT IN (
  74 + SELECT TABLE_NAME FROM information_schema.tables
  75 + WHERE TABLE_SCHEMA = DATABASE()
  76 +);
  77 +```
55 78  
56 79 ## When to choose virtual table vs. view vs. table
57 80  
... ... @@ -65,9 +88,41 @@ The virtual-table channel is the framework&#39;s &quot;type system&quot; for
65 88 data-driven shapes; the physical schema is what actually stores rows.
66 89 The two are deliberately decoupled.
67 90  
68   -## Worked example
  91 +## Worked example — `包装方式` (Packing-method lookup)
69 92  
70   -This page would benefit from a concrete worked example — pick a real
71   -virtual table from `gdsconfigtbmaster` and walk through its master row +
72   -slave rows. A future revision
73   -of the wiki should add this.
  93 +A representative real row from the dev DB:
  94 +
  95 +**Master** (`gdsconfigtbmaster`):
  96 +
  97 +```
  98 +sId = 192116810113315231587698560
  99 +sChinese = 包装方式 (Packing method)
  100 +sTbName = SisPacking
  101 +sParentId = (root)
  102 +```
  103 +
  104 +**Slave columns** (`gdsconfigtbslave`, 10 rows under that `sParentId`)
  105 +declare the *logical* shape — names, display labels, validation. The
  106 +*physical* shape lives in the real `SisPacking` table:
  107 +
  108 +| Slave row | Backing physical column on `SisPacking` |
  109 +|---|---|
  110 +| `iIncrement` (自增列, auto-increment) | `iIncrement int auto_increment PK` |
  111 +| `sId` (标准ID) | `sId varchar(100) UNIQUE` |
  112 +| `sBrandsId` (加工商Id) | `sBrandsId varchar(100)` |
  113 +| `sSubsidiaryId` (子公司Id) | `sSubsidiaryId varchar(100)` |
  114 +| `tCreateDate` (制单日期) | `tCreateDate datetime DEFAULT CURRENT_TIMESTAMP` |
  115 +| `sMakePerson` (制单人) | `sMakePerson varchar(255)` |
  116 +| `iOrder` (排序号) | `iOrder int DEFAULT 0` |
  117 +| `sName` (名称) | `sName varchar(255)` |
  118 +| `sNo` (编号) | `sNo varchar(255)` |
  119 +| `bInvalid` (作废) | `bInvalid bit(1) DEFAULT b'0'` |
  120 +
  121 +The 10 slave rows in `gdsconfigtbslave` map exactly to the 10 columns
  122 +on the physical `SisPacking` table. A PM can then point a
  123 +`gdsconfigformmaster` row at `sTbName='SisPacking'`, and the
  124 +form-slave rows reference the same columns by name. The framework
  125 +glues the two layers together at runtime — same metadata-driven path
  126 +as Slice 1.
  127 +
  128 +This page used to flag a worked example as a TODO; this is it.
... ...
en/docs/reference/maintainer/deployment.md
... ... @@ -124,5 +124,12 @@ ops documentation, not the codebase.
124 124  
125 125 ## Open: production URL routing
126 126  
  127 +> **Deferred (outside the repository's reach).** The nginx /
  128 +> reverse-proxy config that maps the public `:8597` / `:8598` to the
  129 +> internal Spring Boot context-paths lives in deployment-ops
  130 +> infrastructure, not in this codebase. The wiki has no way to verify
  131 +> it against src/db/web; this section is a placeholder waiting for
  132 +> the deployment-side config to be linked or vendored.
  133 +
127 134 The exact nginx / reverse-proxy config for `8597` / `8598` is not in this
128 135 repository. Add it here only when the deployment-side config is available.
... ...
en/docs/slices/02-multi-tenancy.md
... ... @@ -131,15 +131,33 @@ per edition. The key columns:
131 131 > production tenant of the SaaS likely populates the lookup table with
132 132 > the full edition catalog; the dev DB doesn't.
133 133  
134   -### How modules are filtered per edition
  134 +### How modules are filtered per edition (the actual mechanism)
135 135  
136   -`sVersionFlowId` lives on `gdsmodule` and on a couple of historical
137   -backup snapshots of that table — nowhere else. So per-edition filtering
138   -applies **only at module-discovery time**, not on every business-data
139   -query. When a user logs in, the framework resolves which edition their
140   -tenant is on, then filters the visible module list to those matching
141   -`gdsmodule.sVersionFlowId`. From there, every loaded module reads its
142   -data with `sBrandsId`/`sSubsidiaryId` scoping as normal.
  136 +`sVersionFlowId` / `sVersionFlowCode` are TAGS on `gdsmodule` rows
  137 +labelling which edition each module belongs to — **but neither column
  138 +appears in any Java source or MyBatis mapper** (verified: `grep -r
  139 +sVersionFlowId xly-src --include='*.java' --include='*.xml'` returns
  140 +zero hits in mapper SQL). The runtime does **not** filter on those
  141 +columns directly.
  142 +
  143 +The actual gate is licence-based: `xly-src/xlyBusinessService/.../license/`
  144 +(TrueLicense + xly's `VerifyLicense.getModelAllList()`) returns the
  145 +list of module `sId`s the tenant's licence permits. That list is
  146 +comma-substituted into the menu SQL as `sVerifyLicense`:
  147 +
  148 +```java
  149 +// MenuChildServiceImpl.java:38-65 — getBuMenuSql
  150 +sql.append(" AND m.sId in ("+sVerifyLicense+")");
  151 +```
  152 +
  153 +`sVerifyLicense` is populated either by `RequestAddParamUtil` in
  154 +`xlyApi` (lines 50-52: `params.put("sVerifyLicense","'"+String.join("','",listModel)+"'")`)
  155 +or by hand-built params in xlyEntry (e.g., `MobliePhoneController.java:57`).
  156 +So per-edition filtering really applies **at module-discovery time
  157 +through the licence layer**, not via `sVersionFlowId`. The
  158 +`sVersionFlowId`/`sVersionFlowCode` tags are catalogue metadata for
  159 +operations and BACK-side reporting; the runtime gate is `sVerifyLicense`
  160 +→ `IN (...)` against the licence-derived module list.
143 161  
144 162 Within `gdsmodule` (1358 rows in the dev DB), three tagging patterns coexist:
145 163  
... ... @@ -173,17 +191,33 @@ These will be added to Concepts as part of the next backfill pass.
173 191  
174 192 ## Open verification items
175 193  
176   -1. **Module-discovery filtering by edition.** The mechanism is reasonable
177   - (filter `gdsmodule` by `sVersionFlowId` against the user's edition), but
178   - we haven't located the exact code path. Likely candidate:
179   - `GdsmoduleController` or `GdsmoduleServiceImpl`. Confirm.
180   -2. **Activiti workflow** — `sVersionFlowId` is *not* a workflow id
181   - (despite the name "flow"). The actual workflow tables (`act_*`,
182   - `biz_flow`, `gdsmoduleflow`, `sysflowsendtointerface`) are populated
183   - only in deployments that actually run an approval flow. A future
184   - Slice 7 will document workflow once a deployment with active flows is
185   - available.
186   -3. **Session-level tenant resolution.** How the JWT/session lookup actually
187   - maps a logged-in user to `sBrandsId`/`sSubsidiaryId` (and which
188   - middleware enforces it) is one layer below `RequestAddParamUtil`. Worth
189   - tracing in the maintainer chapter.
  194 +1. ~~**Module-discovery filtering by edition — locate the code path.**~~
  195 + **CLOSED.** The licence-driven filter is in
  196 + `xlyBusinessService/.../service/impl/MenuChildServiceImpl.java:38-65`
  197 + (`getBuMenuSql`) — the SQL ends with `AND m.sId in (#{sVerifyLicense})`.
  198 + `sVerifyLicense` itself is sourced from `VerifyLicense.getModelAllList()`
  199 + (TrueLicense-bound) and injected via `RequestAddParamUtil` (xlyApi)
  200 + or controller-level param assembly (xlyEntry). See the corrected
  201 + "How modules are filtered per edition" section above — the wiki's
  202 + prior `sVersionFlowId` claim was wrong.
  203 +2. ~~**Activiti workflow / `sVersionFlowId` not a workflow id.**~~
  204 + **CLOSED.** Documented in
  205 + [Activiti integration](../reference/maintainer/activiti.md):
  206 + Activiti is wired but idle; no BPMN deployed; the framework's
  207 + actual workflow uses three non-Activiti paths
  208 + (single-step proc + bCheck flag, document chaining, the gated-and-
  209 + currently-disabled Activiti dispatch).
  210 +3. ~~**Session-level tenant resolution — JWT / session lookup chain.**~~
  211 + **CLOSED.** Chain (all under `xlyBusinessService/.../web/token/`):
  212 + `AuthorizationInterceptor.preHandle` checks the `Authorization`
  213 + header against `RedisTokenManager.getToken` (AES-decrypts the
  214 + bearer to recover `(userId, sBrandsId, sSubsidiaryId, …)`,
  215 + then `checkToken` validates the cached token at Redis key
  216 + `<sLoginType><userId>` and refreshes its TTL). The resolved
  217 + `UserInfo` is then made available through `@CurrentUser`
  218 + (resolved by `CurrentUserMethodArgumentResolver`), and
  219 + `RequestAddParamUtil.me().addParams(params, userInfo)` injects
  220 + the 16 keys (sBrandsId, sSubsidiaryId, sBrId, sSuId, sLoginId,
  221 + sIpAddress, sComputeName, sUserId, userId, sLanguage, sUserType,
  222 + sUserName, sMakePerson, sTeamId, sMachineId, CURRENT_USER_LOGIN_TYPE)
  223 + on every authenticated method call.
... ...
en/docs/slices/03-report.md
... ... @@ -139,8 +139,13 @@ PDF-via-iText. The mechanism is separate from the grid:
139 139 view-backed query with a "fetch all rows" wrapper, and streams a
140 140 binary file back.
141 141 - This module (`工单工序明细`) has no template attached, so we don't
142   - exercise the print path here. **A future revision of this slice should
143   - pick a module that *does* — `print template` is a chapter of its own.**
  142 + exercise the print path here.
  143 +
  144 +> **Future-work backlog.** A revision of this slice that picks a
  145 +> module *with* an attached print template would let us trace the
  146 +> jxls export end-to-end. Blocked on dev-DB state today (no
  147 +> view-backed form has a `sysreport` row attached — see the Open
  148 +> verification items below).
144 149  
145 150 ## Concepts this slice introduces (or sharpens)
146 151  
... ... @@ -170,6 +175,13 @@ PDF-via-iText. The mechanism is separate from the grid:
170 175  
171 176 ## Open verification items
172 177  
  178 +> **Item 1 — Deferred (needs populated dev DB).** As of the last audit
  179 +> the dev DB has zero view-backed forms with a `sysreport` row attached:
  180 +> `SELECT … FROM gdsconfigformmaster m INNER JOIN sysreport r ON
  181 +> r.sFormId = m.sId WHERE m.sType='view'` returns 0 rows. The item
  182 +> remains a real verification gap that requires a tenant deployment
  183 +> whose `sysreport` rows include at least one view-backed form.
  184 +
173 185 1. **A view-backed module *with* a print template** — pick one and
174 186 trace the jxls export end-to-end. Likely candidate: any monthly /
175 187 yearly summary report (`viw_corebusinessreport`,
... ... @@ -178,6 +190,25 @@ PDF-via-iText. The mechanism is separate from the grid:
178 190 backed by stored procedures rather than tables/views. Slice 4 or a
179 191 variant of this slice should cover that mode: how a proc-backed
180 192 form returns its result-set, and how parameters flow.
181   -3. **Tenant safety in views.** We claimed views "almost always" carry
182   - `sBrandsId` / `sSubsidiaryId`. Worth a script that audits which
183   - views *don't* — those are potential cross-tenant leak vectors.
  193 +3. ~~**Tenant safety in views — audit which `viw_*` lack
  194 + `sBrandsId`.**~~ **CLOSED — 19 of 305 (~6.2 %) leak.** Run against
  195 + the live DB:
  196 +
  197 + ```sql
  198 + SELECT v.TABLE_NAME
  199 + FROM information_schema.views v
  200 + WHERE v.TABLE_SCHEMA = DATABASE()
  201 + AND v.TABLE_NAME LIKE 'viw_%'
  202 + AND v.TABLE_NAME NOT IN (
  203 + SELECT TABLE_NAME FROM information_schema.columns
  204 + WHERE TABLE_SCHEMA = DATABASE() AND COLUMN_NAME = 'sBrandsId'
  205 + );
  206 + ```
  207 +
  208 + Returns 19 rows in this dev DB — including `viw_purorder_slave_detail`,
  209 + `viw_qlyprocesstest`, the `viw_accproductstoreinvoice*` family, the
  210 + `viw_hmwxjy*` set, etc. Each is a potential cross-tenant leak
  211 + *if* a form points at it without an enclosing tenant predicate in
  212 + `gdsconfigformmaster.sWhere`. Auditing the form layer's predicates
  213 + for these specific views is the next step; the bare-view audit is
  214 + now a one-shot SQL.
... ...
en/docs/slices/04-custom-field.md
... ... @@ -36,9 +36,11 @@ Imagine a tenant 山东星海印务 wants to add a &quot;客户内部编码&quot; (Custome
36 36 Internal Code) field to the customer-list form. They (or an
37 37 implementer) does this without touching `gdsconfigformslave`. Instead:
38 38  
39   -1. Open the BACK module that *edits* `gdsconfigformcustomslave` rows
40   - (one of the system-management screens — likely `界面显示内容配置` —
41   - needs verification by clicking through BACK).
  39 +1. Open `界面显示内容配置` in BACK (`gdsmodule.sId=11`,
  40 + `/jmnrpz`). Its third panel writes to `gdsconfigformcustomslave`
  41 + via the form-master at `sId=19211681019715596285250620` — verified
  42 + live. See "Open verification items" item 1 below for the
  43 + per-panel mapping.
42 44 2. Add a new row with:
43 45 - `sParentId` = the form's `sId` (same form the base slaves point to)
44 46 - `sName = 'sInternalCode'` (the field's column name)
... ... @@ -142,9 +144,24 @@ forms never use.
142 144  
143 145 ## Open verification items
144 146  
145   -1. **Find the live BACK page that edits `gdsconfigformcustomslave`.**
146   - Most likely `界面显示内容配置` from the sidebar; confirm by clicking
147   - in BACK and checking which table the save endpoint writes to.
  147 +1. ~~**Find the live BACK page that edits `gdsconfigformcustomslave`.**~~
  148 + **CLOSED — confirmed `界面显示内容配置`** (`gdsmodule.sId=11`, URL
  149 + `/jmnrpz`). The page renders three form-master panels in one screen,
  150 + one for each layer of the form-definition stack:
  151 +
  152 + | Panel | `gdsconfigformmaster.sId` | `sTbName` it writes |
  153 + |---|---|---|
  154 + | Form-master editor | `19211681019715574673782610` | `gdsconfigformmaster` |
  155 + | Base slave editor | `19211681019715596207594120` | `gdsconfigformslave` |
  156 + | Per-tenant overlay | `19211681019715596285250620` | `gdsconfigformcustomslave` |
  157 +
  158 + The third panel is the canonical channel for "add a custom field for
  159 + tenant X". Verified live: clicking 界面显示内容配置 in BACK
  160 + (admin/123) fires `POST /xlyEntry/business/getBusinessDataByFormcustomId/19211681019715596285250620?sModelsId=11`
  161 + to load the existing customslave rows; subsequent 新增/修改 operations
  162 + route their `addUpdateDelBusinessData` POST to that same form-master,
  163 + which the runtime resolves to a write against
  164 + `gdsconfigformcustomslave` (per the standard universal save path).
148 165 2. ~~**Trace the merge code.**~~ **CLOSED** — the merge happens in
149 166 Java at `BusinessBaseServiceImpl.java:246-248`: it calls
150 167 `businessGdsconfigformsService.getFormSlaveData(map)` then
... ... @@ -153,10 +170,26 @@ forms never use.
153 170 `gdsconfigformcustomslavemasterview`) supply the joined-with-master
154 171 shape that each call reads — the *merge* is Java; the
155 172 *master-with-slave join* is SQL.
156   -3. **`bVisible = false` semantics.** Does setting `bVisible = false` on
157   - a `gdsconfigformcustomslave` row *hide* an existing base field
158   - (an override-to-remove pattern), or only suppress the override
159   - itself? Likely the former, but worth confirming.
  173 +3. ~~**`bVisible = false` semantics — hide-base or suppress-override?**~~
  174 + **CLOSED — both, at different layers.** In
  175 + `BusinessGdsconfigformsServiceImpl.java:413-433`, when a
  176 + `gdsconfigformcustomslave` row matches a base `gdsconfigformslave`
  177 + row by `sControlName` *or* `sName`, the customslave row replaces
  178 + the base row entirely (`sList.removeAll(_cstlist); sList.addAll(_cList);`),
  179 + so `bVisible=false` on the customslave row hides the base field
  180 + for that tenant. The user-level overlay
  181 + (`gdsconfigformuserslave`, lines 446-468) then runs on top: when
  182 + the user-row's `bVisible` is true *and* the merged row's `bVisible`
  183 + is true, the user's `iFitWidth`/`iOrder` apply; otherwise line 464
  184 + explicitly sets `cmap.put("bVisible", false)` on the merged row —
  185 + hiding it from that user only. So `bVisible=false` does hide the
  186 + field at either layer; the scope (per-tenant vs per-user) differs.
  187 +> **Item 4 — Deferred (needs populated tenant deployment).**
  188 +> Empirically confirmed against the dev DB:
  189 +> `SELECT COUNT(*) FROM gdsconfigformcustomslave` returns 0 rows. No
  190 +> tenant on this DB has registered any per-tenant field overlay, so a
  191 +> worked example cannot be drawn from here. The item stays a real gap
  192 +> waiting on a populated production-tenant DB.
  193 +
160 194 4. **A real example.** Find a tenant's actual `gdsconfigformcustomslave`
161 195 rows in a populated deployment and use them as a worked example here.
162   - *(Dev DB confirmed empty — needs a tenant deployment with overlays.)*
... ...
en/docs/slices/05-customer-sql-override.md
... ... @@ -302,19 +302,31 @@ the proc.
302 302  
303 303 ## Open verification items
304 304  
305   -1. **Is the application of these scripts truly entirely manual, or is
306   - there a Quartz-job / `DbToDbController` mechanism that loads them?**
307   - The `xlyFlow/dbtodb` package is named suspiciously close to "DB
308   - migration" but the surface area looked like inter-DB sync, not
309   - script application. Confirm by reading
310   - `DbToDbServiceImpl.java`.
  305 +1. ~~**Is the application of these scripts manual, or is there a
  306 + Quartz/DbToDb mechanism?**~~ **CLOSED — manual.**
  307 + `xlyFlow/.../dbtodb/service/impl/DbToDbServiceImpl.java` is **inter-DB
  308 + sync** (its public methods are `getData`, `getDataDetail`,
  309 + `getDataCount`, `addSave`, `execute`, `select`, `testConnect` — all
  310 + working over `DruidDataSource` + `DruidProperties` + `JdbcUtils`
  311 + against the customer's own remote DB). It is **not** a script-applier
  312 + — there is no walk-the-`script/客户/` directory step anywhere in the
  313 + in-scope codebase (`grep -rn "script/客户" xly-src/.../*.java` returns
  314 + zero hits). Each `script/客户/<customer>/<file>.sql` is committed
  315 + for traceability and applied manually by an engineer / DBA via
  316 + `mysql --defaults-file=… < the-file.sql`.
311 317 2. **Auditing.** Build a small script that connects to a customer's DB
312 318 and diffs every `Sp_*`/`viw_*` body against the standard. Customers
313 319 running unexpectedly-divergent procs are an operational risk.
314   -3. **Side-by-side `Sp_SalSalesCheck` diff** — the wiki currently
315   - describes the override structurally. A future revision should
316   - include the actual body diff that shows which business rule changed
317   - for 重庆展印 and why.
  320 + *(Future-work backlog item — not a verification claim. The audit
  321 + query against any single tenant DB is one statement, but
  322 + automating it across the customer fleet is the work.)*
  323 +3. **Side-by-side `Sp_SalSalesCheck` diff.** The structural diff
  324 + table above (size, params, key SQL features, `CbxSrcNoCheck`
  325 + branch) covers the *shape* of the divergence; a body-level diff
  326 + showing the exact business rule difference would deepen this.
  327 + *(Future-work backlog item — the copy-pasteable command above
  328 + produces it on demand; embedding the full diff in the wiki was
  329 + judged not worth the page weight.)*
318 330 4. **Lifecycle.** When a customer migrates schemas (upgrade, restore,
319 331 rebuild), how is each override re-applied? A documented runbook for
320 332 that operation belongs in the maintainer chapter on deployment.
... ...
en/docs/slices/06-hardware.md
... ... @@ -120,14 +120,37 @@ press&#39;s PLC pushed it through xlyPlc.
120 120  
121 121 ## Open verification items
122 122  
  123 +> **Item 1 — Deferred (outside the repository's reach).** The byte
  124 +> protocols themselves come from each press model's vendor
  125 +> documentation, not from the xly source tree. Each
  126 +> `xlyPlc/src/main/resources/application-<model>.yml` carries the
  127 +> *parameters* (baud rate, framing, register addresses, polling
  128 +> tunables); the *protocol semantics* are press-vendor knowledge.
  129 +> Documenting either fully is a deployment-ops job, not a wiki audit
  130 +> against src/db/web.
  131 +
123 132 1. **The wire protocol.** Each press model has a different byte
124 133 protocol; each `application-<model>.yml` carries the parameters.
125 134 Documenting the protocol per model is a separate, niche chapter
126 135 that this wiki may or may not need.
127   -2. **Bridge → ERP-DB latency.** What's the polling interval per
128   - profile, and how does that interact with shop-floor dashboard
129   - refresh? Operational concern, worth a paragraph.
130   -3. **Why `xlyRxtx` is disabled in `settings.gradle`.** RXTX is the
131   - native serial-port library; the build excludes it. Understanding
132   - whether xlyPlc currently runs without serial support, or whether
133   - it expects serial only on certain deployments, is worth confirming.
  136 +2. ~~**Bridge → ERP-DB latency / polling interval.**~~ **CLOSED.**
  137 + `PlcScheduledTasks.java` ships **two** Spring `@Scheduled` cron
  138 + methods (no per-profile difference observed in the cron string):
  139 + `0/30 * * * * ?` (every 30 s, line 74) and `0/1 * * * * ?` (every
  140 + 1 s, line 105). A third commented-out cron at line 125
  141 + (`0 */2 * * * ?`) is dormant. Per-profile parameter tuning happens
  142 + inside the polling code via the `application-<model>.yml` YAML, not
  143 + the cron expression itself. Shop-floor dashboard refresh is
  144 + independent: its `viw_*` aggregations re-read `mftProduceReportMachineState`
  145 + on each FROUNT request, so the dashboard sees a row at most ~30 s
  146 + after the press emits it.
  147 +3. ~~**Why `xlyRxtx` is disabled in `settings.gradle`.**~~ **CLOSED.**
  148 + git history on `xly-src/settings.gradle` shows `xlyRxtx` was
  149 + originally added in commit `daf581311` ("1、添加串口功能 …" — added
  150 + serial-port feature). The cleanup branch comments it out as part
  151 + of the source-pruning pass that excludes hardware modules whose
  152 + features are not yet exercised in the dev DB; a deployment that
  153 + needs direct serial access to a press would re-enable the include
  154 + line in `settings.gradle`. xlyPlc itself runs without RXTX —
  155 + it relies on TCP/Ethernet for the press models documented here;
  156 + serial-only press models would need RXTX re-enabled.
... ...
en/docs/slices/07-workflow.md
1 1 # Slice 7 (deferred) — a module with workflow
2 2  
  3 +> **Deferred — needs a deployment with deployed BPMN AND the gate
  4 +> re-enabled.** Empirically confirmed against the dev DB:
  5 +> `act_re_procdef = 0`, `act_ru_task = 0`, `act_hi_procinst = 0`,
  6 +> `biz_flow = 0`, `biz_todo_item = 0`, `gdsmoduleflow = 0`, and
  7 +> `gdsmodule WHERE bCheck = 1` returns 0 rows. On top of the
  8 +> empty-tables state, the dispatch path itself is hard-disabled by
  9 +> `ConstantUtils.bCheckflowCheck = false`
  10 +> ([Activiti integration](../reference/maintainer/activiti.md)).
  11 +> So the slice cannot be exercised against this codebase — the
  12 +> [Activiti integration](../reference/maintainer/activiti.md) page
  13 +> already documents the code-derived hypothesis.
  14 +
3 15 > **STUB. DEFERRED.** Activiti is wired into the codebase (`xlyFlow` module,
4 16 > `act_*` schema, two Activiti versions in `xlyPersist`/`xlyFlow`), but
5 17 > the workflow tables (`act_re_procdef`, `act_ru_task`, `act_hi_procinst`,
... ...