Slice 3 — a module backed by a view (a read-only report)
Slice 1 traced a CRUD module — a form that reads and writes one table. Slice 3 takes the next step: a module that reads from a database view, joining several base tables, and presents the joined result as a sortable, filterable grid. No save, no delete — read-only.
This is the foundation of every "list view" in xly: order summaries, daily
production reports, stock snapshots, KPI dashboards. The framework treats
view-backed modules through the same metadata pipeline as table-backed
modules, with a single difference: gdsconfigformmaster.sType = 'view'
suppresses the write operations.
What we're documenting
| Module |
工单工序明细 (Work-Order Process Details) |
| URL fragment |
/indexPage/commonList (a generic "list-only" page template)
|
Module sId |
19211681019715708462888040 |
| Backing object |
viw_mftworkorderprocess — a database view, not a base table |
| Field count | 40 |
| Write procs | None (read-only by design) |
| Print template attached | None — pure grid |
The view it reads from joins the work-order master/slave family
(mftworkordermaster, mftworkorderslave, …) into a flat per-row
representation. That the view exists is itself part of xly's
report-rendering convention: instead of rebuilding the join in every
read query, xly authors a database-side CREATE VIEW and points one
metadata row at it.
Why this module
It's the simplest member of the most common operational pattern in xly: view-backed list pages. There are 405 view-backed forms in this dev DB (20% of all forms) — second only to table-backed forms. Once you've internalised this slice, every other operational report is a variant.
It's also a counter-example to two things Slice 1 might have made you believe:
- That every module reads from a single table — view-backed modules read joins.
- That every URL is a unique pinyin path.
/indexPage/commonListis a shared template URL: many different view-backed modules answer at/indexPage/commonList. The URL doesn't identify the module — thesModelsIdparameter on the API calls does.
The trace
1. Page shell
Same as Slice 1: navigating to /indexPage/commonList lands the SPA shell;
the SPA then resolves which module to load via its sidebar / menu state.
The URL is display state, not a route driver.
2. Metadata fetch
GET /xlyEntry/business/getModelBysId/19211681019715708462888040?sModelsId=19211681019715708462888040
Same handler as Slice 1
(BusinessBaseController.java:63-69). Same five-key composite response:
formData, gdsformconst, gdsjurisdiction, billnosetting, report.
The report key is empty here — this module has no print template
attached (verified: SELECT COUNT(*) FROM sysreport WHERE sFormId IN
(forms-of-this-module) returned 0).
The crucial difference from Slice 1 is in formData. The form-master row
has:
sType = 'view'
sTbName = 'viw_mftworkorderprocess'
sSqlStr = (typically a SELECT … FROM viw_mftworkorderprocess … skeleton)
sWhere/sOrder = (default predicates)
The sType = 'view' bit is the framework's only signal that this module
is read-only. The metadata structure is otherwise identical to a CRUD
module's.
3. Grid data fetch
POST /xlyEntry/business/getBusinessDataByFormcustomId/{formId}?sModelsId={moduleId}
The same endpoint as Slice 1. The handler doesn't care whether the
backing object is a table or a view — it just substitutes sTbName into
the parameterised SQL composed from sSqlStr + sWhere + sOrder. The
view does the heavy lifting of the join; the framework adds tenant
filters and pagination.
Why a view and not a runtime JOIN? xly authors the join in SQL DDL (
CREATE VIEW) so the framework's runtime SQL stays a flatSELECT … FROM <one-name> WHERE …. This makes it easy to plug different "shape" sources (table, view, proc) into the same form machinery without the runtime caring which it is. The trade is a proliferation of named views — 311 in this dev DB.
4. The view itself
viw_mftworkorderprocess joins the work-order master with the per-process
slave detail. Looking at the auto-catalog page for this view
gives you the full CREATE VIEW definition.
Naming convention: views in xly are prefixed viw_ or Viw_ (case
inconsistent across the schema). Some join-views are very wide (40+
columns is common). They almost always carry sBrandsId /
sSubsidiaryId from their primary base table so the framework's
universal tenant filter still applies — Slice 2's tenant boundary
extends through views unmodified.
5. Saving — there is none
For sType = 'view' the framework does not offer Add/Update/Delete
buttons. The MyBatis side of addUpdateDelBusinessData would technically
attempt to write to a view if asked, but the framework doesn't render
the buttons for it. (A maintainer wanting to make a particular view
updatable would either point the form at a base table and use a custom
save proc, or rely on MySQL's automatically-updatable-view behaviour —
this module does neither.)
6. Printable reports (when present)
Some view-backed modules do have print templates — Excel-via-jxls and PDF-via-iText. The mechanism is separate from the grid:
-
getModelBysIdreturns thereportarray, populated fromsysreportrows linked to the form viasFormId. - The frontend's "打印" / "导出" buttons hit
xlyEntry/com/xly/report/controllers (PrintReportController,PrintReportControllerOld), which load a jxls / iText template, run the same view-backed query with a "fetch all rows" wrapper, and stream a binary file back. - This module (
工单工序明细) has no template attached, so we don't exercise the print path here. A future revision of this slice should pick a module that does —print templateis a chapter of its own.
Concepts this slice introduces (or sharpens)
-
View-backed modules (new concept page):
gdsconfigformmaster.sType = 'view'makes a module read-only; the rest of the metadata flow is identical. -
The "shared template URL" pattern —
/indexPage/commonList,/indexPage/commonBill,/indexPage/commonClassify,/indexPage/commonNewBillare each used by hundreds of modules. The URL is a page-shape selector; module identity comes from thesModelsIdquery param, not the path. -
Report templates (preview only — full coverage in a future slice
pass):
sysreportlinked viasFormId, jxls/iText templates served byPrintReportController.
Reference entries this slice exercises
-
Builder: how to define a virtual table
— for view-backed modules, the form's
sTypeandsTbNameare the declaration. -
Maintainer: the runtime — same
BusinessBaseControllerpath as Slice 1; thesTypebranch is the one new bit to document. - New page: Views and reports (under Maintainer) — the
viw_/Viw_naming convention, the rule that views must carry tenant columns through to remain tenant-safe, the print-template flow.
Open verification items
-
A view-backed module with a print template — pick one and
trace the jxls export end-to-end. Likely candidate: any monthly /
yearly summary report (
viw_corebusinessreport,viw_accordercostanalysisnew, …). -
The
sType = 'proc'variant — 209 forms are backed by stored procedures rather than tables/views (10% of forms). Slice 4 or a variant of this slice should cover that mode: how a proc-backed form returns its result-set, and how parameters flow. -
Tenant safety in views. We claimed views "almost always" carry
sBrandsId/sSubsidiaryId. Worth a script that audits which views don't — those are potential cross-tenant leak vectors.