# Modules, forms, virtual tables

The three core nouns of xly's metadata model. A reader who internalises
these can navigate the rest of the framework. The vocabulary is not always
consistent in the codebase — same concept, different names in different
files — so part of this page is reconciling the synonyms.

## Module

The unit of "a configurable thing in the system".

- **One row per module** in [`gdsmodule`](../auto-catalog/tables/gdsmodule.md).
- Carries the URL fragment (`sName`), display names (`sChinese`,
  `sEnglish`, `sBig5`), tree position (`sParentId`, `sAllId`), product
  edition (`sVersionFlowId`, see [Slice 2](../slices/02-multi-tenancy.md)),
  optional CRUD procedure names (`sSaveProName`, `sDeleteProName`,
  `sProcName`, `sCalcProName`, `sSaveProNameBefore`).

The Java codebase calls this concept "module", "model", "models", and
"modle" (typo, common). The variable `sModelsId` and method
`getModelConfigByModleId` both take a *module* `sId`, despite the naming.
See the naming caution in [Slice 1](../slices/01-hello-world.md).

## Form

The screen layout for a module — header + N field definitions.

- **Master:** one row per form in
  [`gdsconfigformmaster`](../auto-catalog/tables/gdsconfigformmaster.md).
  Carries the backing object (`sTbName`), backing-object type
  (`sType` ∈ `{table, view, proc}`), default SQL fragments
  (`sSqlStr`, `sWhere`, `sOrder`), grid behaviour (`bGrd`, `iPageSize`),
  jurisdiction column.
- **Slave:** one row per *field* in
  [`gdsconfigformslave`](../auto-catalog/tables/gdsconfigformslave.md).
  Carries the field name (`sName` — matches a column in the backing
  object), control type (`sControlName`), display labels, validation
  rules, default value, dropdown SQL, button instructions.
- A module is linked to its form via `gdsconfigformmaster.sParentId =
  gdsmodule.sId` — semantic FK, no enforcement. (Note: `gdsmodule.sFormId`
  exists but is empty for most modules and is *not* the canonical link.)

The form is the user-visible artefact: when a PM "creates a screen" in
BACK, what they're really doing is INSERTing a `gdsconfigformmaster` row
plus 5-50 `gdsconfigformslave` rows.

## Virtual table

xly's term (and table prefix) for a "table" that is itself defined as
metadata, not as a `CREATE TABLE` statement.

- One row per virtual table in
  [`gdsconfigtbmaster`](../auto-catalog/tables/gdsconfigtbmaster.md).
- One row per virtual column in
  [`gdsconfigtbslave`](../auto-catalog/tables/gdsconfigtbslave.md).

Virtual tables are how PMs declare "I want this kind of thing" without
asking an engineer. Most virtual tables back to a real physical table
(the framework supports the migration step, or it pre-creates a generic
"wide" table the PM populates), but the *abstraction* the runtime sees
is the metadata declaration, not the underlying storage.

This is distinct from a database **view**: a virtual table is metadata; a
view is a `CREATE VIEW` SQL object. Both can back a form (via
`gdsconfigformmaster.sType = 'table'` or `'view'`). See [Slice 3](../slices/03-report.md)
for the view-backed case.

## How they fit together

```
gdsmodule  (module, 1 row)
   └── (joined via sParentId)
       gdsconfigformmaster  (form-master, 1 row per form)
          ├── gdsconfigformslave  (fields, N rows)
          ├── gdsconfigformcustomslave  (per-tenant fields, N or 0)
          └── gdsconfigformuserslave    (per-user tweaks, N or 0)

gdsmodule.sName   →  registered route in  gdsroute  (URL whitelist)
gdsmodule.sFormId →  (mostly empty — historical)
```

Every form has exactly one module. Every module *should* have at most one
form, but a few have several `gdsconfigformmaster` rows pointing at the
same `sParentId` — those represent screens with multiple panels or
sub-tabs.

## Three nouns, one engine

The runtime — `BusinessBaseController` and `BusinessBaseServiceImpl`,
documented in [Slice 1](../slices/01-hello-world.md) — handles every
module / form / virtual-table combination through one universal
dispatch path. There is no per-module Java; PMs creating new modules
are creating new rows.

The flip side: that one engine has accumulated 3,900+ lines in
`BusinessBaseServiceImpl` alone, plus another 600+ in
`BusinessGdsconfigformsServiceImpl`. Edge cases, special-case
table handling (e.g., the `mftproductionplanslave` hardcode at
`BusinessBaseServiceImpl.java:1768`), per-tenant overlay merge
logic, and the multi-tenant scope-bypass list all live in this
single class. Adding a new feature that the universal dispatch
doesn't handle means either expanding this class or writing
custom code that bypasses it — both of which erode the "one
engine handles everything" property the design promised.

## Business-data table prefixes

The wiki treats business modules as illustrations rather than subjects,
but the schema names them in a regular pattern. A maintainer can map
any business-data table to its domain by the three-letter prefix:

| Prefix | Domain | Sample tables (live count) |
|---|---|---|
| `gds` | Framework metadata (modules, forms, fields, permissions, parameters, charts) | `gdsmodule`, `gdsconfigformmaster`, `gdsconfigformslave`, `gdsjurisdiction`, `gdsroute`, `gdsformconst`, `gdsparameter`, `gdsconfigcharmaster`/`slave` (chart definitions used by the BACK 图表配置 screen), … |
| `sys` | Framework system (numbering, jurisdiction grants, reports, search, billing settings) — distinct from `gds*` "definition" tier | `sysjurisdiction`, `sysbillnosettings`, `sysreport`, `syssearch`, `sysapi`, `syssystemsettings`, … (68 tables) |
| `sis` | Shared lookup tables / classifiers backing dropdowns | `sisbank`, `siscolor`, `sisversionflow`, `sisjurisdictionclassify`, … (80 tables) |
| `sft` | Login-session / group-permission link tables | `sftlogininfo`, `sftlogininfojurisdictiongroup`, … (8 tables) |
| `ele` | Master data ("element"): customer, employee, machine, materials, product, process, semigoods, costframe | `elecustomer*`, `eleemployee*`, `elemachine*`, `elematerials*`, `eleproduct*`, … (89 tables) |
| `mft` | Manufacturing: work-order, production-plan, production-report | `mftworkordermaster`, `mftproductionplan*`, `mftproductionreport*`, … (82 tables) |
| `sal` | Sales | `salsalesordermaster`, `salsalesorderslave`, `salsalesorderprocess`, … (67 tables) |
| `quo` | Quotation | `quoquotationmaster`, `quoquotationslave`, `quoquotationcalc_tmp`, … (23 tables) |
| `acc` | Accounting | `accordercostanalysis`, `accordercostanalysisoperation`, … (31 tables) |
| `pur` | Purchasing | `purpurchaseapply`, `purpurchasearrive`, `purpurchasechecking`, … (28 tables) |
| `ops` | Outside-processing / outsourcing | `opsoutsidearrive`, `opsoutsidechecking`, `opsoutsideinstore`, … (23 tables) |
| `cah` | Cashier / financial | `cahcashierinit`, `cahcostchangemaster`, `cahpaymentmaster`, `cahreceiptmaster`, … (22 tables) |
| `sgd` | Semi-goods (半成品) | `sgdsemigoodscheck`, `sgdsemigoodsinstore`, `sgdsemigoodsmatchbill`, … (21 tables) |
| `ept` | Equipment / machine fixed assets | `eptmachinefixedborrow`, `eptmachinefixedchange`, `eptmachinefixedinstore`, … (21 tables) |
| `mit` | Materials inventory transactions | `mitmaterialsadjust`, `mitmaterialscheck`, `mitmaterialsinstore`, … (19 tables) |
| `pit` | Product inventory transactions | `pitproductadjust`, `pitproductbarcode`, `pitproductcheck`, `pitproductinstore`, … (18 tables) |
| `qly` | Quality testing | `qlycomematerialstest`, `qlyproducttest`, `qlyprocesstest`, … (8 tables) |
| `kpi` | KPI tracking | `kpimaster`, `kpidetail`, `kpimoduleuserday`, … (7 tables) |
| `udf` | User-defined / generic-voucher framework | `udfaccountno`, `udfvouchermaster`, `udfvouchertemplatemaster`, … (5 tables) |
| `viw_` / `Viw_` | Database **views** (case inconsistent across schema) | `viw_mftworkorderprocess`, `viw_corebusinessreport`, `viw_accordercostanalysisnew`, … (311 views in total) |
| `plat_` | B2B printing-platform layer (out of scope per [index](../index.md#whats-out-of-scope)) | 92 tables — not documented here |
| `ai_` | AI / LLM features (out of scope) | 7 tables — not documented here |
| `act_`, `qrtz_` | Third-party schemas (Activiti workflow, Quartz scheduler) | covered transitively under [Activiti](../reference/maintainer/activiti.md) and [tech-stack Quartz](../reference/maintainer/tech-stack.md#4-workflow-scheduling) |

The business-domain prefixes (`ele`, `mft`, `sal`, `quo`, `acc`, `pur`,
`ops`, `cah`, `sgd`, `ept`, `mit`, `pit`, `qly`, `kpi`, `udf`) and
their slaves all follow the same metadata-driven runtime — there is no
per-prefix Java code, just rows in `gdsconfigformmaster` /
`gdsconfigformslave` pointing at each backing table or view.
