Corp/backend/commerce/README.md
2026-08-17 19:31:56 +03:00

70 lines
3.0 KiB
Markdown

# Commerce analytics core
`commerce` is the product-centric analytics boundary of EUSRR. It does not
replace an operational CRM or warehouse. External and future internal systems
all supply records to the same canonical product model.
## Stable model
- `Product` is one sellable or stockable SKU. `parent` groups variants.
- `SourceRecord` retains the latest raw payload with source provenance.
- `ProductBinding` maps a source product/offer identifier to `Product`.
- `Sale` and `SaleLine` contain normalized product-level sales facts.
- `Cost` contains commissions, delivery, advertising and other expenses.
- `StockSnapshot` contains point-in-time stock observations by source and
warehouse.
Money is never aggregated across currencies. Returns are represented as a
`Sale` with `event_type="return"`; line values remain positive and analytics
applies the sign.
## Integration contract
An adapter subclasses `commerce.integrations.SourceAdapter`, handles API
pagination internally and yields `ExternalRecord` objects. Register it in
`adapter_registry`, then invoke the Celery task `commerce.sync_source`.
Adapters must not store credentials in `SourceSystem.metadata`; secrets belong
in environment-backed configuration or a dedicated encrypted credential
store.
`IntegrationConnection` is that encrypted credential boundary for connections
created by employees. The first production connector is `moysklad`: it scans
the complete paginated assortment, aggregate stock, shipments and customer
returns. Raw API payloads are retained in `SourceRecord` before normalization.
Repeated scans update the same source identities and append stock observations.
Celery Beat checks due connections every minute. Each connection stores whether
automatic synchronization is enabled, its interval (15 minutes by default) and
the next planned run; row locking and connection status prevent overlapping
full scans. Both a Celery worker and Celery Beat must be running.
Imported cards are matched by an existing source binding, SKU or barcode. A
card that cannot be matched safely is created separately and can later be
merged into a canonical product through `products/{id}/merge/`; bindings and
normalized facts move with it.
Source-specific code must not create alternative product models. It stores the
raw payload first and then resolves a product through `ProductBinding` or
`commerce.services.match_product` before writing normalized facts.
## API
The authenticated API is mounted at `/api/v1/commerce/`:
- `products/`
- `products/{id}/analytics/`
- `products/{id}/merge/`
- `connections/` (`test/` and `sync/` actions)
- `sync-runs/`
- `sources/`
- `product-bindings/`
- `sales/`
- `sale-lines/`
- `costs/`
- `stock-snapshots/`
All employees may read. Writes require staff status or the corresponding
Django model permission. The frontend management screen can bootstrap and
correct normalized records manually; integrations should still write them
through application services so raw provenance and idempotency are preserved.