-
Editorial pass per user direction: stop justifying the architecture. For every "why this design works" passage, name the costs the design imposes — not as a parenthetical aside but as substantive critical analysis. Each major architectural-claim page now carries an explicit drawbacks/costs section. Pages revised: concepts/thesis.md - "The reward" → "What the design enables (and what each enabler still costs)": for each promised benefit (single codebase, PMs evolve without engineering, customisations layered cleanly), name the limit. Added closing observation that data-driven design redistributes complexity to people and tools the framework can't compile-check. - "When it breaks down": rewrote to call out that "bypassing the framework" via 18 customer dirs makes the data-driven thesis partial, not complete. concepts/semantic-fk.md - "Why xly disabled FKs": added critical analysis. Both reasons could be addressed surgically; the chosen "no FKs anywhere" is the trade for DB-enforced integrity, paid every day the system runs. concepts/master-slave.md - "Slave naming caveat": stop framing retention as wise pragmatism. The naming was a poor choice; preservation has a real ongoing cost. concepts/modules-forms-vtables.md - "Three nouns, one engine": the universal dispatch path concentrates 3,500+ lines + edge cases + special-case hardcodes in one class. Naming the trade. concepts/multi-tenancy.md - "How the design scales" → "How the design scales — and where it doesn't": shared schema = shared contention; tenant-filter index discipline; no physical hard-delete; rigid (sBrandsId, sSubsidiaryId) tenancy unit. concepts/customization-channels.md - Soften "90%+ should live here" claim — that's an aspirational target, not a measured fact. The 18 customer override directories are evidence the channel-2 demand is non-trivial. concepts/api-surface.md - "Why three tiers, not one" → "Why three tiers (and what splitting them costs)": three WARs to deploy, duplicate code, no shared session, three reverse-proxy entries. Note the alternative (single-WAR with package boundaries) and what that would cost vs gain. reference/maintainer/proc-dispatch.md - "Why dynamic proc dispatch matters": added five concrete costs (no compile-time check, no type safety, no call-site discoverability, no static analysis, broken stack traces). Reframed: dynamic dispatch made it cheap to keep adding procs, which made the pile grow, which made the pile harder to audit. reference/maintainer/cache-invalidation.md - New "Drawbacks of this design" section: confusing co-named systems, eviction in same transaction as write (silent corruption on Redis outage), allEntries=true blunt eviction, no batching, direct DB writes bypass everything. Also fixed the "if cache is local" hedge in section 3 (we've now empirically confirmed Redis- backed, so cache is shared). reference/maintainer/bi-engine.md - New "Drawbacks of the homebrewed approach" section: every chart needs a SQL author, charts run heavy SQL on OLTP DB, no semantic consistency between charts, no drill-down, customer-divergent KPI logic. Also dedup'd the duplicated "What this is not" section. reference/maintainer/sql-templates.md - "Why this is a 'template' library and not a code generator" → added costs: no enforcement, no regeneration, no template-origin tracking, customer overrides drift from scaffold. The 1,687 procs the schema carries are the evidence that "discipline rather than enforcement" doesn't fully hold. reference/maintainer/activiti.md - "Why this design works for xly's audience" → "Why xly avoided Activiti — and what that costs": scattered workflow logic, no central audit trail, no parallel-branch/reassignment, invisible flow-graph evolution, idle Activiti engine paying boot cost anyway. - "Why xly bothered with Activiti at all" → "Why xly bothered with Activiti — and whether it was worth it": named the costs (second engine, second schema, second auth surface, modeler UI to learn) and the damning fact that on this dev DB the engine is idle. A future cleanup could plausibly remove Activiti entirely. reference/maintainer/runtime.md - New "What 'universal CRUD' means in practice" section: 3,500-line single-point-of-failure class, no type system on Map<String,Object>, poor discoverability ("what endpoints write to table X" is unanswerable). The trade: adding a module is essentially free, touching the runtime essentially never is. - Updated cache-invalidation cross-link to drop the "open question" hedge (now empirically resolved). slices/04-custom-field.md - "Why it works without code changes" → "Why it works without code changes — and what that costs": merge runs on every request, three near-empty tables on every schema, display-only extension (real persisted fields still need ALTER TABLE), debuggability requires diffing 3 overlay tables. slices/05-customer-sql-override.md - Added drawbacks: no version control on the deployed body, no type-safety bridge, compounds the BI problem. Reframed the "right rule of thumb": 18 customer override directories suggest the channel-2 demand is structural, not exceptional — that's evidence the metadata model isn't expressive enough, not a celebration of the escape hatch. slices/06-hardware.md - "The cleanest story xly tells about an awkward problem" → removed the "cleanest" framing. Added costs of "DB as the only contract": no backpressure, no request/response, bridge-side state invisible to the framework, three layers of polling multiply latency, hardest code (byte protocols) gets least CI. A real-time-aware architecture would use streaming end-to-end; xly's choice trades latency, observability, flow control for operational simplicity. Liveable for press tempo, not for faster shop-floor signals.
-
User asked: are there examples of customising workflow other than the standard hardcoded one? Investigated and found a substantial customer example. Findings: - script/客户/万昌/计件工资/日报审核/领班驳回.sql is a 185-line customer-side workflow customisation defining Sp_mftproductionreportmaster_check1_0 ("Foreman Rejection", state 1 → state 0 transition). - Naming convention: Sp_<table>_check<currentState>_<nextState>. No procs in standard DB use this pattern; it's 万昌's own convention. - The proc resets SIX approval flags simultaneously (bManager, bIPQC, bDeputy, bSubmit, bWorkshopManager, bCheck) — 万昌 has ALTER TABLEd mftproductionreportmaster to add 5 columns that don't exist in the standard schema. - Adds sRejectMemo rejection-reason history (also custom column), appends each rejection's reason + timestamp + foreman name. - Calls sp_add_flow_log — a custom audit-log proc that doesn't exist in the standard DB. - Includes customer-specific business rules (e.g., block rejection if any slave row has bSAPCheck=1 — SAP-sync guard). So the answer to "any examples of customising workflow": - Yes — 万昌 has built their OWN multi-level approval workflow on top of xly's button-dispatch primitive: schema extension + custom transition procs + custom audit log. The framework provides only the button-press dispatch (via /business/ genericProcedureCall* or sButtonParam); everything else is customer-defined. - This is fundamentally different from Activiti — no BPMN, no FSM library, no engine. Just SQL + ALTER TABLE. - Other customer dirs (千彩, 重庆展印, 朝阳, etc.) mostly customise *calculations and reports*, not workflow. 万昌's pattern is rare but real. Slice 5 gets a "Worked-example 2" section walking through 领班驳回.sql in detail, plus a customisation-patterns-at-a-glance breakdown of what each of the 18 customer dirs actually contains. Activiti.md gets a back-reference: "A real Path-1 customisation example" pointing at the new slice 5 worked example, with the empirical observation that Activiti deployment is NOT seen in any script/客户/ directory. -
User asked: if Activiti isn't used, what implements workflow? Investigated and replaced the original two-path table with a proper three-path explainer. Findings: - Path 1 (single-step approval via stored proc + bCheck flag) is the dominant pattern. Each business table carries bCheck (426 tables), tCheckDate (400), sCheckPerson (398) — virtually universal. The 审核 button POSTs to /business/doExamine which calls the proc named in gdsmodule.sProcName; the proc owns the business validation and flips bCheck=1 directly. No engine, no state machine, no queue. - Path 2 (document chaining) is how multi-document business processes work: separate gdsmodule entries arranged as parent + ordered children, each with its own form and approval button. A "convert-to-next-doc" proc on form A creates the document for form B. The "01/04, 02/04…" KPI Work Center step numbering is just iOrder of children under a parent gdsmodule. No state machine; the workflow is the chain of procs wired by buttons. - Path 3 (Activiti BPMN) is GATED by ConstantUtils.bCheckflowCheck which is hard-coded false. So even if a tenant configured gdsmoduleflow rows and deployed BPMN, the gate in ExamineServiceImpl.doExcuExamine would short-circuit. To activate Path 3 a maintainer would have to recompile xly with the constant flipped, OR runtime-patch it. The page now leads with all three paths and a comparison table covering state storage, step transitions, reassignment/delegation, parallel branches, current activation status, and tooling. Concluding section explains why this design fits xly's audience (printing-industry ERP, where each business step is naturally its own document) and the trade-off (workflow logic scattered across stored procs rather than visualised in BPMN).
-
Critical correction in response to "no BPMN deployed means not used?": yes, in this dev DB, Activiti's runtime is never invoked. Findings: - xly has TWO parallel approval mechanisms, only one of which uses Activiti. - Path 1 (simple toggle): the "审核" button in the SPA POSTs to /business/doExamine → BusinessBaseController.java:384-391 → BusinessBaseServiceImpl.doExamine() → ExamineServiceImpl. Verified (grep -E 'runtimeService|taskService|processService|complete' against ExamineServiceImpl.java returns 0 hits) — this service flips bCheck=1 via direct SQL, never touching Activiti. This is the path actually used today. - Path 2 (BPMN workflow): would only trigger when a gdsmodule has bCheck=1 AND a populated gdsmoduleflow row pointing at a deployed procdef. None of these exist in the dev DB. - The bCheck flag is therefore xly's own approval boolean, owned by BusinessBaseServiceImpl, used widely as a list filter (SalesOrderServiceImpl etc. do WHERE bCheck=1 on every "show approved-only" query). - /business/doExamine + /business/getProData added to internal.md endpoint table. - activiti.md gets a "Two approval paths" section as the lead so a reader doesn't conflate the simple bCheck toggle with the workflow engine. - The TL;DR is rewritten to make the "engine wired but not invoked" story explicit.
-
Re-investigated Activiti's actual integration after the user asked how it's used. Substantive findings: State on this dev DB: engine wired, never used. - The Activiti 6.0 engine bootstraps with xlyEntry. activiti-spring- boot-starter-rest-api:6.0.0 declared in xlyFlow/build.gradle pulls ProcessEngineAutoConfiguration. xlyEntry's EntryApplicationBoot only excludes Activiti's REST-security autoconfig, not the engine. xlyFlow is consumed as api project(':xlyFlow') in xlyEntry, so all xlyFlow controllers serve at xlyEntry's context-path. - ActivitiConfig.java is a real @Configuration that wires Chinese fonts and a custom ICustomProcessDiagramGenerator into the engine. - Real call sites confirmed: * runtimeService.startProcessInstanceByKey(...) at ProcessServiceImpl.java:107 * taskService / repositoryService used across CurrencyFlowController, ModelerController, ProcessDefinitionController, ProcessActController, BizTodoItemServiceImpl, etc. * Modeler endpoints: /modeler/model/{id}/save, /modeler/editor/ stencilset, /modeler/create, /modeler/deploy/{id}. - Empirical state: every workflow table is 0 rows in this dev DB — act_re_model, act_re_procdef, act_ru_task, act_hi_procinst, gdsmoduleflow, biz_flow, biz_todo_item, plus 0 gdsmodule rows with bCheck=1. Engine running, plumbing hot, no traffic. CheckFlowController correction: - The class file is 22 lines with ZERO handler methods. Just @RestController @RequestMapping("/checkflow") shell. The wiki previously described it as "Activiti workflow surface (approve/ reject/view)" — this was wrong. /checkflow/* returns 404. - Real workflow URLs live in xlyFlow's CurrencyFlowController: /complete/{taskId}/{sBrandsId}/{sSubsidiaryId}/{sUserId}, /completeerp/{...}, plus /modeler/* for BPMN authoring. - Fixed in runtime.md (load-bearing-controllers row), internal.md (specialised-runtime row), with cross-link to activiti.md. activiti.md rewritten: - Reframed from "deferred for full coverage" to "engine wired, never used in this dev DB" — the engine state is concrete and documentable; only the flow content is empty. - Added "Activiti is wired — engine ON" section with the bootstrap chain. - Added a concrete table of Activiti API call sites (file:line + which Activiti service is invoked). - Added URLs-the-modeler-exposes section listing the real workflow URLs served via xlyEntry. - Added the act_* schema state table with the live 0-row counts. - Added "What would make it move" — the 6-step path from drawing a BPMN to seeing a flowing approval. - Added "Why xly bothered with Activiti at all" — the architectural rationale. - Documented xlyApi/xlyPersist 5.17 declarations as vestigial (only IdGen.java's User import; type signature works under 6.0 too — safe to remove). -
Audit every concrete claim in the 41 hand-written en pages against the three primary sources (DB, source on cleanup branch, source-tree inventory). Fix divergent claims in place; preserve framing where verified. Substantive corrections: - request-lifecycle / runtime / slice 01: the metadata read sources from five tables/families (gdsconfigformmaster + overlays, gdsformconst, sysjurisdiction, sysbillnosettings, sysreport), not four. The map key `gdsjurisdiction` is misleading — the per-user grant read queries `sysjurisdiction`; `gdsjurisdiction` is the builder-side action catalogue. `gdsformconst`, `gdsconfigformmaster`, `gdsconfigformslave` are NOT tenant-scoped; they filter by form-id only. - multi-tenancy: four metadata tables (gdsformconst, gdsmodule, gdsconfigformmaster, gdsconfigformslave) are an explicit exception to the "every table tenant-scoped" promise — `sTableNameList` strips sBrandsId/sSubsidiaryId from writes against them. - sSaveProName / sSaveProNameBefore are pre/post-save HOOKS on top of the always-running base path (BusinessBaseServiceImpl.add/update), not either/or branches. Default add/update path is in BusinessBaseServiceImpl, not AddDelUpdCommonServiceImpl. - cache-invalidation: redis cache is cleared synchronously in BACK via @CacheEvict on CleanRedisServiceImpl during save. The JMS CHANGE_GDS_MODULE queue triggers PRO_ERPMERGEBASEGDSMODULE (base-data merge), NOT cache invalidation despite the name. Cross-node coherence open question (no custom CacheManager bean configured). - messaging: enumerate all 24 P2pQueue destinations grouped by intent; fix CHANGE_GDS_MODULE description; clarify single Consumer.java with 24 @JmsListener methods (not 24 listener classes). - API paths: /checkflow lowercase (mapping value, not class name); /procedureCall/doGenericProcedureCall (not /business/genericProcedureCall*). - tech-stack: Druid 6 java imports + 16 yml mentions (was 25 conflated); fastjson per-module xlyInterface 9 (was 10); commons-lang3 39 (was 41); @Document classes 20 PLAT_* + 2 DIKE_TEST* (was "all PLAT_*"); xlyPersist activiti hit is IdGen.java (was BaseDao.java); add Springfox to declared-but-no-imports table; reconcile module list to 11 framework core + xlyPlc plugin + xlyPlatConstant utility. - index.md: clarify xlyFace as "in build, not documented"; add xlyErpTask / xlyPlatTask scheduler bullet; correct MongoDB framing (caller is in xlyPersist with no consumers, not xlyPlat*); add xlyPlc note; extend backup-table OOS to cover *_copy1 / *_history / *YYYYMMDD[HHMMSS]. - deployment.md: split deployable Boot apps from library modules; enumerate 12 commented-out includes (was 3); remove xlyPlatConstant from out-of-scope Plat* list; split profile permutations by service. - activiti.md: add xlyApi to 5.17 dependency list; replace speculative BPMN path hint with verified state; name actual ActivitiConfig.java; note act_id_* are views projecting xly users into Activiti shapes. - api-reference/external.md: fix bearer-token validation flow (sysapibrand via AES-decrypted corpid, not sysapithirdtoken); /online/* are page renders not API execution; /pro/* mostly returns Thymeleaf views; mark sysapidbtodb as xlyFlow-owned; /token/getToken accepts GET and POST. - api-reference/webhooks.md: add Swagger Docket caveat (UI shell ships but no Docket bean → /v2/api-docs effectively empty); flag /send/sendQw as stub (returns "ok"). - slices/03-report.md: fix dir path xlyEntry/com/xly/report/ → xlyEntry/com/xly/web/report/; reframe PrintReportControllerOld as dead source (file body fully commented out). - concepts/modules-forms-vtables.md: add 22-prefix glossary table (gds/sys/sis/sft/ele/mft/sal/quo/acc/pur/ops/cah/sgd/ept/mit/pit/qly/ kpi/udf/viw_/plat_/ai_/act_/qrtz_) so a maintainer can enumerate business-data domains at a glance. - concepts/master-slave.md: disambiguate document-row pattern from DataSource master/slave (different concept, name overlap). - proc-dispatch.md: add proc-name molds (Sp_*_BeforeSave/AfterSave/ SaveReturn, sp_btn_*, PRO_ERPMERGE*) + function-layer paragraph (Fun_*/Fn_*/get_*; SQL-called, not Java-dispatched). - concepts/index.md: schema label MySQL\nxlyweberp → xlyweberp_*. Pass E (live behavioural traces) deferred — source/DB-side audit was thorough; live traces best done as a follow-up sweep against a running instance.
-
Verified every factual claim in the hand-written prose against the codebase under ../xly/ and the live xlyweberp_saas_ai schema, and fixed the drift: - semantic-fk: clarified — zero FKs on xly tables; the ones that exist are all on bundled Activiti / Quartz schemas, which the runtime doesn't join through. - multi-tenancy / runtime / slice 2: corrected RequestAddParamUtil shape (16 keys, 56 lines), noted the parallel xlyApi copy. - slice 1: corrected line ranges for addUpdateDelBusinessData; clarified that sTableNameList is a cache-invalidation gate, not an authorisation gate; corrected the gdsroute claim — unregistered paths are NOT 404'd server-side, the SPA uses gdsroute as a client-side whitelist (web-verified). - runtime: fixed controller package paths (Gdsmodule/Gdsconfigform/Gdsconfigtb live in systemweb/, not businessweb/); added CheckFlowController. - sql-templates / proc-dispatch: 8 scaffolds, not 7 (sSqlStr.sql). - master-slave: removed table names that don't exist (accOrderCostAnalysisMaster, quoQuotationCalc, mftWorkOrderCalc, mftWorkOrderSlaveMoney); added the *_tmp family that does. - permissions: full rewrite. The original page described gdsjurisdiction as a per-(module, role, button) rule table backed by empty plat_base_authority_* lookups; reality is gdsjurisdiction is a per-module catalog of buttons/actions, with sysjurisdiction carrying the actual role/user grants. - Removed environment-specific facts (row counts, sizes, timestamps, per-DB enumerations) so the wiki documents the framework, not one particular DB snapshot. Auto-catalog generator (en/scripts/gen_catalog.py): - Rewritten to query MySQL directly via ~/.my.cnf (replaces the recon/*.tsv pipeline). - Stripped ephemeral fields from output (Rows, Data size, Created, Updated on tables; Created, Last altered on routines; Definer on views). - Strips the schema-name prefix from VIEW_DEFINITION so view bodies are portable across deployments. - Wipes and rewrites docs/auto-catalog/{tables,views,procedures,functions}/ on each run. - Auto-catalog regenerated. New API Reference chapter (5): - concepts/api-surface.md introduces the three-tier design (xlyEntry internal, xlyApi external, xlyInterface webhooks). - api-reference/{index,internal,external,webhooks,messaging}.md cover each surface, with the data-driven /api/invoke{sApiCode} pattern, sysapi schema, token flow, and the SpringFox Swagger UI location on xlyInterface. - mkdocs nav: chapters renumbered (Auto-Catalog → 6, Glossary → 7, Contributing → 8); glossary/contributing wrapped as section indexes to render correctly under navigation.sections. Other: - Bilingual top-level README (Chinese + English). - requirements.txt: pymysql added for the new generator. -
Documents the xly (小羚羊) printing-industry ERP framework. Built with MkDocs Material; CJK search via jieba; 3,076 auto-generated catalog pages from recon/*.tsv plus hand-written prose for the framework's core mental model and end-to-end vertical slices. Phase 0 recon: stack, schema shape, framework metadata layer, scope. Phase 1 wiki: scaffold + auto-catalog + Slices 1-6 (Slice 7 deferred). Slice coverage: 1. CRUD module (Hello World) — observed network + cited source 2. Multi-tenancy & product editions — sBrandsId/sSubsidiaryId/sVersionFlowId 3. View-backed module (read-only report) 4. Custom field overlay (gdsconfigformcustomslave) 5. Per-customer SQL override (script/客户/<customer>/) 6. Hardware integration (xlyPlc, optional) 7. Workflow (deferred — Activiti tables empty in dev DB) Concepts: thesis, modules-forms-vtables, master/slave, semantic-FK, customization channels & layers, multi-tenancy, request lifecycle. Reference (Builder): define-form, define-vtable, permissions, attach-workflow (deferred). Reference (Maintainer): runtime, proc-dispatch, cache-invalidation, sql-templates, deployment, activiti.