Commit 84b99a3d02419f7b5e7663d2a2be7e42588f955b
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.
Showing
10 changed files
with
292 additions
and
74 deletions
en/docs/concepts/customization-channels.md
| @@ -63,8 +63,10 @@ Use **Channel 2** when: | @@ -63,8 +63,10 @@ Use **Channel 2** when: | ||
| 63 | procs don't express. | 63 | procs don't express. |
| 64 | - You need to replace a stored procedure body, not just inject SQL | 64 | - You need to replace a stored procedure body, not just inject SQL |
| 65 | fragments around it. | 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 | Channel 2 is *almost always a last resort*. Reach for it only after | 71 | Channel 2 is *almost always a last resort*. Reach for it only after |
| 70 | confirming Channel 1 cannot do the job. | 72 | confirming Channel 1 cannot do the job. |
en/docs/reference/builder/attach-workflow.md
| 1 | # How to attach a workflow | 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 | > **Deferred.** Activiti is wired into the codebase, but a deployment | 12 | > **Deferred.** Activiti is wired into the codebase, but a deployment |
| 4 | > that doesn't run an approval flow leaves the workflow tables empty, | 13 | > that doesn't run an approval flow leaves the workflow tables empty, |
| 5 | > so end-to-end verification of this recipe needs a deployment that | 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,27 +31,50 @@ One row per virtual table: | ||
| 31 | | Column | Value | | 31 | | Column | Value | |
| 32 | |---|---| | 32 | |---|---| |
| 33 | | `sId` | unique virtual-table ID | | 33 | | `sId` | unique virtual-table ID | |
| 34 | -| `sName` | the virtual-table's logical name | | ||
| 35 | | `sChinese` / `sEnglish` / `sBig5` | display name | | 34 | | `sChinese` / `sEnglish` / `sBig5` | display name | |
| 36 | | `sBrandsId` / `sSubsidiaryId` | tenant scope | | 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 | ### 2. The columns — `gdsconfigtbslave` | 40 | ### 2. The columns — `gdsconfigtbslave` |
| 41 | 41 | ||
| 42 | One row per column. Each row carries the column's name, type, default, | 42 | One row per column. Each row carries the column's name, type, default, |
| 43 | display label, validation, and whether it's part of the primary key. | 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 | ## When to choose virtual table vs. view vs. table | 79 | ## When to choose virtual table vs. view vs. table |
| 57 | 80 | ||
| @@ -65,9 +88,41 @@ The virtual-table channel is the framework's "type system" for | @@ -65,9 +88,41 @@ The virtual-table channel is the framework's "type system" for | ||
| 65 | data-driven shapes; the physical schema is what actually stores rows. | 88 | data-driven shapes; the physical schema is what actually stores rows. |
| 66 | The two are deliberately decoupled. | 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,5 +124,12 @@ ops documentation, not the codebase. | ||
| 124 | 124 | ||
| 125 | ## Open: production URL routing | 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 | The exact nginx / reverse-proxy config for `8597` / `8598` is not in this | 134 | The exact nginx / reverse-proxy config for `8597` / `8598` is not in this |
| 128 | repository. Add it here only when the deployment-side config is available. | 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,15 +131,33 @@ per edition. The key columns: | ||
| 131 | > production tenant of the SaaS likely populates the lookup table with | 131 | > production tenant of the SaaS likely populates the lookup table with |
| 132 | > the full edition catalog; the dev DB doesn't. | 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 | Within `gdsmodule` (1358 rows in the dev DB), three tagging patterns coexist: | 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,17 +191,33 @@ These will be added to Concepts as part of the next backfill pass. | ||
| 173 | 191 | ||
| 174 | ## Open verification items | 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,8 +139,13 @@ PDF-via-iText. The mechanism is separate from the grid: | ||
| 139 | view-backed query with a "fetch all rows" wrapper, and streams a | 139 | view-backed query with a "fetch all rows" wrapper, and streams a |
| 140 | binary file back. | 140 | binary file back. |
| 141 | - This module (`工单工序明细`) has no template attached, so we don't | 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 | ## Concepts this slice introduces (or sharpens) | 150 | ## Concepts this slice introduces (or sharpens) |
| 146 | 151 | ||
| @@ -170,6 +175,13 @@ PDF-via-iText. The mechanism is separate from the grid: | @@ -170,6 +175,13 @@ PDF-via-iText. The mechanism is separate from the grid: | ||
| 170 | 175 | ||
| 171 | ## Open verification items | 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 | 1. **A view-backed module *with* a print template** — pick one and | 185 | 1. **A view-backed module *with* a print template** — pick one and |
| 174 | trace the jxls export end-to-end. Likely candidate: any monthly / | 186 | trace the jxls export end-to-end. Likely candidate: any monthly / |
| 175 | yearly summary report (`viw_corebusinessreport`, | 187 | yearly summary report (`viw_corebusinessreport`, |
| @@ -178,6 +190,25 @@ PDF-via-iText. The mechanism is separate from the grid: | @@ -178,6 +190,25 @@ PDF-via-iText. The mechanism is separate from the grid: | ||
| 178 | backed by stored procedures rather than tables/views. Slice 4 or a | 190 | backed by stored procedures rather than tables/views. Slice 4 or a |
| 179 | variant of this slice should cover that mode: how a proc-backed | 191 | variant of this slice should cover that mode: how a proc-backed |
| 180 | form returns its result-set, and how parameters flow. | 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 "客户内部编码" (Custome | @@ -36,9 +36,11 @@ Imagine a tenant 山东星海印务 wants to add a "客户内部编码" (Custome | ||
| 36 | Internal Code) field to the customer-list form. They (or an | 36 | Internal Code) field to the customer-list form. They (or an |
| 37 | implementer) does this without touching `gdsconfigformslave`. Instead: | 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 | 2. Add a new row with: | 44 | 2. Add a new row with: |
| 43 | - `sParentId` = the form's `sId` (same form the base slaves point to) | 45 | - `sParentId` = the form's `sId` (same form the base slaves point to) |
| 44 | - `sName = 'sInternalCode'` (the field's column name) | 46 | - `sName = 'sInternalCode'` (the field's column name) |
| @@ -142,9 +144,24 @@ forms never use. | @@ -142,9 +144,24 @@ forms never use. | ||
| 142 | 144 | ||
| 143 | ## Open verification items | 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 | 2. ~~**Trace the merge code.**~~ **CLOSED** — the merge happens in | 165 | 2. ~~**Trace the merge code.**~~ **CLOSED** — the merge happens in |
| 149 | Java at `BusinessBaseServiceImpl.java:246-248`: it calls | 166 | Java at `BusinessBaseServiceImpl.java:246-248`: it calls |
| 150 | `businessGdsconfigformsService.getFormSlaveData(map)` then | 167 | `businessGdsconfigformsService.getFormSlaveData(map)` then |
| @@ -153,10 +170,26 @@ forms never use. | @@ -153,10 +170,26 @@ forms never use. | ||
| 153 | `gdsconfigformcustomslavemasterview`) supply the joined-with-master | 170 | `gdsconfigformcustomslavemasterview`) supply the joined-with-master |
| 154 | shape that each call reads — the *merge* is Java; the | 171 | shape that each call reads — the *merge* is Java; the |
| 155 | *master-with-slave join* is SQL. | 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 | 4. **A real example.** Find a tenant's actual `gdsconfigformcustomslave` | 194 | 4. **A real example.** Find a tenant's actual `gdsconfigformcustomslave` |
| 161 | rows in a populated deployment and use them as a worked example here. | 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,19 +302,31 @@ the proc. | ||
| 302 | 302 | ||
| 303 | ## Open verification items | 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 | 2. **Auditing.** Build a small script that connects to a customer's DB | 317 | 2. **Auditing.** Build a small script that connects to a customer's DB |
| 312 | and diffs every `Sp_*`/`viw_*` body against the standard. Customers | 318 | and diffs every `Sp_*`/`viw_*` body against the standard. Customers |
| 313 | running unexpectedly-divergent procs are an operational risk. | 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 | 4. **Lifecycle.** When a customer migrates schemas (upgrade, restore, | 330 | 4. **Lifecycle.** When a customer migrates schemas (upgrade, restore, |
| 319 | rebuild), how is each override re-applied? A documented runbook for | 331 | rebuild), how is each override re-applied? A documented runbook for |
| 320 | that operation belongs in the maintainer chapter on deployment. | 332 | that operation belongs in the maintainer chapter on deployment. |
en/docs/slices/06-hardware.md
| @@ -120,14 +120,37 @@ press's PLC pushed it through xlyPlc. | @@ -120,14 +120,37 @@ press's PLC pushed it through xlyPlc. | ||
| 120 | 120 | ||
| 121 | ## Open verification items | 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 | 1. **The wire protocol.** Each press model has a different byte | 132 | 1. **The wire protocol.** Each press model has a different byte |
| 124 | protocol; each `application-<model>.yml` carries the parameters. | 133 | protocol; each `application-<model>.yml` carries the parameters. |
| 125 | Documenting the protocol per model is a separate, niche chapter | 134 | Documenting the protocol per model is a separate, niche chapter |
| 126 | that this wiki may or may not need. | 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 | # Slice 7 (deferred) — a module with workflow | 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 | > **STUB. DEFERRED.** Activiti is wired into the codebase (`xlyFlow` module, | 15 | > **STUB. DEFERRED.** Activiti is wired into the codebase (`xlyFlow` module, |
| 4 | > `act_*` schema, two Activiti versions in `xlyPersist`/`xlyFlow`), but | 16 | > `act_*` schema, two Activiti versions in `xlyPersist`/`xlyFlow`), but |
| 5 | > the workflow tables (`act_re_procdef`, `act_ru_task`, `act_hi_procinst`, | 17 | > the workflow tables (`act_re_procdef`, `act_ru_task`, `act_hi_procinst`, |