142 lines
5.0 KiB
Markdown
142 lines
5.0 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
|
|
- 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)
|