- Add config_type field ("server"|"storage") to Configuration and LocalConfiguration
- Create modal: Сервер/СХД segmented control in configs.html and project_detail.html
- Configurator: ENC/DKC/CTL categories in Base tab, HIC section in PCI tab hidden for server configs
- Add SW tab (categories: SW) to configurator, visible only when components present
- TAB_CONFIG.pci: add HIC section for storage HIC adapters (separate from server HBA/NIC)
- Migration 029: ALTER TABLE qt_configurations ADD COLUMN config_type
- Fix: skip Error 1833 (Cannot change column used in FK) in GORM AutoMigrate
- Operator guide: docs/storage-components-guide.md with LOT naming rules and DE4000H catalog template
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
128 lines
6.3 KiB
Markdown
128 lines
6.3 KiB
Markdown
# 02 - Architecture
|
||
|
||
## Local-first rule
|
||
|
||
SQLite is the runtime source of truth.
|
||
MariaDB is sync transport plus setup and migration tooling.
|
||
|
||
```text
|
||
browser -> Gin handlers -> SQLite
|
||
-> pending_changes
|
||
background sync <------> MariaDB
|
||
```
|
||
|
||
Rules:
|
||
- user CRUD must continue when MariaDB is offline;
|
||
- runtime handlers and pages must read and write SQLite only;
|
||
- MariaDB access in runtime code is allowed only inside sync and setup flows;
|
||
- no live MariaDB fallback for reads that already exist in local cache.
|
||
|
||
## Sync contract
|
||
|
||
Bidirectional:
|
||
- projects;
|
||
- configurations;
|
||
- `vendor_spec`;
|
||
- pending change metadata.
|
||
|
||
Pull-only:
|
||
- components;
|
||
- pricelists and pricelist items;
|
||
- partnumber books and partnumber book items.
|
||
|
||
Readiness guard:
|
||
- every sync push/pull runs a preflight check;
|
||
- blocked sync returns `423 Locked` with a machine-readable reason;
|
||
- local work continues even when sync is blocked.
|
||
- sync metadata updates must preserve project `updated_at`; sync time belongs in `synced_at`, not in the user-facing last-modified timestamp.
|
||
- pricelist pull must persist a new local snapshot atomically: header and items appear together, and `last_pricelist_sync` advances only after item download succeeds.
|
||
- UI sync status must distinguish "last sync failed" from "up to date"; if the app can prove newer server pricelist data exists, the indicator must say local cache is incomplete.
|
||
|
||
## Pricing contract
|
||
|
||
Prices come only from `local_pricelist_items`.
|
||
|
||
Rules:
|
||
- `local_components` is metadata-only;
|
||
- quote calculation must not read prices from components;
|
||
- latest pricelist selection ignores snapshots without items;
|
||
- auto pricelist mode stays auto and must not be persisted as an explicit resolved ID.
|
||
|
||
## Pricing tab layout
|
||
|
||
The Pricing tab (Ценообразование) has two tables: Buy (Цена покупки) and Sale (Цена продажи).
|
||
|
||
Column order (both tables):
|
||
|
||
```
|
||
PN вендора | Описание | LOT | Кол-во | Estimate | Склад | Конкуренты | Ручная цена
|
||
```
|
||
|
||
Per-LOT row expansion rules:
|
||
- each `lot_mappings` entry in a BOM row becomes its own table row with its own quantity and prices;
|
||
- `baseLot` (resolved LOT without an explicit mapping) is treated as the first sub-row with `quantity_per_pn` from `_getRowLotQtyPerPN`;
|
||
- when one vendor PN expands into N LOT sub-rows, PN вендора and Описание cells use `rowspan="N"` and appear only on the first sub-row;
|
||
- a visual top border (`border-t border-gray-200`) separates each vendor PN group.
|
||
|
||
Vendor price attachment:
|
||
- `vendorOrig` and `vendorOrigUnit` (BOM unit/total price) are attached to the first LOT sub-row only;
|
||
- subsequent sub-rows carry empty `data-vendor-orig` so `setPricingCustomPriceFromVendor` counts each vendor PN exactly once.
|
||
|
||
Controls terminology:
|
||
- custom price input is labeled **Ручная цена** (not "Своя цена");
|
||
- the button that fills custom price from BOM totals is labeled **BOM Цена** (not "Проставить цены BOM").
|
||
|
||
CSV export reads PN вендора, Описание, and LOT from `data-vendor-pn`, `data-desc`, `data-lot` row attributes to bypass the rowspan cell offset problem.
|
||
|
||
## Configuration versioning
|
||
|
||
Configuration revisions are append-only snapshots stored in `local_configuration_versions`.
|
||
|
||
Rules:
|
||
- the editable working configuration is always the implicit head named `main`; UI must not switch the user to a numbered revision after save;
|
||
- create a new revision when spec, BOM, or pricing content changes;
|
||
- revision history is retrospective: the revisions page shows past snapshots, not the current `main` state;
|
||
- rollback creates a new head revision from an old snapshot;
|
||
- rename, reorder, project move, and similar operational edits do not create a new revision snapshot;
|
||
- revision deduplication includes `items`, `server_count`, `total_price`, `custom_price`, `vendor_spec`, pricelist selectors, `disable_price_refresh`, and `only_in_stock`;
|
||
- BOM updates must use version-aware save flow, not a direct SQL field update;
|
||
- current revision pointer must be recoverable if legacy or damaged rows are found locally.
|
||
|
||
## Sync UX
|
||
|
||
UI-facing sync status must never block on live MariaDB calls.
|
||
|
||
Rules:
|
||
- navbar sync indicator and sync info modal read only local cached state from SQLite/app settings;
|
||
- background/manual sync may talk to MariaDB, but polling endpoints must stay fast even on slow or broken connections;
|
||
- any MariaDB timeout/invalid-connection during sync must invalidate the cached remote handle immediately so UI stops treating the connection as healthy.
|
||
|
||
## Naming collisions
|
||
|
||
UI-driven rename and copy flows use one suffix convention for conflicts.
|
||
|
||
Rules:
|
||
- configuration and variant names must auto-resolve collisions with `_копия`, then `_копия2`, `_копия3`, and so on;
|
||
- copy checkboxes and copy modals must prefill `_копия`, not ` (копия)`;
|
||
- the literal variant name `main` is reserved and must not be allowed for non-main variants.
|
||
|
||
## Configuration types
|
||
|
||
Configurations have a `config_type` field: `"server"` (default) or `"storage"`.
|
||
|
||
Rules:
|
||
- `config_type` defaults to `"server"` for all existing and new configurations unless explicitly set;
|
||
- the configurator page is shared for both types; the SW tab is always visible regardless of type;
|
||
- storage configurations use the same vendor_spec + PN→LOT + pricing flow as server configurations;
|
||
- storage component categories map to existing tabs: `ENC`/`DKC`/`CTL` → Base, `HIC` → PCI (HIC-карты СХД; `HBA`/`NIC` — серверные, не смешивать), `SSD`/`HDD` → Storage (используют существующие серверные LOT), `ACC` → Accessories (используют существующие серверные LOT), `SW` → SW.
|
||
- `DKC` = контроллерная полка (модель СХД + тип дисков + кол-во слотов + кол-во контроллеров); `CTL` = контроллер (кэш + встроенные порты); `ENC` = дисковая полка без контроллера.
|
||
|
||
## Vendor BOM contract
|
||
|
||
Vendor BOM is stored in `vendor_spec` on the configuration row.
|
||
|
||
Rules:
|
||
- PN to LOT resolution uses the active local partnumber book;
|
||
- canonical persisted mapping is `lot_mappings[]`;
|
||
- QuoteForge does not use legacy BOM tables such as `qt_bom`, `qt_lot_bundles`, or `qt_lot_bundle_items`.
|