- 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>
137 lines
4.3 KiB
Markdown
137 lines
4.3 KiB
Markdown
# 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
|
|
|
|
### 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 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)
|