spree-data-model
/spree-data-model
Wyjaśnia, jak modele domenowe Spree 6 się łączą i gdzie przechowywane są dane: sklepy, produkty, koszyki, zamówienia i płatności.
- Autor
- spree ↗
- Repozytorium
- agent-skills ↗
- Licencja
- MIT
- Ostatnia zmiana repozytorium
- 30.09.2026
Ocena i bezpieczeństwo
- Pliki w paczce
- 2
- Skrypty
- 0
- Komentarze HTML
- 0
- Adresy URL w SKILL.md
- 1
Brak sygnałów. Repozytorium zmienione 30.09.2026, bez odwołań do wycofanych modeli i API.
MIT, otwarta: można używać i zmieniać, z zachowaniem informacji o autorze.
Co robi i kiedy
Wyjaśnia, jak modele domenowe Spree 6 się łączą i gdzie przechowywane są dane: sklepy, produkty, koszyki, zamówienia i płatności.
Wyzwalacze z opisu
Oryginalny opis (EN)
Use when the user asks how Spree 6's domain models relate or where something is stored — Store/Channel/Market/Catalog, Product/Variant/Price/Category/Collection, Customer/Company/CustomerGroup, Cart vs Order, LineItem, TaxLine/Discount/Fee, Fulfillment/DeliveryMethod/StockLevel, Return/Exchange/Claim, Payment/PaymentSession/Refund/StoreCredit/GiftCard, Seller/OrderGroup, prefixed ID prefixes and document numbers. Phrasings: "how does X connect to Y", "what's the difference between Cart and Order", "Store vs Channel vs Market", "what statuses can a fulfillment have", "what prefix is `ful_`", "w
Jak działa
- The big picture
- 1. Commerce axis — Store, Channel, Market, Catalog
- 2. Catalog — products and what hangs off them
- 3. Buyer — Customer, Company, CustomerGroup, Address
- 4. Purchase — Cart → Order
- 5. Money rows — TaxLine, Discount, Fee
- 6. Fulfillment, inventory and after-sales
- 7. Payments
- 8. Marketplace (brief)
- 9. Identifiers — prefixed IDs and document numbers
Podgląd SKILL.md
---
name: spree-data-model
description: Use when the user asks how Spree 6's domain models relate or where something is stored — Store/Channel/Market/Catalog, Product/Variant/Price/Category/Collection, Customer/Company/CustomerGroup, Cart vs Order, LineItem, TaxLine/Discount/Fee, Fulfillment/DeliveryMethod/StockLevel, Return/Exchange/Claim, Payment/PaymentSession/Refund/StoreCredit/GiftCard, Seller/OrderGroup, prefixed ID prefixes and document numbers. Phrasings: "how does X connect to Y", "what's the difference between Cart and Order", "Store vs Channel vs Market", "what statuses can a fulfillment have", "what prefix is `ful_`", "where are taxes stored", "how do I query orders". Architecture and relationships — for adding new models use spree-resource; for behavior use the domain skill (spree-checkout, spree-fulfillment, …).
---
# Spree Data Model (Spree 6)
A relationship map of the models you'll touch most. Field-level detail lives in `node_modules/@spree/docs/dist/developer/core-concepts/*.md`; when in doubt, read `server/`'s installed gem source (`bundle exec gem contents spree_core`, or `spree exec bundle show spree_core`).
## The big picture
```mermaid
erDiagram
Store ||--o{ Channel : "sells through"
Store ||--o{ Market : "sells into"
Store ||--o{ Catalog : "audience ranges"
Store ||--o{ Product : "owns"
Store ||--o{ Cart : "has"
Store ||--o{ Order : "records"
Product ||--o{ Variant : "has (default_variant = face)"
Variant ||--o{ Price : "per currency / price list"
Variant ||--o{ StockLevel : "per location"
StockLevel }o--|| StockLocation : "at"
Cart ||--o{ LineItem : "while shopping"
Cart ||--o| Order : "completes into"
Order ||--o{ LineItem : "copied at completion"
Order ||--o{ Fulfillment : "ships as"
Order ||--o{ Payment : "paid by"
Order ||--o{ TaxLine : "taxed by"
Order ||--o{ Discount : "reduced by"
Order ||--o{ Fee : "surcharged by"
Order ||--o{ Return : "returns"
Order }o--o| Customer : "placed by"
Order }o--o| Company : "bought for"
Fulfillment }o--|| DeliveryMethod : "via"
Fulfillment }o--|| StockLocation : "from"
Payment }o--|| PaymentMethod : "via"
```
All models are `Spree::*`, inherit `Spree.base_class`, carry a prefixed ID, and are store-scoped unless noted. **No model has a state machine** — lifecycle fields are string `status` columns (`has_status`) moved by workflows.
## 1. Commerce axis — Store, Channel, Market, Catalog
| Model | What it is | Key points |
|---|---|---|
| `Store` (`store_`) | The tenant boundary — its own catalog, orders, settings | Query through it: `store.products`, `store.orders`. `Spree::Store.default` may be nil; `Spree::Current.store` is the request's store |
| `Channel` (`ch_`) | *Where* an order comes from — online, POS, wholesale portal, app | One default per store ("Online Store"). Resolved per request from `X-Spree-Channel` (code or `ch_` id). `ProductPublication` (`pp_`) decides which products a channel lists; orders carry `channel_id` |
| `Market` (`mkt_`) | *Region* a store sells into — countries + currency + locales + tax-inclusive flag + tax provider | Resolved from the customer's country; falls back to the default market |
| `Catalog` (`cat_`) | *What an audience sees and pays* | `CatalogProduct` = assortment (empty ⇒ pricing overlay only; non-empty ⇒ restricted range), optional `PriceList`, `CatalogAssignment` to a `CustomerGroup` or `Company`, plus quantity rules and order minimums. A channel may have a `default_catalog` |
**Adding a store in code** (a platform provisioning merchants): creating a `Spree::Store` row only gets its default policies and delivery profile. Seed what it needs to trade with `Spree::Seeds::StoreResources.call(store: store)` — tax categories, channels (incl. `wholesale`), roles, digital delivery, payment methods, product types, customer groups, returns setup, commission rate, seller requirements, API keys, saved reports, allowed origins; per-store and safe to re-run, never touches other stores. Then, once you know where the merchant ships from, `Spree::Stores::ProvisionDefaults.call(store:, country:, locale: nil, currency: nil)` builds the default market, warehouse, delivery zones and pickup (first-time only — on a configured store it reads as a data reset). Demo data for that store: `bin/rails spree:load_sample_data STORE_CODE=<code>` (or `STORE_ID=store_…`) or `Spree::SampleData::LoadJob.perform_later(store.id)`.
## 2. Catalog — products and what hangs off them
| Model | Notes |
|---|---|
| `Product` (`prod_`) | `status`: `draft` / `active` / `archived` (+ `proposed` / `rejected` for marketplace review). Belongs to `ProductType`, `DeliveryProfile`, optional `Seller`, `TaxCategory` |
| `Variant` (`variant_`) | The buyable SKU. **No master variant** — every product has ≥1 variant and a real FK `product.default_variant` (`default_variant_id`), the variant that fronts price/SKU/weight for single-variant products |
| `Price` (`price_`) | `amount`, `compare_at_amount`, `currency`, optional `price_list_id`. Read/write via `variant.price_in('EUR')`, `variant.amount_in('EUR')`, `variant.set_price('EUR', 19.99, 24.99)` — there is no `variant.price` |
| `PriceList` (`pl_`) + `PriceRule` | Conditional price overrides; `status` `draft` / `active` / `inactive` / `scheduled`. See `spree-pricing` |
| `Category` (`ctg_`) | Store-owned **tree** (nested set) for navigation; products join via `ProductCategory` |
| `Collection` (`coll_`) | **Flat**, manual or rule-based (`CollectionRule`) product grouping for merchandising |
| `OptionType` (`opt_`) / `OptionValue` (`optval_`) | Size/Color; display text is `label` (not `presentation`); `option_type.color_swatch?` |
| `ProductType` (`pt_`) | Template: which option types, categories, custom field definitions and delivery profile a kind of product uses |
| `Media` (`media_`) | Images/videos, polymorphic `viewable`; `product.primary_media`; variants link through `VariantMedia` |
| `CustomFieldDefinition` (`cfdef_`) / `CustomField` (`cf_`) | Merchant-defined typed attributes on products, customers, orders…; store-scoped definitions; `storefront_visible`; `record.set_custom_field('custom.material', 'Cotton')` / `get_custom_field('custom.material')` |
Integration data that nobody edits goes in `metadata` (JSON, every major model), not custom fields. A cart's `metadata` is copied onto the order(s) it becomes at completion — and it's customer-writable through the Store API, so don't trust keys that came from it.
## 3. Buyer — Customer, Company, CustomerGroup, Address
- **Customer** (`Spree.customer_class`, default `Spree::Customer`, `cust_`) — the shopper account. **Customers are global** (not store-owned); store-specific standing (groups, companies) is evaluated per store. Has `orders`, `carts` (open `Spree::Cart`s), `addresses`, `store_credits`, `gift_cards`, `wishlists`, `customer_groups`, `companies` (through `CompanyMembership`). Staff are a separate class: `Spree.admin_user_class` (`adm_`).
- **Company** (`comp_`) — a B2B buyer organization, store-scoped, a **tree** up to five levels (`parent` / `children`); nodes are `kind` `company` (legal entity, may hold `TaxIdentifier`s) or `division`. Members via `CompanyMembership` (`cmem_`) with company roles; invites via `CompanyInvitation`. Carts/orders carry `company_id`. See `spree-b2b`.
- **CustomerGroup** (`cg_`) — store-scoped segment used by catalogs, promotions and price rules.
- **Address** (`addr_`) — polymorphic `owner` (customer or company address book). Fields `first_name`, `last_name`, `address1/2`, `city`, `postal_code`, `country_code`, `state_code`, `phone`, `company`.
## 4. Purchase — Cart → Order
| | `Spree::Cart` (`cart_`) | `Spree::Order` (`or_`) |
|---|---|---|
| Purpose | Mutable checkout state | Financial record — immutable once `placed` (`draft` orders from admin/B2B edit flows stay editable) |
| Lifecycle field | none — only `completed_at` (set when completed) | `status`: `draft` → `placed` / `canceled` (plus derived `payment_status`, `fulfillment_status`) |
| Changes via | `Spree::Carts::*` workflows (AddItem, UpsertItems, Recalculate, Complete, Merge) | `Spree::Orders::*` workflows (Cancel, admin edit twins); statuses only via `Spree::Orders::UpdateStatuses` |
| Store API | `/api/v3/store/carts` | `/api/v3/store/orders` (read), Admin API for management |
- `Spree::Carts::Complete` is the single hard gate: it re-prices, checks `Spree::Checkout::Requirements`, processes payment, and copies the cart into a new Order (`order.cart_id`; `cart.order`). The cart is kept with `completed_at` set. Checkout "steps" are advisory, derived from outstanding requirements.
- Shared behavior lives in **`Spree::Purchase::*` concerns** included by both (`Addresses`, `Currency`, `Market`, `Channel`, `Company`, `Taxation`, `Totals`, `PaymentProcessing`, `StoreCredits`, `GiftCards`, `Validations`, `Lifecycle`, `DigitalItems`, `QuantityRules`, `PurchaseOrder`, `Freight`, …; `CheckoutSteps` is cart-only). Code meant for both takes "a purchase", not an order.
- **Dual owner FKs:** `LineItem`, `Fulfillment`, `Payment`, `PaymentSession`, `TaxLine`, `Discount`, `Fee`, `StockReservation` carry both `cart_id` and `order_id` (exactly one set). Always read **`record.owner`** — never assume `.order`.
- **LineItem** (`li_`) — variant, quantity, unit price captured into the row (plus `price_list_id`, `seller_id`); has its own tax lines, discounts, fees, fulfillment items.
- `payment_status`: `none`, `authorized`, `partially_paid`, `paid`, `partially_refunded`, `refunded`, `overcharged`, `voided`. `fulfillment_status`: `unfulfilled`, `backorder`, `partial`, `fulfilled`, `delivered`, `canceled` (the validator also accepts legacy `pending`, `ready`, `shipped`, which only appear on migrated rows — tolerate them when reading, removed in 6.1). Never write these yourself.
- Querying: `store.orders.placed_orders`, `.canceled_orders`, `.drafts`; API `q[status_eq]=placed`. Carts: `store.carts.incomplete`.
## 5. Money rows — TaxLine, Discount, Fee
There is no generic adjustment table. Three typed rows (shared concern `Spree::TypedAdjustmentLine`: `amount`, `label`, `metadata`, cart/order owner):
| Row | Attaches to | Notes |
|---|---|---|
| `TaxLine` (`tl_`) | line item, fulfillment or fee | `included` (in-price) vs additional; keeps rate/label snapshot; `tax_rate` optional (provider-computed tax) |
| `Discount` (`disc_`) | line item or fulfillment | `promotion` / `promotion_action` snapshot; applied promotions tracked by `OrderPromotion` |
| `Fee` (`fee_`) | line item, fulfillment, or the purchase itself | surcharges, duties, handling |
Totals on the purchase: `item_total`, `discount_total`, `delivery_total`, `fee_total`, `tax_total` (= `included_tax_total` + `additional_tax_total`), `total`, `amount_due`. **`Spree::Carts::RecalculateTotals`** is the one seam that regenerates rows on a cart. **Placed orders are money-frozen**: `Spree::Orders::RecalculateTotals` re-sums existing rows and never re-applies today's promotions or rates. See `spree-order-totals`, `spree-taxes`.
## 6. Fulfillment, inventory and after-sales
- **Fulfillment** (`ful_`) — one parcel from one `StockLocation` via one `DeliveryMethod`; `status` `unfulfilled` → `fulfilled` → `delivered`, or `canceled` (final — no resume). Number derived from the order (`R1001-F1`). Items are `FulfillmentItem` (`fi_`, `on_hand` / `backordered` / `shipped` / `returned`). Candidate rates are `DeliveryRate` (`dr_`). Carrier journeys are `Delivery` (`dlv_`) rows; labels are `ShippingLabel` (`lbl_`).
- **Delivery setup:** `DeliveryProfile` (`fp_`, how a product ships; products reference one) → `DeliveryOriginGroup` (`og_`) → `DeliveryZone` (`dz_`) + `DeliveryMethod` (`dm_`, with a calculator, rules and a fulfillment provider type — shipping, digital, pickup).
- **Inventory:** `StockLocation` (`sloc_`) ↔ `StockLevel` (`sl_`, one per variant per location: `count_on_hand`, `backorderable`, reserved/allocated/incoming counts) → `StockMovement` (`sm_`); checkout holds via `StockReservation` (`res_`); `StockTransfer` (`st_`) and `PurchaseOrder` (`po_`) move stock in. See `spree-inventory`, `spree-fulfillment`.
- **After-sales:** `Return` (`ret_`, `requested` → `approved` → `received` → `refunded` | `canceled`) with `ReturnLineItem`; `Exchange` (`exch_`, `requested` → `approved` → `received` → `fulfilled` | `canceled`); `Claim` (`claim_`, `open` → `approved` → `resolved` | `denied` / `canceled`). Each transition is a workflow (`Spree::Returns::*`, …). See `spree-returns`.
## 7. Payments
- **PaymentMethod** (`pm_`) — store-scoped gateway config (STI subclasses, preferences for credentials).
- **PaymentSession** (`ps_`) — provider-side session (Stripe PaymentIntent, Adyen session…) for session-based methods; `status` `pending` / `processing` / `completed` / `failed` / `canceled` / `expired`. `PaymentSetupSession` (`pss_`) saves a method for later.
- **Payment** (`py_`) — `status` `checkout` / `processing` / `pending` (authorized) / `completed` (captured) / `failed` / `void` / `invalid`; polymorphic `source` (`CreditCard` `card_`, `PaymentSource` `psrc_`, `StoreCredit`, …). Number derived from the order (`R1001-P1`).
- **Refund** (`re_`) — against a payment, with a `RefundReason`. **StoreCredit** (`credit_`) — customer balance, used as a payment source. **GiftCard** (`gc_`) — `active` / `partially_redeemed` / `redeemed` / `canceled`; redeeming issues store credit. See `spree-payments`.
## 8. Marketplace (brief)
`Seller` (`sel_`, `pending` … `approved` / `rejected` / `suspended`) owns products, stock locations and delivery methods. A mixed cart completes into **one Order per seller** grouped by an `OrderGroup` (`ogrp_`, which owns the shared payment, addresses and email; its status is derived from the children). Commission lines, `SellerTransfer` and `SellerPayout` track money owed. See `spree-marketplace`.
## 9. Identifiers — prefixed IDs and document numbers
Every API-facing model has a Stripe-style prefixed ID (`Spree::PrefixedId`, `has_prefix_id :prod`): `prefix_<sqids(id)>`. The integer PK stays internal.
```ruby
product.prefixed_id # "prod_86Rf07xd4z"
Spree::Product.find_by_prefix_id!(id) # raises RecordNotFound; find_by_prefix_id returns nil
Spree::Product.decode_prefixed_id(id) # → integer PK, or nil if the ID isn't a prod_ ID
```
Common prefixes: `prod_` product, `variant_` variant, `price_`, `ctg_` category, `coll_` collection, `cart_`, `or_` order, `li_` line item, `py_` payment, `ps_` payment session, `re_` refund, `ful_` fulfillment, `dm_` delivery method, `sl_` stock level, `sloc_` stock location, `cust_` customer, `adm_` admin user, `addr_` address, `comp_` company, `mkt_` market, `ch_` channel, `sel_` seller, `media_`, `tl_` / `disc_` / `fee_` money rows, `ret_` / `exch_` / `claim_`. **Full table (~135 models): [references/prefixed-ids.md](references/prefixed-ids.md).**
Your own models get one the same way: `has_prefix_id :brand` (the `spree:api_resource` generator adds it).
**Document numbers** are separate, human-readable references: orders `R1001` (sequential by default; merchant sets format/prefix/suffix/start in Settings → Store → Order numbers), returns `RET…`, exchanges `EX…`, claims `CLM…`, imports `IM…`, exports `EF…`, purchase orders `PO…`. Fulfillments and payments derive from their order (`R1001-F1`, `R1001-P1`). Custom format: `Spree.number_generators[:order] = 'MyApp::BranchOrderNumbers'` (subclass `Spree::NumberGenerators::Base#generate(record)`; use `Spree::NumberSequence.next_value` for counters). Your own model: `has_spree_number prefix: 'CN'` + unique `number` column.
## Gotchas
- `Spree::Order` in a 5.x mental model meant "cart". In Spree 6, cart-phase code uses `Spree::Cart`; an Order only exists once placed (or as an admin draft).
- `customer.carts` returns the customer's **open `Spree::Cart` records** across all stores — scope it: `customer.carts.where(store: store)` (or `store.carts.incomplete.where(customer: customer)`). An incomplete `Spree::Order` is an admin draft, not a cart (`customer.orders.drafts`). `last_incomplete_spree_order` is deprecated (it returns drafts) and removed in 6.1.
- `Spree::Taxonomy` and `Spree::Taxon` still exist as deprecated, data-only upgrade artifacts — use `Category` / `Collection`.
- Order's `status` uses a plain `STATUSES` constant, not `has_status`, so `Spree::Order.add_status` isn't available.
- Money in the API is a string (`"135.60"`) with a `display_*` twin.
## Where to read further
- `node_modules/@spree/docs/dist/developer/core-concepts/architecture.md` — overview + ER diagram
- `…/core-concepts/{stores,channels,markets,catalogs,products,pricing,carts,orders,order-totals,taxes,discounts,fees,fulfillments,inventory,payments,returns-exchanges-claims,customers,companies,sellers}.md`
- `…/how-to/custom-document-numbers.md`
- https://spreecommerce.org/docs/developer/core-concepts/architecture
Pliki w paczce (2)
- SKILL.md 17 KB
- references/prefixed-ids.md 7 KB
Wersje
| Repozytorium | Wynik | Licencja |
|---|---|---|
| Ta strona · spree/agent-skills | 100 | MIT |
| OrcaQubits/agentic-commerce-skills-plugins | 100 | MIT |
Migawka bazy z 28.10.2026 · wersja paczki 1c0fc8677ba9 (skrót SHA-256 całej paczki)
Pobrania w ostatnich 30 dniach: 0 · ranking
Zgłoś problem z tym skillem: szkodliwa treść, błędny opis albo prośba autora o usunięcie.
Podobne skille
- 100spree-payments
Integruje bramki płatności ze Spree: model PaymentMethod, PaymentSession, Stripe, Adyen, PayPal, StoreCredit i GiftCard, zwroty, maszynę stanów płatności i własną bramkę. - 100spree-api-v3
Obsługuje REST API v3 w Spree 6: uwierzytelnianie, zakresy, identyfikatory, podział koszyka i zamówienia, format odpowiedzi i błędy. - 100spree-returns
Obsługuje w Spree 6 zwroty, wymiany i reklamacje, ich statusy, okna zwrotów oraz przyjęcie towaru na magazyn. - 100spree-checkout
Wdraża checkout Spree: maszynę stanów zamówienia, płatności i wysyłki, przepływ zwrotów, checkout gościa i API checkoutu V3.