Modularize Go files, extract JS to static, implement competitor pricelists

Go refactoring:
- Split handlers/pricing.go (2446→291 lines) into 5 focused files
- Split services/stock_import.go (1334→~400 lines) into stock_mappings.go + stock_parse.go
- Split services/sync/service.go (1290→~250 lines) into 3 files

JS extraction:
- Move all inline <script> blocks to web/static/js/ (6 files)
- Templates reduced: admin_pricing 2873→521, lot 1531→304, vendor_mappings 1063→169, etc.

Competitor pricelists (migrations 033-039):
- qt_competitors + partnumber_log_competitors tables
- Excel import with column mapping, dedup, bulk insert
- p/n→lot resolution via weighted_median, discount applied
- Unmapped p/ns written to qt_vendor_partnumber_seen
- Quote counts (unique/total) shown on /admin/competitors
- price_method="weighted_median", price_period_days=0 stored explicitly

Fix price_method/price_period_days for warehouse items:
- warehouse: weighted_avg, period=0
- competitor: weighted_median, period=0
- Removes misleading DB defaults (was: median/90)

Update bible: architecture.md, pricelist.md, history.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-03-13 07:44:10 +03:00
parent c0fecde34e
commit f48615e8a9
41 changed files with 11822 additions and 9008 deletions

View File

@@ -8,7 +8,7 @@ PriceForge supports three pricelist types (`source` field):
|------|-------------|--------|
| `estimate` | `qt_lot_metadata` — snapshot of current prices | Active |
| `warehouse` | `stock_log` — stock import data | Active |
| `competitor` | B2B marketplace APIs | Reserved |
| `competitor` | Excel exports from competitor B2B portals | Active |
---
@@ -52,7 +52,22 @@ PriceForge supports three pricelist types (`source` field):
## Competitor
Reserved for future integration with B2B marketplace APIs (automated quotes).
**Source**: Excel exports from competitor B2B portals, uploaded manually via `/admin/competitors`.
**Rules (CRITICAL)**:
1. **Only mapped partnumbers** — only positions with a record in `qt_partnumber_book_items` are included. Unmapped p/ns are counted and written to `qt_vendor_partnumber_seen` (source_type = `"competitor:<code>"`).
2. **No pricing settings** — do not load from `lot_metadata`: no `price_period_days`, `price_coefficient`, `manual_price`, `meta_prices`.
3. **Price method**: always `weighted_median` (quantity-weighted median across all matched p/ns for a lot).
4. Field `price_method` always contains `"weighted_median"`, `price_period_days` always `0`.
5. **Discount**: effective price = `weighted_median × (1 expected_discount_pct / 100)`.
6. **Deduplication**: if price and qty for a p/n are unchanged since last import — skip insert into `partnumber_log_competitors`.
**Key tables**:
- `qt_competitors` — competitor profiles (name, code, discount %, column mapping JSON, currency)
- `partnumber_log_competitors` — historical quote log (competitor_id, partnumber, price USD, price local, qty, date)
**Implementation**: `internal/services/competitor_import.go`, `internal/repository/competitor.go`
---