# 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/客户/<customer>/<file>.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/客户/<customer>/`) 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.
