How to define a form
A recipe for the metadata rows that produce a working module + form in BACK and FROUNT. Use this when you want to add a new screen without writing Java.
What you'll insert
Three rows, in order, in three tables. Plus optional rows for fields, permissions, and document numbering.
1. The module — gdsmodule
One row registers the module's existence. Required columns:
| Column | Value |
|---|---|
sId |
unique ID — use the IdGen next-id helper, or any 32-char string not already taken |
sName |
the URL fragment, e.g. /indexPage/commonList (use a shared page-template URL — see Slice 3) |
sChinese / sEnglish / sBig5
|
display name in three languages |
sParentId |
parent module's sId — places this module in the menu tree |
sBrandsId / sSubsidiaryId
|
tenant scope — should be your tenant's IDs (or '1111111111' if standard / system-level) |
sVersionFlowId / sVersionFlowCode
|
product-edition catalogue tags (look up in sisversionflow where populated); live menu visibility is still gated by the licence-derived module list |
bVisible |
1 to show in the menu |
bInvalid |
0 for active |
Leave sSaveProName, sDeleteProName, sCalcProName, sProcName,
sSaveProNameBefore empty unless you need custom CRUD (see the runtime
reference).
2. The form-master — gdsconfigformmaster
One row per form per module. Required:
| Column | Value |
|---|---|
sId |
unique form ID |
sParentId |
the module's sId from step 1 |
sTbName |
the backing object's name — a real table, view, or proc |
sType |
'table', 'view', or 'proc'
|
sSqlStr |
the read SQL skeleton; usually SELECT ... FROM {sTbName} WHERE ...
|
sWhere |
default WHERE predicates (the runtime adds tenant filters automatically) |
sOrder |
default ORDER BY |
iPageSize |
rows per page in the grid |
bGrd |
1 if a grid layout, 0 if a single-record form |
sBrandsId / sSubsidiaryId
|
tenant scope |
For a read-only report, use sType = 'view' and point sTbName at a
viw_* view. The framework will not render
save/delete buttons.
3. The fields — gdsconfigformslave, one row per field
Most-used columns (the table has 60+, most have sane defaults):
| Column | Value |
|---|---|
sId |
unique field ID |
sParentId |
the form's sId from step 2 |
iOrder |
sort order in the grid |
sName |
the column name on the backing object |
sChinese / sEnglish / sBig5
|
display label |
sControlName |
UI control type — 文本框 (textbox), 下拉框 (dropdown), 日期 (date), 数字 (number), … |
bVisible |
1 to show |
bNotEmpty |
1 to require |
bReadonly |
1 for display-only |
bCanInput |
1 for editable |
iColValue |
column-span in form layout |
sDefault |
default value |
sChineseDropDown |
dropdown SQL (if applicable) — SELECT sId AS sValue, sChinese AS sText FROM …
|
sActiveId / sActiveKey
|
for popup-lookup controls — refers to another module |
Optional layers
Document numbering — sysbillnosettings
If the module's records need auto-generated bill numbers (work-order
numbers, quotation numbers, etc.), add a row here keyed by the form's
sId. The runtime returns this in the getModelBysId response under
billnosetting.
Permissions — gdsjurisdiction
See How to set permissions. One row per (module, button-or-data, role) tuple.
Tenant overrides
A tenant who wants a different view of this form inserts rows into
gdsconfigformpersonalize (form-level override) or
gdsconfigformcustomslave (field-level override). See
Slice 4.
Verifying
Once all rows are inserted, the SPA's next request to
getModelBysId/{your-module-sId} should return your form's metadata.
Click the new sidebar item — the form should render. If it doesn't:
- Check
bVisible = 1ongdsmodule. - Check that
gdsroutehas an entry for the URL (one is auto-added in most deployments). - Check
sBrandsId/sSubsidiaryIdon every row match the user's tenant. - Check that the form's
sParentIdmatches the module'ssIdexactly — semantic FK, no cross-row check at write time.
Cache invalidation
After inserting, the runtime's cache holds the previous (empty)
state until something clears it. When BACK saves the change, the save
service synchronously calls BusinessCleanRedisData.delCleanRedisData*,
which fires @CacheEvict on the relevant cache regions in
CleanRedisServiceImpl. If you're inserting via raw SQL, no eviction
runs — you'll need to either invoke BusinessCleanRedisDataImpl
methods directly from inside the application, bounce the running
services, or wait for the TTL to expire. (The JMS path with the
similarly-named ConsumerChangeGdsModuleThread does base-data merging
via stored proc, NOT cache invalidation despite the name — see
Cache invalidation on metadata change.)