Files
PriceForge/bible-local/pricelist.md
2026-03-13 10:24:34 +03:00

5.3 KiB
Raw Blame History

Pricelists

Types

PriceForge supports three pricelist types (source field):

Type Data source Status
estimate qt_lot_metadata — snapshot of current prices Active
warehouse stock_log — stock import data Active
competitor Excel exports from competitor B2B portals Active

Estimate

Source: snapshot of current component prices from qt_lot_metadata.

Filter:

  • Include only rows with current_price > 0.
  • Exclude hidden rows (is_hidden = 1).

Stores:

  • price_period_days — price calculation window
  • price_coefficient — markup coefficient
  • manual_price — manually set price
  • meta_prices — historical prices

Purpose: primary pricelist for calculations and estimates.

Categories: loaded from lot.lot_category for each component.


Warehouse

Source: stock_log (stock import records).

Rules (CRITICAL):

  1. Only mapped partnumbers — only positions with a record in qt_partnumber_book_items are included. Unmapped partnumbers must not appear in any form.
  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_avg (quantity-weighted average).
  4. Field price_method always contains "weighted_avg".
  5. These are raw stock prices without additional processing.

Categories: loaded from lot.lot_category.

Implementation: internal/warehouse/snapshot.go


Competitor

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


Data Model

type Pricelist struct {
    ID      uint   `gorm:"primaryKey"`
    Version string
    Source  string // "estimate" | "warehouse" | "competitor"
    // ...
}

type PricelistItem struct {
    ID          uint    `gorm:"primaryKey"`
    PricelistID uint
    LotName     string  `gorm:"size:255"`
    Price       float64 `gorm:"type:decimal(12,2)"`
    LotCategory *string `gorm:"column:lot_category;size:50" json:"category,omitempty"`

    // Virtual fields (via JOIN or programmatically)
    LotDescription string   `gorm:"-:migration" json:"lot_description,omitempty"`
    AvailableQty   *float64 `gorm:"-" json:"available_qty,omitempty"`
    Partnumbers    []string `gorm:"-" json:"partnumbers,omitempty"`
}

gorm:"-:migration" — no DB column created, but mapped on SELECT. gorm:"-" — fully ignored in all DB operations.


Categories (lot_category)

  • Source: lot.lot_category column in table lot.
  • NOT from qt_lot_metadata.
  • NOT derived from LOT name.
  • Persisted into qt_pricelist_items.lot_category when pricelist is created.
  • JSON field name: "category".
  • JOIN with lot is only needed for lot_description; category is already in qt_pricelist_items.
  • Default value when category is missing: PART_.

Pricelist Creation (background task)

Creation runs via Task Manager. Handler returns task_id; frontend polls.

POST /api/pricelists/create
→ { "task_id": "uuid" }
→ polling GET /api/tasks/:id
→ { "status": "completed", "result": { "pricelist_id": 42 } }

Task type: TaskTypePricelistCreate.

Implementation:

  • Service: internal/services/pricelist/service.go
  • Warehouse calc: internal/warehouse/snapshot.go
  • Handler: internal/handlers/pricelist.go

Pricelist Deletion Guard

CountUsage(id) checks qt_configurations for references across all three pricelist columns: pricelist_id, warehouse_pricelist_id, competitor_pricelist_id.

A pricelist is only deletable when all three counts are zero.


Competitor Pricelist

  • Source: competitor
  • Rebuilt via: POST /api/competitors/pricelist (task type: competitor_import)
  • Logic: Latest quote per (competitor_id, partnumber) across ALL active competitors → apply each competitor's expected_discount_pctweighted_median per lot → insert into qt_pricelist_items
  • price_method: weighted_median, price_period_days: 0
  • Quotes stored in partnumber_log_competitors; unmapped p/ns recorded in qt_vendor_partnumber_seen with source_type = 'competitor:<code>'