03-report.md 9.34 KB

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
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. View-backed forms (sType = 'view') are second only to table-backed forms in the framework's catalog — every operational report and dashboard is a variant of this pattern. Once you've internalised this slice, the rest follow.

It's also a counter-example to two things Slice 1 might have made you believe:

  1. That every module reads from a single table — view-backed modules read joins.
  2. That every URL is a unique pinyin path. /indexPage/commonList is a shared template URL: many different view-backed modules answer at /indexPage/commonList. The URL doesn't identify the module — the sModelsId parameter 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 flat SELECT … 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 viw_* views in the schema.

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:

  • getModelBysId returns the report array, populated from sysreport rows linked to the form via sFormId.
  • The frontend's "打印" / "导出" buttons hit xlyEntry/src/main/java/com/xly/web/report/PrintReportController is the live class. (PrintReportControllerOld.java exists in the same directory but its class body is fully commented out, dead source.) The controller loads a jxls / iText template, runs the same view-backed query with a "fetch all rows" wrapper, and streams a binary file back.
  • This module (工单工序明细) has no template attached, so we don't exercise the print path here.

Future-work backlog. A revision of this slice that picks a module with an attached print template would let us trace the jxls export end-to-end. Blocked on dev-DB state today (no view-backed form has a sysreport row attached — see the Open verification items below).

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/commonNewBill are each used by hundreds of modules. The URL is a page-shape selector; module identity comes from the sModelsId query param, not the path.
  • Report templates (preview only — full coverage in a future slice pass): sysreport linked via sFormId, jxls/iText templates served by PrintReportController.

Reference entries this slice exercises

  • Builder: how to define a virtual table — for view-backed modules, the form's sType and sTbName are the declaration.
  • Maintainer: the runtime — same BusinessBaseController path as Slice 1; the sType branch 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

Item 1 — Deferred (needs populated dev DB). As of the last audit the dev DB has zero view-backed forms with a sysreport row attached: SELECT … FROM gdsconfigformmaster m INNER JOIN sysreport r ON r.sFormId = m.sId WHERE m.sType='view' returns 0 rows. The item remains a real verification gap that requires a tenant deployment whose sysreport rows include at least one view-backed form.

  1. 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, …).
  2. The sType = 'proc' variant — a sizable minority of forms are backed by stored procedures rather than tables/views. 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.
  3. Tenant safety in views — audit which viw_* lack sBrandsId. CLOSED — 19 of 305 (~6.2 %) leak. Run against the live DB:
   SELECT v.TABLE_NAME
   FROM information_schema.views v
   WHERE v.TABLE_SCHEMA = DATABASE()
     AND v.TABLE_NAME LIKE 'viw_%'
     AND v.TABLE_NAME NOT IN (
       SELECT TABLE_NAME FROM information_schema.columns
       WHERE TABLE_SCHEMA = DATABASE() AND COLUMN_NAME = 'sBrandsId'
     );

Returns 19 rows in this dev DB — including viw_purorder_slave_detail, viw_qlyprocesstest, the viw_accproductstoreinvoice* family, the viw_hmwxjy* set, etc. Each is a potential cross-tenant leak if a form points at it without an enclosing tenant predicate in gdsconfigformmaster.sWhere. Auditing the form layer's predicates for these specific views is the next step; the bare-view audit is now a one-shot SQL.