Files
PriceForge/bible/history.md
2026-02-20 19:01:07 +03:00

159 lines
5.6 KiB
Markdown

# Change History
> Architectural decisions and significant refactoring are recorded here.
> Every architectural decision MUST be documented in this file.
---
## 2026-02-20: Pricelist Formation Hardening (Estimate/Warehouse/Meta)
### Decision
Hardened pricelist formation rules to remove ambiguity and prevent silent data loss in UI/API.
### What changed
- `estimate` snapshot now explicitly excludes hidden components (`qt_lot_metadata.is_hidden = 1`).
- Category snapshot in `qt_pricelist_items.lot_category` now always has a deterministic fallback:
`PART_` is assigned even when a LOT row is missing in `lot`.
- `PricelistItem` JSON contract was normalized to a single category field
(`LotCategory -> json:"category"`), removing duplicate JSON-tag ambiguity.
- Meta-price source expansion now always includes the base LOT, then merges `meta_prices`
sources with deduplication (exact + wildcard overlaps).
### Rationale
- Prevent hidden/ignored components from leaking into estimate pricelists.
- Keep frontend category rendering stable for all items.
- Avoid non-deterministic JSON serialization when duplicate tags are present.
- Ensure meta-article pricing includes self-history and does not overcount duplicate sources.
### Constraints
- `estimate` pricelist invariant: `current_price > 0 AND is_hidden = 0`.
- `category` in API must map from persisted `qt_pricelist_items.lot_category`.
- Missing category source must default to `PART_`.
- Meta source list must contain each LOT at most once.
### Files
- Model: `internal/models/pricelist.go`
- Estimate/Warehouse snapshot service: `internal/services/pricelist/service.go`
- Pricing handler/meta expansion: `internal/handlers/pricing.go`
- Pricing service/meta expansion: `internal/services/pricing/service.go`
- Tests:
- `internal/services/pricelist/service_estimate_test.go`
- `internal/services/pricelist/service_warehouse_test.go`
- `internal/handlers/pricing_meta_expand_test.go`
- `internal/services/pricing/service_meta_test.go`
- `internal/services/stock_import_test.go`
---
## 2026-02-20: Seen Registry Deduplication by Partnumber
### Decision
Changed `qt_vendor_partnumber_seen` semantics to one row per `partnumber` (vendor/source are no longer part of uniqueness).
### Rationale
- Eliminates duplicate seen rows when the same partnumber appears both with vendor and without vendor.
- Keeps ignore behavior consistent regardless of vendor presence.
- Simplifies operational cleanup and prevents re-creation of vendor/no-vendor duplicates.
### Constraints
- `partnumber` is now the unique key in seen registry.
- Ignore checks are resolved by `partnumber` only.
- Stock provenance must be preserved (`source_type='stock'`) when stock data exists for the partnumber.
### Files
- Migration: `migrations/025_dedup_vendor_seen_by_partnumber.sql`
- Service: `internal/services/stock_import.go`
- Service: `internal/services/vendor_mapping.go`
- Model: `internal/models/lot.go`
---
## 2026-02-18: Global Vendor Partnumber Mapping
### Decision
Implemented global vendor partnumber → LOT mapping with bundle support and seen-based ignore logic.
### Key rules
- `lot_partnumbers` is the canonical mapping contract (1:1 per key).
- Composite mappings use internal bundle tables (`qt_lot_bundles`, `qt_lot_bundle_items`).
- Ignore logic moved from `stock_ignore_rules` to `qt_vendor_partnumber_seen.is_ignored`.
- Resolver order: exact `(vendor, partnumber)` → fallback `(vendor='', partnumber)`.
### Rationale
- Preserves external/client contracts (`lot_partnumbers`, LOT-based pricelists).
- Avoids multi-row ambiguity in `lot_partnumbers`.
- Supports complex assembled vendor SKUs without client-side changes.
- Centralizes ignore behavior across all sources via seen-registry.
### Constraints
- Bundle LOT is internal and must stay hidden in regular LOT list by default.
- Resolver order is mandatory and fixed.
- Bundle allocation for missing estimate: fallback from previous active warehouse pricelist; if absent → `0`.
### Files
- Migration: `migrations/023_vendor_partnumber_global_mapping.sql`
- Backfill: `migrations/024_backfill_vendor_seen_from_stock_and_ignore.sql`
- Resolver: `internal/lotmatch/matcher.go`
- Service: `internal/services/vendor_mapping.go`
- Warehouse calc: `internal/warehouse/snapshot.go`
- Stock import: `internal/services/stock_import.go`
- API: `internal/handlers/pricing.go`, `cmd/pfs/main.go`
---
## 2026-02-10: LOT Page Refactoring
### Decision
Moved LOT management to a dedicated `/lot` page. Removed LOT tab from Pricing Admin.
### What changed
- Created `web/templates/lot.html` — two tabs: LOT (component management) and Mappings (partnumber ↔ LOT).
- Removed LOT tab from `admin_pricing.html`; default tab changed to `estimate`.
- Removed "Сопоставление partnumber → LOT" section from Warehouse tab.
- Updated navigation: LOT → `/lot`, Pricing Admin → `/admin/pricing`.
- Added `Lot()` handler in `internal/handlers/web.go`.
- Added `/lot` route in `cmd/pfs/main.go`.
### Rationale
Separation of concerns: LOT/component management is distinct from pricing administration.
---
## Warehouse Pricing: Weighted Average
### Decision
Warehouse pricelist uses `weighted_avg` (quantity-weighted average) as the sole price calculation method.
Commit `edff712` switched from `weighted_median` to `weighted_avg`.
- `price_method` field always contains `"weighted_avg"`.
- No `price_period_days`, `price_coefficient`, `manual_price`, `meta_prices` in warehouse pricelists.
---
## Architecture Conventions
> All future architectural decisions must be documented here with:
> - Date
> - Decision summary
> - Rationale
> - Constraints / invariants
> - Affected files