Offers

NOTE

Статус: Live (HEAD 5e4ce54, 2026-05-06). Read-model для supplier-offers; live-fetch UPSERT добавлен в Live-B/C/D циклах. Admin read endpoint работает.

Offers BC хранит наблюдения по предложениям поставщиков (SupplierOffer aggregate) и поддерживает write-port для live-fetch warmup из других BC. Это не канонический товар — это снимки конкретных позиций конкретных поставщиков с привязкой к credential, по которой получены.

Назначение

Хранить per-(offer × credential × seller × observed_at) снимки price/stock/delivery от поставщиков. Source-of-truth для proposal pipeline на read-side и для downstream BC (matching, charnorm).

Статус документа

Область действия

Входит:

  • SupplierOffer aggregate (offerID, supplier_ref, supplier_code, manufacturer, etc).
  • Observation read repo — основной потребитель ingestion (write через offers/app).
  • ObservationWriter port — Live-B пишет fresh price; Live-C пишет stock/delivery.
  • Admin endpoint /api/v1/admin/offers/supplier-offers.

Не входит:

  • Канонический товар (catalog/canonical).
  • Matching offer ↔ canonical (matching BC).
  • Visibility filtering (visibility BC применяется на proposal-edge).

Публичный контракт

Вход

HTTP (admin):

  • GET /api/v1/admin/offers/supplier-offers — list по фильтру (supplier, manufacturer, code).

Internal Go API:

  • OffersWriter.UpsertObservation(...) — пишут ingestion + Live-B/C.
  • OffersReader.GetByID(...) / .ListByCanonical(...) / etc — для matching, proposal.
  • ObservationWriter.UpsertLivePrice(...) / UpsertLiveStock(...) — Live-B/C cache warm.

Выход

  • Outbox events offer.observation.v1 (наполняется ingestion’ом или live-fetch warm).

Внутренняя архитектура

  • domain/offer.goSupplierOffer aggregate + IDs (OfferID).
  • domain/value_objects.goObservation, WarehouseStock, DeliveryWindow.
  • domain/ports.go — Reader + Writer + ObservationWriter (live-write).
  • domain/repository.go — domain port.
  • app/UpsertObservation — main use case.
  • infra/pg/ — Repo + ON CONFLICT logic per Live-B unique key (offer_id, credential_ref, seller_ref, observed_at) с NULLS NOT DISTINCT.

Зависимости

  • PostgreSQLsupplier_offer + supplier_offer_observation.
  • catalog/canonical — read-only (через mapping в matching BC).

Хранилище

ТаблицаНазначение
supplier_offeraggregate metadata (supplier_ref + supplier_code key)
supplier_offer_observationper-observation snapshot (price/stock/delivery + observed_at + credential_ref + seller_ref)
outbox_eventspublish

Конфигурация

Per-BC env vars не требуются (всё через PG pool из api-server).

Тестирование

  • Unit: go test ./internal/core/offers/...
  • Integration: -tags=integration для Repo (UNIQUE с NULLS NOT DISTINCT specifics).

Наблюдаемость

  • Metrics: offers_observations_written_total{source="ingestion|live"}, offers_observations_read_total{purpose}.
  • Phase logs: offers.upsert.phase.

Открытые вопросы / TODO

  • Public read endpoint /v1/offers/{id} (target spec, не активирован).

Связанные документы