# 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) 1. Handler receives JSON body; validates via `ShouldBindJSON`. 2. `LocalConfigurationService.Create` or `Update` is called. 3. Service computes `total_price` from `req.Items.Total()` (sum of `unit_price * quantity` per item). 4. A new revision snapshot is created via `createWithVersion`; revision number increments. 5. `quoteService.RecordUsage` is called best-effort (warn on failure, do not abort save). 6. Configuration row written to SQLite (`local_configurations`); version row appended to `local_configuration_versions`. 7. Pending change queued in `pending_changes` for 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) 1. Handler calls `LocalConfigurationService.RefreshPricesNoAuth(uuid, pricelistServerID)`. 2. If online, `SyncPricelistsIfNeeded` runs best-effort (warn on failure, do not block). 3. Resolves target pricelist in order: a. Explicitly requested pricelist (`pricelistServerID` param). b. Pricelist stored in configuration row (`localCfg.PricelistID`). c. Latest local pricelist as fallback. 4. For each item in the config, looks up price from `local_pricelist_items` via `GetLocalPricesForLots` (batch, single query). 5. Items with matching prices are updated; items with no price keep their existing `unit_price`. 6. 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) 1. Readiness guard checked; returns 423 if guard blocks sync. 2. `SyncService.SyncPricelists` pulls from `qt_pricelists` and `qt_pricelist_items` (MariaDB). 3. For each pricelist: header upserted first, then items replaced atomically via `ReplaceLocalPricelistItems`. 4. After all pricelists: `RecalculateAllLocalPricelistUsage` marks which pricelists are referenced by active configurations. 5. Sync result (status, error, timestamp) written to `app_settings` via `SetPricelistSyncResult`. **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) 1. Incoming `items[]` (lot_name, quantity, unit_price) replace the configuration's `items` entirely. 2. New item list saved through `LocalConfigurationService.UpdateItemsNoAuth`. 3. 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_versions` is 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/push` drains the queue by writing to MariaDB. - If a push fails, `increment_attempts` and `last_error` are updated; row stays in queue. - `RepairPendingChanges` reconciles orphaned changes (configuration/project deleted locally). --- ## 7. Error handling boundary rules - Handlers: log 500 responses with `slog.Error`; surface error message via `RespondError`. - 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.Warn` and continue.