# 07 — Development ## Commands ```bash # Run (dev) go run ./cmd/qfs make run # Build make build-release # Optimized build with version info CGO_ENABLED=0 go build -o bin/qfs ./cmd/qfs # Cross-platform build make build-all # Linux, macOS, Windows make build-windows # Windows only # Verification go build ./cmd/qfs # Must compile without errors go vet ./... # Linter # Tests go test ./... make test # Utilities make install-hooks # Git hooks (block committing secrets) make clean # Clean bin/ make help # All available commands ``` --- ## Code Style - **Formatting:** `gofmt` (mandatory) - **Logging:** `slog` only (structured logging to the binary's stdout/stderr). No `console.log` or any other logging in browser-side JS — the browser console is never used for diagnostics. - **Errors:** explicit wrapping with context (`fmt.Errorf("context: %w", err)`) - **Style:** no unnecessary abstractions; minimum code for the task --- ## Guardrails ### What Must Never Be Restored The following components were **intentionally removed** and must not be brought back: - cron jobs - importer utility - admin pricing UI/API - alerts - stock import ### Configuration Files - `config.yaml` — runtime user file, **not stored in the repository** - `config.example.yaml` — the only config template in the repo ### Sync and Local-First - Any sync changes must preserve local-first behavior - Local CRUD must not be blocked when MariaDB is unavailable - Runtime business code must not query MariaDB directly; all normal reads/writes go through SQLite snapshots - Direct MariaDB access is allowed only in `internal/services/sync/*` and dedicated setup/migration tools under `cmd/` - `connMgr.GetDB()` in handlers/services outside sync is a code review failure unless the code is strictly setup or operator tooling - Local SQLite migrations must be implemented in code; do not add a server-side registry of client SQLite SQL patches - Read-only local cache tables may be reset during startup recovery if migration fails; do not apply that strategy to user-authored tables like configurations, projects, pending changes, or connection settings ### Formats and UI - **CSV export:** filename must use **project code** (`project.Code`), not project name Format: `YYYY-MM-DD (ProjectCode) ConfigName Article.csv` - **Breadcrumbs UI:** names longer than 16 characters must be truncated with an ellipsis ### Architecture Documentation - **Every architectural decision must be recorded in `bible/`** - The corresponding Bible file must be updated **in the same commit** as the code change - On every user-requested commit, review and update the Bible in that same commit --- ## Common Tasks ### Add a Field to Configuration 1. Add the field to `LocalConfiguration` struct (`internal/models/`) 2. Add GORM tags for the DB column 3. Write a SQL migration (`migrations/`) 4. Update `ConfigurationToLocal` / `LocalToConfiguration` converters 5. Update API handlers and services ### Add a Field to Component 1. Add the field to `LocalComponent` struct (`internal/models/`) 2. Update the SQL query in `SyncComponents()` 3. Update the `componentRow` struct to match 4. Update converter functions ### Add a Pricelist Price Lookup ```go // Modern pattern price, found := s.lookupPriceByPricelistID(pricelistID, lotName) if found && price > 0 { // use price } ``` --- ## Known Gotchas 1. **`CurrentPrice` removed from components** — any code using it will fail to compile 2. **`HasPrice` filter removed** — `component.go ListComponents` no longer supports this filter 3. **Quote calculation:** always SQLite-only; do not add a live MariaDB fallback 4. **Items JSON:** prices are stored in the `items` field of the configuration, not fetched from components 5. **Migrations are additive:** already-applied migrations are skipped (checked by `id` in `local_schema_migrations`) 6. **`SyncedAt` removed:** last component sync time is now in `app_settings` (key=`last_component_sync`) --- ## Debugging Price Issues **Problem: quote returns no prices** 1. Check that `pricelist_id` is set on the configuration 2. Check that pricelist items exist: `SELECT COUNT(*) FROM local_pricelist_items` 3. Check `lookupPriceByPricelistID()` in `quote.go` 4. Verify the correct source is used (estimate/warehouse/competitor) **Problem: component sync not working** 1. Components sync as metadata only — no prices 2. Prices come via a separate pricelist sync 3. Check `SyncComponents()` and the MariaDB query **Problem: configuration refresh does not update prices** 1. Refresh uses the latest estimate pricelist by default 2. Latest resolution ignores pricelists without items (`EXISTS local_pricelist_items`) 3. Old prices in `config.items` are preserved if a line item is not found in the pricelist 4. To force a pricelist update: set `configuration.pricelist_id` 5. In configurator, `Авто` must remain auto-mode (runtime resolved ID must not be persisted as explicit selection)