- 400 → 422 для всех ошибок валидации входных данных (handlers: export, quote, sync, vendor_spec, partnumber_books, pricelist) - SQL-запросы вынесены из handlers в localdb (partnumber_books, pricelist, support_bundle); ValidateMariaDBConnection перенесён в internal/db/validate.go - List-ответы унифицированы: ключ items, поля total_count/page/per_page/total_pages (component, pricelist, partnumber_books); шаблоны обновлены - Молчаливые ошибки заменены на slog.Warn/Error (support_bundle, vendor_spec, component, configuration, local_configuration, localdb) - N+1 запросы устранены: batch-запросы в export.go и vendor_workspace_import.go - fmt.Println → slog в cmd/ (qfs, migrate, migrate_ops_projects, migrate_project_updated_at) - Заголовки recovery/verify добавлены во все 28 SQL-миграций - Добавлены bible-local/runtime-flows.md и bible-local/decisions/ - Обновлён субмодуль bible до v0.2.0-13 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.3 KiB
4.3 KiB
Runtime Flows
Critical mutation paths, deduplication logic, and cross-entity side effects. Update this file in the same commit as any change to the flows below.
1. Configuration save (create/update)
- Handler receives JSON body; validates via
ShouldBindJSON. LocalConfigurationService.CreateorUpdateis called.- Service computes
total_pricefromreq.Items.Total()(sum ofunit_price * quantityper item). - A new revision snapshot is created via
createWithVersion; revision number increments. quoteService.RecordUsageis called best-effort (warn on failure, do not abort save).- Configuration row written to SQLite (
local_configurations); version row appended tolocal_configuration_versions. - Pending change queued in
pending_changesfor later sync push.
DO NOT read prices from local_components during save - prices must already be on items.
DO NOT skip version creation on rename/reorder/project-move - those operations call different paths that must NOT call createWithVersion.
2. Refresh prices (POST /api/configs/:uuid/refresh-prices)
- Handler calls
LocalConfigurationService.RefreshPricesNoAuth(uuid, pricelistServerID). - If online,
SyncPricelistsIfNeededruns best-effort (warn on failure, do not block). - Resolves target pricelist in order:
a. Explicitly requested pricelist (
pricelistServerIDparam). b. Pricelist stored in configuration row (localCfg.PricelistID). c. Latest local pricelist as fallback. - For each item in the config, looks up price from
local_pricelist_itemsviaGetLocalPricesForLots(batch, single query). - Items with matching prices are updated; items with no price keep their existing
unit_price. - Updated configuration saved as a new version (same flow as §1 from step 4 onward).
DO NOT read prices from qt_pricelist_items (MariaDB) directly - prices come from SQLite cache only.
3. Pricelist sync (POST /api/sync/pricelists)
- Readiness guard checked; returns 423 if guard blocks sync.
SyncService.SyncPricelistspulls fromqt_pricelistsandqt_pricelist_items(MariaDB).- For each pricelist: header upserted first, then items replaced atomically via
ReplaceLocalPricelistItems. - After all pricelists:
RecalculateAllLocalPricelistUsagemarks which pricelists are referenced by active configurations. - Sync result (status, error, timestamp) written to
app_settingsviaSetPricelistSyncResult.
DO NOT write pricelist header without items in the same transaction - must be atomic. DO NOT query MariaDB from runtime handlers outside sync/setup flows.
4. Vendor spec apply (POST /api/configs/:uuid/vendor-spec/apply)
- Incoming
items[](lot_name, quantity, unit_price) replace the configuration'sitemsentirely. - New item list saved through
LocalConfigurationService.UpdateItemsNoAuth. - A new revision is created reflecting the BOM-derived item state.
DO NOT apply vendor spec without going through the service layer - handler must not write items directly to DB.
5. Configuration versioning invariants
local_configuration_versionsis append-only; rows are never updated or deleted.- Version deduplication: if new snapshot hash equals current head, no new version is created.
- Rollback = create new HEAD revision from old snapshot data (does not restore version pointer to old row).
- UI must always show "main" (implicit head) as the active state; never point to a numbered revision after save.
- Operations that do NOT create a new version: rename, reorder within project, project move, pricelist selector change only.
6. Pending changes queue
- Every local write (create/update/delete) appends a row to
pending_changes. POST /api/sync/pushdrains the queue by writing to MariaDB.- If a push fails,
increment_attemptsandlast_errorare updated; row stays in queue. RepairPendingChangesreconciles orphaned changes (configuration/project deleted locally).
7. Error handling boundary rules
- Handlers: log 500 responses with
slog.Error; surface error message viaRespondError. - Services: wrap errors with
fmt.Errorf("context: %w", err); do NOT log inside service. - Repositories: return raw errors; no logging.
- Best-effort operations (usage stats, background sync): log
slog.Warnand continue.