Files
QuoteForge/bible/07-dev.md
Michael Chus d0400b18a3 feat(vendor-spec): BOM import, LOT autocomplete, pricing, partnumber_seen push
- BOM paste: auto-detect columns by content (price, qty, PN, description);
  handles $5,114.00 and European comma-decimal formats
- LOT input: HTML5 datalist rebuilt on each renderBOMTable from allComponents;
  oninput updates data only (no re-render), onchange validates+resolves
- BOM persistence: PUT handler explicitly marshals VendorSpec to JSON string
  (GORM Update does not reliably call driver.Valuer for custom types)
- BOM autosave after every resolveBOM() call
- Pricing tab: async renderPricingTab() calls /api/quote/price-levels for all
  resolved LOTs directly — Estimate prices shown even before cart apply
- Unresolved PNs pushed to qt_vendor_partnumber_seen via POST
  /api/sync/partnumber-seen (fire-and-forget from JS)
- sync.PushPartnumberSeen(): upsert with ON DUPLICATE KEY UPDATE last_seen_at
- partnumber_books: pull ALL books (not only is_active=1); re-pull items when
  header exists but item count is 0; fallback for missing description column
- partnumber_books UI: collapsible snapshot section (collapsed by default),
  pagination (10/page), sync button always visible in header
- vendorSpec handlers: use GetConfigurationByUUID + IsActive check (removed
  original_username from WHERE — GetUsername returns "" without JWT)
- bible/09-vendor-spec.md: updated with all architectural decisions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 22:21:13 +03:00

4.3 KiB

07 — Development

Commands

# 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

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

// 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 removedcomponent.go ListComponents no longer supports this filter
  3. Quote calculation: always offline-first (SQLite); online path is separate
  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)