# 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)
- **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)
