# Two customization channels xly customers customize the system through **two distinct paths**. Understanding the difference is essential — they have different powers, different costs, and different operational consequences. ## Channel 1 — Metadata edits via BACK The supported, PM-driven path. A customer's implementer (or the customer themselves) opens the BACK builder, navigates to a module, and inserts / updates / deletes rows in metadata tables: - `gdsmodule` to register a new module - `gdsconfigformmaster` + `gdsconfigformslave` to define a form - `gdsconfigformcustomslave` to add a per-tenant field overlay ([Slice 4](../slices/04-custom-field.md)) - `gdsconfigformpersonalize` to override a form's SQL/where/order per tenant - `gdsjurisdiction` to grant or restrict permissions - `gdsroute`, `gdsformconst`, `sysbillnosettings`, … as needed These edits are **data**. They travel with the customer's database. They are visible in the BACK UI so a PM can audit them. The framework's runtime reads them on every request (with caching). The Java code is unchanged; the application's behaviour is what those rows say it is. This is the path the architecture intends customers to use. Whether the actual ratio is 90/10 in favour of Channel 1 isn't measured anywhere; the empirical signal is that 18 customer directories under `script/客户/` exist, which is a non-trivial slice of the customer base needing what Channel 1 can't express. Take "90%+ should live here" as an aspirational target, not a measured fact. ## Channel 2 — Per-customer SQL overrides The escape hatch. When metadata cannot express what a customer needs — typically because the customer needs different *procedural logic*, not just different fields or labels — engineers commit a hand-written SQL file to `script/客户//.sql` in the codebase. The file typically contains a `DROP PROCEDURE … CREATE PROCEDURE` pair (or a `DROP VIEW … CREATE VIEW` for view replacements). The file is then **manually applied** to the target customer's MySQL schema by a DBA or engineer. From that moment, that customer's database has a literally-different stored procedure with the same name as the standard. Documented in detail in [Slice 5](../slices/05-customer-sql-override.md). This channel is **real and used in production** — eighteen customers have override directories in the codebase today. It is also significantly more expensive to maintain than Channel 1. ## How to choose Use **Channel 1** when: - The customization is structural (new field, new module, different label). - The behaviour can be expressed by changing SQL fragments (`sSqlStr` / `sWhere` / `sOrder`). - The customization should be visible in BACK and editable by a PM. - You want it to travel with the database backup. Use **Channel 2** when: - The customer needs procedural logic the framework's Add/Update/Calc procs don't express. - You need to replace a stored procedure body, not just inject SQL fragments around it. - The runtime divergence should live in source-controlled `.sql` files (under `script/客户//`) so a maintainer reviewing the customer's runtime can see the per-customer changes at a glance, rather than discovering them only by connecting to the live DB. Channel 2 is *almost always a last resort*. Reach for it only after confirming Channel 1 cannot do the job. ## Cost comparison | Property | Channel 1 (metadata) | Channel 2 (SQL override) | |---|---|---| | Travels with DB backup | yes | only if the customer's schema is the backup target | | Visible in BACK | yes | no | | Editable by a PM | yes | no — engineer-only | | Cross-tenant impact | none — rows are scoped by `sBrandsId` | none — applied per-customer schema | | Re-applies after schema rebuild | yes (rows in DB) | no — must be manually re-run | | Auditable from a single source | yes (DB rows + their `sMakePerson`) | partial — file is in repo but the apply step has no log | | Discovery by reading codebase | requires connecting to DB | yes — file lives in `script/客户/` | ## What this means for slice readers When a slice describes "the standard behaviour", it means the behaviour seen by a tenant whose customizations are entirely in Channel 1. A customer with Channel 2 overrides may experience different runtime behaviour for the procs that have been replaced — *the framework's controller code can't tell the difference*. Reasoning about runtime behaviour for such customers requires looking at the deployed schema, not just the codebase.