@mercurjs/docs 2.3.8-canary.3 → 2.3.8-canary.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/content/learn/architecture.mdx +166 -263
  2. package/content/platform/attribute/concepts/attribute-types.mdx +73 -46
  3. package/content/platform/attribute/concepts/global-vs-inline.mdx +54 -32
  4. package/content/platform/attribute/concepts/variant-axes.mdx +54 -32
  5. package/content/platform/attribute/guides/attach-attributes-to-a-product.mdx +96 -53
  6. package/content/platform/attribute/guides/create-a-variant-axis.mdx +106 -57
  7. package/content/platform/attribute/guides/create-an-attribute.mdx +105 -56
  8. package/content/platform/attribute/overview.mdx +37 -33
  9. package/content/platform/attribute/reference/data-models.mdx +53 -38
  10. package/content/platform/attribute/reference/events.mdx +47 -24
  11. package/content/platform/attribute/reference/links.mdx +98 -27
  12. package/content/platform/attribute/reference/service.mdx +71 -32
  13. package/content/platform/attribute/reference/workflows.mdx +158 -28
  14. package/content/platform/catalog/concepts/master-products.mdx +39 -37
  15. package/content/platform/catalog/concepts/product-seller-allowlist.mdx +56 -35
  16. package/content/platform/catalog/concepts/status-lifecycle.mdx +55 -49
  17. package/content/platform/catalog/concepts/variants-categories-collections.mdx +55 -35
  18. package/content/platform/catalog/guides/allowlist-stores.mdx +100 -37
  19. package/content/platform/catalog/guides/create-a-master-product.mdx +124 -48
  20. package/content/platform/catalog/guides/publish-or-reject-a-product.mdx +120 -55
  21. package/content/platform/catalog/overview.mdx +39 -35
  22. package/content/platform/catalog/reference/data-models.mdx +154 -43
  23. package/content/platform/catalog/reference/events.mdx +41 -25
  24. package/content/platform/catalog/reference/links.mdx +196 -28
  25. package/content/platform/catalog/reference/service.mdx +57 -35
  26. package/content/platform/catalog/reference/workflows.mdx +124 -30
  27. package/content/platform/commission/concepts/order-commission-lines.mdx +54 -52
  28. package/content/platform/commission/concepts/rule-matching.mdx +53 -63
  29. package/content/platform/commission/concepts/rules-and-rates.mdx +61 -59
  30. package/content/platform/commission/guides/batch-update-rules.mdx +69 -41
  31. package/content/platform/commission/guides/create-a-rate.mdx +101 -53
  32. package/content/platform/commission/guides/refresh-order-commission-lines.mdx +104 -35
  33. package/content/platform/commission/overview.mdx +43 -35
  34. package/content/platform/commission/reference/data-models.mdx +59 -45
  35. package/content/platform/commission/reference/events.mdx +28 -34
  36. package/content/platform/commission/reference/links.mdx +42 -33
  37. package/content/platform/commission/reference/service.mdx +103 -28
  38. package/content/platform/commission/reference/workflows.mdx +101 -27
  39. package/content/platform/offer/concepts/pricing-and-inventory.mdx +63 -49
  40. package/content/platform/offer/concepts/shipping.mdx +28 -29
  41. package/content/platform/offer/concepts/what-is-an-offer.mdx +48 -45
  42. package/content/platform/offer/guides/bulk-create-offers.mdx +97 -61
  43. package/content/platform/offer/guides/create-an-offer.mdx +112 -60
  44. package/content/platform/offer/guides/manage-offer-inventory.mdx +78 -38
  45. package/content/platform/offer/overview.mdx +39 -32
  46. package/content/platform/offer/reference/data-models.mdx +77 -47
  47. package/content/platform/offer/reference/events.mdx +44 -18
  48. package/content/platform/offer/reference/links.mdx +92 -40
  49. package/content/platform/offer/reference/service.mdx +66 -31
  50. package/content/platform/offer/reference/workflows.mdx +146 -37
  51. package/content/platform/order-group/concepts/computed-totals.mdx +40 -37
  52. package/content/platform/order-group/concepts/order-splitting.mdx +41 -39
  53. package/content/platform/order-group/concepts/the-order-group.mdx +39 -32
  54. package/content/platform/order-group/guides/list-order-groups.mdx +96 -56
  55. package/content/platform/order-group/guides/retrieve-an-order-group.mdx +93 -42
  56. package/content/platform/order-group/guides/split-a-cart.mdx +115 -40
  57. package/content/platform/order-group/overview.mdx +42 -32
  58. package/content/platform/order-group/reference/data-models.mdx +48 -31
  59. package/content/platform/order-group/reference/events.mdx +29 -17
  60. package/content/platform/order-group/reference/links.mdx +90 -29
  61. package/content/platform/order-group/reference/service.mdx +55 -31
  62. package/content/platform/order-group/reference/workflows.mdx +83 -24
  63. package/content/platform/payout/concepts/account-lifecycle.mdx +64 -67
  64. package/content/platform/payout/concepts/accounts-and-onboarding.mdx +50 -40
  65. package/content/platform/payout/concepts/payout-pipeline.mdx +70 -73
  66. package/content/platform/payout/guides/create-a-payout-account.mdx +65 -41
  67. package/content/platform/payout/guides/process-a-provider-webhook.mdx +84 -38
  68. package/content/platform/payout/guides/start-provider-onboarding.mdx +90 -46
  69. package/content/platform/payout/overview.mdx +43 -34
  70. package/content/platform/payout/reference/data-models.mdx +63 -36
  71. package/content/platform/payout/reference/events.mdx +56 -30
  72. package/content/platform/payout/reference/links.mdx +52 -22
  73. package/content/platform/payout/reference/service.mdx +72 -33
  74. package/content/platform/payout/reference/workflows.mdx +80 -19
  75. package/content/platform/product-edit/concepts/change-actions.mdx +52 -45
  76. package/content/platform/product-edit/concepts/change-pipeline.mdx +60 -52
  77. package/content/platform/product-edit/concepts/status-and-auto-confirm.mdx +43 -46
  78. package/content/platform/product-edit/guides/confirm-or-decline-a-change.mdx +104 -56
  79. package/content/platform/product-edit/guides/edit-a-product.mdx +126 -46
  80. package/content/platform/product-edit/guides/request-a-revision.mdx +99 -39
  81. package/content/platform/product-edit/overview.mdx +41 -34
  82. package/content/platform/product-edit/reference/data-models.mdx +73 -47
  83. package/content/platform/product-edit/reference/events.mdx +39 -29
  84. package/content/platform/product-edit/reference/links.mdx +32 -24
  85. package/content/platform/product-edit/reference/service.mdx +45 -26
  86. package/content/platform/product-edit/reference/workflows.mdx +173 -31
  87. package/content/platform/review/concepts/product-vs-seller-reviews.mdx +42 -41
  88. package/content/platform/review/concepts/ratings-and-moderation.mdx +45 -44
  89. package/content/platform/review/concepts/the-review-model.mdx +52 -45
  90. package/content/platform/review/guides/compute-aggregate-ratings.mdx +62 -37
  91. package/content/platform/review/guides/create-a-review.mdx +109 -39
  92. package/content/platform/review/guides/moderate-a-review.mdx +83 -37
  93. package/content/platform/review/guides/respond-to-a-review.mdx +85 -40
  94. package/content/platform/review/overview.mdx +43 -33
  95. package/content/platform/review/reference/data-models.mdx +41 -27
  96. package/content/platform/review/reference/events.mdx +55 -37
  97. package/content/platform/review/reference/links.mdx +80 -22
  98. package/content/platform/review/reference/service.mdx +61 -33
  99. package/content/platform/review/reference/workflows.mdx +80 -24
  100. package/content/platform/store/concepts/lifecycle.mdx +68 -44
  101. package/content/platform/store/concepts/store-entity.mdx +45 -27
  102. package/content/platform/store/concepts/team.mdx +56 -35
  103. package/content/platform/store/guides/create-a-store.mdx +102 -35
  104. package/content/platform/store/guides/manage-the-team.mdx +122 -32
  105. package/content/platform/store/guides/moderate-a-store.mdx +107 -38
  106. package/content/platform/store/overview.mdx +41 -34
  107. package/content/platform/store/reference/data-models.mdx +132 -57
  108. package/content/platform/store/reference/events.mdx +42 -17
  109. package/content/platform/store/reference/links.mdx +67 -49
  110. package/content/platform/store/reference/service.mdx +105 -33
  111. package/content/platform/store/reference/workflows.mdx +304 -24
  112. package/llms.txt +111 -111
  113. package/package.json +1 -1
@@ -1,22 +1,55 @@
1
1
  ---
2
- title: "Event reference"
2
+ title: "Offer events"
3
3
  sidebarTitle: "Events"
4
- description: "Events emitted by the Offer domain, for subscribers and side effects."
4
+ description: "Events the offer workflows emit, with their payloads, for subscribers and side effects."
5
5
  ---
6
6
 
7
- The Offer domain emits events as offers change. Subscribe to them to run side
8
- effects instead of polling, such as re-indexing search, syncing external
9
- systems, or kicking off follow-up workflows.
7
+ The offer and offer condition workflows emit events after they write. Subscribe to them to reindex search, sync an external system, or start a follow-up workflow instead of polling.
8
+
9
+ ## Offer events
10
+
11
+ The offer workflows emit one event per affected offer. The names come from `OfferWorkflowEvents`.
12
+
13
+ | Event | Emitted when | Payload |
14
+ | --- | --- | --- |
15
+ | `offer.created` | `createOffersWorkflow` creates an offer | `{ id, product_id }` |
16
+ | `offer.updated` | `updateOffersWorkflow` updates an offer | `{ id, product_id }` |
17
+ | `offer.updated` | `batchOfferInventoryItemsWorkflow` changes an offer’s inventory links | `{ id }` |
18
+ | `offer.deleted` | `deleteOffersWorkflow` soft-deletes an offer | `{ id, product_id }` |
19
+
20
+ <Note>
21
+ `upsertOfferPricesWorkflow` emits no event of its own. When `updateOffersWorkflow` runs it, `offer.updated` covers the change. When you run it directly, use its `offerPricesUpserted` hook.
22
+ </Note>
23
+
24
+ ## Offer condition events
25
+
26
+ The condition workflows emit one event per condition. The names come from `OfferConditionWorkflowEvents`.
27
+
28
+ | Event | Emitted when | Payload |
29
+ | --- | --- | --- |
30
+ | `offer_condition.created` | `createOfferConditionsWorkflow` creates a condition | `{ id }` |
31
+ | `offer_condition.updated` | `updateOfferConditionsWorkflow` updates a condition | `{ id }` |
32
+ | `offer_condition.deleted` | `deleteOfferConditionsWorkflow` deletes a condition | `{ id }` |
33
+
34
+ ## Subscribe to an offer event
35
+
36
+ A subscriber receives the payload and the container. This one reads the new offer with its store.
10
37
 
11
38
  ```ts title="src/subscribers/offer-created.ts"
12
39
  import type { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"
40
+ import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
13
41
 
14
42
  export default async function offerCreatedHandler({
15
43
  event,
16
44
  container,
17
45
  }: SubscriberArgs<{ id: string; product_id: string }>) {
18
- const offerId = event.data.id
19
- // ...re-index the offer, notify the store, etc.
46
+ const query = container.resolve(ContainerRegistrationKeys.QUERY)
47
+ const { data: [offer] } = await query.graph({
48
+ entity: "offer",
49
+ fields: ["id", "sku", "seller.name"],
50
+ filters: { id: event.data.id },
51
+ })
52
+ container.resolve("logger").info(`${offer.seller.name} listed ${offer.sku}`)
20
53
  }
21
54
 
22
55
  export const config: SubscriberConfig = {
@@ -24,16 +57,9 @@ export const config: SubscriberConfig = {
24
57
  }
25
58
  ```
26
59
 
27
- ## Offer events
60
+ Payloads carry ids only, so query the offer for the fields you need. For an `offer.deleted` subscriber, read values from the `offersDeleted` hook instead, since the row is already soft-deleted.
28
61
 
29
- | Event | Emitted when | Payload |
30
- | --- | --- | --- |
31
- | `offer.created` | An offer is created | `{ id, product_id }` |
32
- | `offer.updated` | An offer's row, prices, or inventory links change | `{ id, product_id }` |
33
- | `offer.deleted` | An offer is deleted | `{ id, product_id }` |
62
+ ## Related
34
63
 
35
- <Note>
36
- `createOffersWorkflow` and `updateOffersWorkflow` emit one event per affected
37
- offer. `batchOfferInventoryItemsWorkflow` emits `offer.updated` with a single
38
- `{ id }` payload for the batched offer.
39
- </Note>
64
+ - [Offer workflows](/platform/offer/reference/workflows)
65
+ - [Offer module links](/platform/offer/reference/links)
@@ -1,60 +1,112 @@
1
1
  ---
2
- title: "Links to other modules"
2
+ title: "Offer module links"
3
3
  sidebarTitle: "Links"
4
- description: "How the Offer domain links to other modules across the marketplace."
4
+ description: "Every module link that connects offers to stores, the catalog, pricing, inventory, fulfillment, carts, and orders."
5
5
  ---
6
6
 
7
- Modules in Mercur never reference each other directly. They connect through
8
- **module links**. The offer sits at the center of the marketplace, so it links
9
- out to the store, the master catalog, pricing, inventory, fulfillment, and the
10
- cart and order line items that reference it. Once a link is defined, you retrieve
11
- related records with `query.graph` using the link alias.
7
+ The Offer module never references another module directly. Eight module links in `packages/core/src/links/` connect the offer to the rest of the marketplace, and `query.graph` follows them by alias.
8
+
9
+ | Link | Other side | Kind | File |
10
+ | --- | --- | --- | --- |
11
+ | Offer to store | `Seller` | Read-only, on `seller_id` | `offer-seller-link.ts` |
12
+ | Offer to product | `Product` | Read-only, on `product_id` | `offer-product-link.ts` |
13
+ | Offer to variant | `ProductVariant` | Read-only, on `variant_id` | `offer-variant-link.ts` |
14
+ | Offer to shipping profile | `ShippingProfile` | Read-only, on `shipping_profile_id` | `offer-shipping-profile-link.ts` |
15
+ | Offer to price | `Price` | Writable, one offer to many prices | `offer-price-link.ts` |
16
+ | Offer to inventory item | `InventoryItem` | Writable, many to many | `offer-inventory-item-link.ts` |
17
+ | Cart line item to offer | Cart `LineItem` | Writable, many items to one offer | `cart-line-item-offer-link.ts` |
18
+ | Order line item to offer | `OrderLineItem` | Writable, many items to one offer | `order-line-item-offer-link.ts` |
19
+
20
+ ## Offer to store
21
+
22
+ Each offer belongs to one store. The link reads `offer.seller_id`, so you write the store by setting that field, not through the link.
23
+
24
+ ```ts
25
+ const { data: offers } = await query.graph({
26
+ entity: "offer",
27
+ fields: ["id", "sku", "seller.name"],
28
+ filters: { seller_id: "sel_01JD8Q1234567" },
29
+ })
30
+ ```
31
+
32
+ ## Offer to product and variant
33
+
34
+ Each offer points at one master product and one master variant. Both links are read-only and read `offer.product_id` and `offer.variant_id`.
35
+
36
+ ```ts
37
+ const { data: offers } = await query.graph({
38
+ entity: "offer",
39
+ fields: ["id", "product.title", "product_variant.title"],
40
+ filters: { product_id: "prod_01JD8Q1234567" },
41
+ })
42
+ ```
43
+
44
+ The variant alias is `product_variant`. Use `product_variant.price_set.id` to reach the shared price set that holds the offer’s prices.
45
+
46
+ ## Offer to shipping profile
47
+
48
+ Each offer ships with one profile, read from `offer.shipping_profile_id`.
12
49
 
13
50
  ```ts
14
51
  const { data: offers } = await query.graph({
15
52
  entity: "offer",
16
- fields: ["id", "sku", "prices.*", "inventory_items.*", "seller.name"],
53
+ fields: ["id", "shipping_profile.id", "shipping_profile.name"],
17
54
  })
18
55
  ```
19
56
 
20
- ## Catalog & store
57
+ ## Offer to price
58
+
59
+ One offer has many prices. The link records which rows on the variant’s shared price set belong to the offer. The offer workflows create and dismiss these links, so don’t write them yourself.
60
+
61
+ ```ts
62
+ const { data: offers } = await query.graph({
63
+ entity: "offer",
64
+ fields: [
65
+ "prices.amount",
66
+ "prices.currency_code",
67
+ "prices.min_quantity",
68
+ "prices.price_rules.attribute",
69
+ "prices.price_rules.value",
70
+ ],
71
+ })
72
+ ```
21
73
 
22
- | Linked module | Relationship |
23
- | --- | --- |
24
- | **Product** | An offer points at one master product (`offer.product_id`, read-only). |
25
- | **Product variant** | An offer points at one master variant (`offer.variant_id`, read-only). |
26
- | **Seller** | An offer belongs to one store (`offer.seller_id`, read-only). |
74
+ Every row returned also carries an `offer_id` price rule with the offer’s id.
27
75
 
28
- ## Pricing & inventory
76
+ ## Offer to inventory item
29
77
 
30
- | Linked module | Relationship |
31
- | --- | --- |
32
- | **Price** | An offer has many prices: a writable list-link to the offer-owned rows on the master variant's shared price set. |
33
- | **Inventory item** | An offer has many inventory items: a writable list-link (`offer_inventory_item`) whose pivot carries a `required_quantity` column. |
78
+ Offers and inventory items link many to many. The link table is `offer_inventory_item`, with an extra `required_quantity` integer column that defaults to `1`.
34
79
 
35
- ## Fulfillment
80
+ The link has three aliases:
36
81
 
37
- | Linked module | Relationship |
38
- | --- | --- |
39
- | **Shipping profile** | An offer ships with one shipping profile (`offer.shipping_profile_id`, read-only). |
82
+ - **`offer.inventory_items`**: the linked `InventoryItem` rows, without pivot columns
83
+ - **`offer.inventory_item_link`**: the pivot rows, with `required_quantity` and a nested `inventory_item`
84
+ - **`inventory_item.offers`**: the offers that draw on an inventory item
40
85
 
41
- ## Cart & order
86
+ ```ts
87
+ const { data: offers } = await query.graph({
88
+ entity: "offer",
89
+ fields: [
90
+ "inventory_item_link.required_quantity",
91
+ "inventory_item_link.inventory_item.sku",
92
+ ],
93
+ })
94
+ ```
95
+
96
+ ## Cart and order line items to offer
97
+
98
+ Line items link to the offer that was bought. Both links set `isList: true` on the line item side, so one offer can back line items in any number of carts and orders.
99
+
100
+ ```ts
101
+ const { data: [cart] } = await query.graph({
102
+ entity: "cart",
103
+ fields: ["items.id", "items.offer.id", "items.offer.seller_id"],
104
+ filters: { id: "cart_01JD8Q1234567" },
105
+ })
106
+ ```
42
107
 
43
- | Linked module | Relationship |
44
- | --- | --- |
45
- | **Cart line item** | A cart line item links to the offer it added; one offer can back many cart line items (list-link on the line-item side). |
46
- | **Order line item** | An order line item links to the purchased offer; one offer can back many order line items across child orders (list-link on the line-item side). |
108
+ The alias is `offer` on both cart and order line items. At checkout, `mirrorLineItemOfferLinksToOrderStep` copies each cart line item’s link to the new order line item. A subscriber does the same for line items added later by order edits, exchanges, and claims.
47
109
 
48
- <Note>
49
- Read-only links (Product, Variant, Seller, Shipping profile) resolve from a
50
- field on the `offer` row and can't be written through the link itself. The
51
- Price and Inventory-item links are writable and are managed by the offer
52
- workflows.
53
- </Note>
110
+ ## Product to shipping profile
54
111
 
55
- <Tip>
56
- The cart- and order-line-item links are list-links **on the line-item side** so
57
- the same offer can be added to many carts and placed on many orders. Without
58
- that, Medusa would enforce a 1:1 line-item ↔ offer relationship and block
59
- re-use.
60
- </Tip>
112
+ `createOffersWorkflow` also writes Medusa’s own product to shipping profile link when the product has none. That link is Medusa’s, not Mercur’s. See [How an offer ships](/platform/offer/concepts/shipping).
@@ -1,53 +1,88 @@
1
1
  ---
2
- title: "Service reference"
2
+ title: "Offer module service"
3
3
  sidebarTitle: "Service"
4
- description: "The Offer module service and its methods for working with records directly."
4
+ description: "Resolve the Offer module service and use its generated and custom methods."
5
5
  ---
6
6
 
7
- The Offer module exposes a service you can resolve from the Medusa container to
8
- read and write records directly, without going through a workflow. Use it inside
9
- custom services, subscribers, or scheduled jobs.
7
+ The Offer module service reads and writes `Offer` and `OfferCondition` rows directly. Use it in subscribers, scheduled jobs, and your own steps when you don’t need what the workflows add on top.
8
+
9
+ ## Resolve the service
10
+
11
+ Resolve the service from the container with `MercurModules.OFFER`.
10
12
 
11
13
  ```ts
12
14
  import { MercurModules } from "@mercurjs/types"
13
15
 
14
16
  const offerModuleService = container.resolve(MercurModules.OFFER)
15
17
 
16
- const [offers, count] = await offerModuleService.listAndCountOffers({
17
- seller_id: "sel_123",
18
- })
18
+ const [offers, count] = await offerModuleService.listAndCountOffers(
19
+ { seller_id: "sel_01JD8Q1234567" },
20
+ { take: 20 }
21
+ )
19
22
  ```
20
23
 
24
+ <Warning>
25
+ The service writes rows only. It doesn’t create prices, inventory items, or links, emit events, or roll back on failure. Use the [workflows](/platform/offer/reference/workflows) for anything that touches pricing, stock, or links.
26
+ </Warning>
27
+
21
28
  ## Generated methods
22
29
 
23
- The `Offer` model gets a standard set of auto-generated methods:
30
+ The service extends `MedusaService({ Offer, OfferCondition })`, which generates these methods for each model:
24
31
 
25
- | Method | Description |
32
+ | Offer | OfferCondition |
26
33
  | --- | --- |
27
- | `createOffers(data)` | Create one or more offers |
28
- | `retrieveOffer(id, config?)` | Retrieve an offer by id |
29
- | `listOffers(filters?, config?)` | List offers matching filters |
30
- | `listAndCountOffers(filters?, config?)` | List offers with a total count |
31
- | `updateOffers(data)` | Update one or more offers |
32
- | `deleteOffers(ids)` | Delete one or more offers |
34
+ | `retrieveOffer(id, config?)` | `retrieveOfferCondition(id, config?)` |
35
+ | `listOffers(filters?, config?)` | `listOfferConditions(filters?, config?)` |
36
+ | `listAndCountOffers(filters?, config?)` | `listAndCountOfferConditions(filters?, config?)` |
37
+ | `createOffers(data)` | `createOfferConditions(data)` |
38
+ | `updateOffers(data)` | `updateOfferConditions(data)` |
39
+ | `deleteOffers(ids)` | `deleteOfferConditions(ids)` |
40
+ | `softDeleteOffers(ids)` | `softDeleteOfferConditions(ids)` |
41
+ | `restoreOffers(ids)` | `restoreOfferConditions(ids)` |
33
42
 
34
- ## Group by seller
43
+ ## listOffers and listAndCountOffers with group_by_seller
35
44
 
36
- `listOffers` and `listAndCountOffers` accept a `group_by_seller` filter. When
37
- set, the service collapses offers to one row per `(product, seller)` group and
38
- populates `variant_count` and `offer_ids` on each returned offer.
45
+ Both offer list methods accept an extra `group_by_seller` filter. Without it they behave like the generated methods.
39
46
 
40
47
  ```ts
41
- const [grouped] = await offerModuleService.listAndCountOffers({
42
- product_id: "prod_123",
43
- group_by_seller: true,
44
- })
48
+ listOffers(
49
+ filters?: Record<string, unknown> & { group_by_seller?: boolean },
50
+ config?: FindConfig<OfferDTO>,
51
+ sharedContext?: Context
52
+ ): Promise<OfferDTO[]>
53
+
54
+ listAndCountOffers(
55
+ filters?: Record<string, unknown> & { group_by_seller?: boolean },
56
+ config?: FindConfig<OfferDTO>,
57
+ sharedContext?: Context
58
+ ): Promise<[OfferDTO[], number]>
45
59
  ```
46
60
 
47
- <Warning>
48
- The service writes the `offer` row and its computed fields directly. It does
49
- **not** create the offer's prices, inventory items, or module links, and it
50
- does **not** emit events or run compensation. Prefer the
51
- [workflows](/platform/offer/reference/workflows) for anything that must wire up
52
- pricing, inventory, or links.
53
- </Warning>
61
+ With `group_by_seller: true`, the service returns one offer per `(product_id, seller_id)` group, the newest by `created_at`. Each returned offer gets `variant_count` and `offer_ids` for its group, and the count is the number of groups.
62
+
63
+ Grouped mode reads only the `product_id` and `seller_id` filters. Pagination uses `config.skip` and `config.take`, with `take` defaulting to 20.
64
+
65
+ ## getStatistics
66
+
67
+ Counts offers, and the distinct stores and products behind them, as they stood at a point in time.
68
+
69
+ ```ts
70
+ getStatistics(
71
+ input?: OfferStatisticsInput,
72
+ sharedContext?: Context
73
+ ): Promise<OfferStatisticsOutput>
74
+ ```
75
+
76
+ `input.at` defaults to now. `input.filters.seller_id` takes one id or a list. An offer counts when it was created at or before `at` and wasn’t deleted by then.
77
+
78
+ ```ts
79
+ const { offers, sellers, products } =
80
+ await offerModuleService.getStatistics({ at: new Date("2026-10-01") })
81
+ ```
82
+
83
+ The result is `{ offers, sellers, products }`, each a number.
84
+
85
+ ## Related
86
+
87
+ - [Offer data models](/platform/offer/reference/data-models)
88
+ - [Offer workflows](/platform/offer/reference/workflows)
@@ -1,66 +1,175 @@
1
1
  ---
2
- title: "Workflows"
2
+ title: "Offer workflows"
3
3
  sidebarTitle: "Workflows"
4
- description: "Offer workflows for creating, updating, deleting, and batching inventory."
4
+ description: "Every offer and offer condition workflow, with its input, hooks, and events."
5
5
  ---
6
6
 
7
- This reference lists the workflows for the Offer domain. Import them from
8
- `@mercurjs/core/workflows` and run them against the Medusa container.
7
+ The Offer module ships eight workflows: five for offers and their prices and stock, and three for offer conditions. Import them from `@mercurjs/core/workflows` and run them with the Medusa container.
9
8
 
10
- ## Offer workflows
9
+ | Workflow | Id | Emits |
10
+ | --- | --- | --- |
11
+ | `createOffersWorkflow` | `create-offers` | `offer.created` |
12
+ | `updateOffersWorkflow` | `update-offers` | `offer.updated` |
13
+ | `upsertOfferPricesWorkflow` | `upsert-offer-prices` | None |
14
+ | `deleteOffersWorkflow` | `delete-offers` | `offer.deleted` |
15
+ | `batchOfferInventoryItemsWorkflow` | `batch-offer-inventory-items` | `offer.updated` |
16
+ | `createOfferConditionsWorkflow` | `create-offer-conditions` | `offer_condition.created` |
17
+ | `updateOfferConditionsWorkflow` | `update-offer-conditions` | `offer_condition.updated` |
18
+ | `deleteOfferConditionsWorkflow` | `delete-offer-conditions` | `offer_condition.deleted` |
19
+
20
+ ## createOffersWorkflow
11
21
 
12
- | Workflow | Input | Purpose |
22
+ Creates offers with their inventory items, prices, and links. Returns the created `OfferDTO[]`.
23
+
24
+ | Input | Type | Description |
13
25
  | --- | --- | --- |
14
- | `createOffersWorkflow` | `{ offers[], additional_data? }` | Create offers with prices, inventory, and links |
15
- | `updateOffersWorkflow` | `{ offers[], updated_by?, additional_data? }` | Update offer rows and rewrite their price ladders |
16
- | `deleteOffersWorkflow` | `{ ids[], additional_data? }` | Delete offers |
17
- | `batchOfferInventoryItemsWorkflow` | `{ offer_id, create?, update?, delete?, additional_data? }` | Add, update, and detach an offer's inventory-item links |
26
+ | `offers` | `CreateOfferDTO[]` | One entry per offer |
27
+ | `additional_data` | `Record<string, unknown>` | Optional. Passed to `offersCreated` |
28
+
29
+ Each `CreateOfferDTO` requires `seller_id`, `created_by`, `sku`, `variant_id`, `shipping_profile_id`, `prices`, and at least one `inventory_items` entry. Optional fields are `ean`, `upc`, `manage_inventory`, `allow_backorder`, `leadtime_to_ship`, `condition_id`, and `metadata`.
30
+
31
+ An `inventory_items` entry takes `required_quantity` (default `1`), `sku`, `title`, and `stock_levels` (`location_id`, `stocked_quantity`). A price takes `amount`, `currency_code`, and optional `min_quantity`, `max_quantity`, and `rules`.
32
+
33
+ The run creates the inventory items and links them to the offer and to the store of the first entry. It adds prices to each variant’s price set with an `offer_id` rule, creating the price set first if the variant has none. It also links each master product to its first offer’s shipping profile when the product has no profile.
34
+
35
+ | Hook | Payload |
36
+ | --- | --- |
37
+ | `validate` | `{ input }` |
38
+ | `offersCreated` | `{ offers: OfferDTO[], additional_data }`, each offer with `prices` |
18
39
 
19
- ## Hooks
40
+ ## updateOffersWorkflow
20
41
 
21
- Each workflow exposes hooks so you can extend it without forking:
42
+ Updates offer rows and, for entries with a `prices` array, replaces that offer’s prices through `upsertOfferPricesWorkflow`. Returns the updated `OfferDTO[]`.
43
+
44
+ | Input | Type | Description |
45
+ | --- | --- | --- |
46
+ | `offers` | `UpdateOfferDTO[]` | Keyed by `id`. Every other field is optional |
47
+ | `updated_by` | `string` | Optional. The actor id, passed to both hooks |
48
+ | `additional_data` | `Record<string, unknown>` | Optional. Passed to `offersUpdated` |
22
49
 
23
- | Workflow | Hooks |
50
+ `UpdateOfferDTO` accepts `sku`, `shipping_profile_id`, `manage_inventory`, `allow_backorder`, `leadtime_to_ship`, `condition_id`, `metadata`, and `prices`. Leave out `prices` to keep the current rows. A `condition_id` must point at an active condition.
51
+
52
+ | Hook | Payload |
24
53
  | --- | --- |
25
- | `createOffersWorkflow` | `validate`, `offersCreated` |
26
- | `updateOffersWorkflow` | `validate`, `offersUpdated` |
27
- | `upsertOfferPricesWorkflow` | `offerPricesUpserted` |
28
- | `deleteOffersWorkflow` | `offersDeleted` |
29
- | `batchOfferInventoryItemsWorkflow` | `validate`, `offerInventoryItemsBatched` |
54
+ | `validate` | `{ input }` |
55
+ | `offersUpdated` | `{ offers, previous_offers, updated_by, additional_data }` |
56
+
57
+ `previous_offers` holds the offer rows as they were before the write.
30
58
 
31
- ### Hook payloads
59
+ ## upsertOfferPricesWorkflow
60
+
61
+ Replaces each listed offer’s prices without touching other offers on the same variant. Returns `{ offer_id, price_ids }[]` for the prices it created.
62
+
63
+ | Input | Type | Description |
64
+ | --- | --- | --- |
65
+ | `offers` | `{ id, prices: UpsertOfferPriceDTO[] }[]` | The full new price list per offer |
66
+ | `updated_by` | `string` | Optional. Passed to the hook |
67
+
68
+ A price with an `id` updates that row, a price without one is added, and owned prices left out are deleted with their links. An `id` that belongs to another offer fails the run with `Price … does not belong to offer …`.
32
69
 
33
70
  | Hook | Payload |
34
71
  | --- | --- |
35
- | `offersCreated` | `{ offers: OfferDTO[], additional_data? }` — each offer carries `created_by` and its `prices` |
36
- | `offersUpdated` | `{ offers: OfferDTO[], previous_offers: OfferDTO[], updated_by?, additional_data? }` — `previous_offers` is the row state before the write |
37
- | `offerPricesUpserted` | `{ offers: Array<{ offer_id, previous_prices: OfferPriceDTO[], prices: OfferPriceDTO[], created: string[], updated: string[], deleted: string[] }>, updated_by: string \| null }` |
38
- | `offersDeleted` | `{ ids: string[], offers: OfferDTO[], additional_data? }` — `offers` is a snapshot taken before deletion, including `prices` |
72
+ | `offerPricesUpserted` | `{ offers, updated_by }`, where `updated_by` is `string \| null` |
39
73
 
40
- `updated_by` is the authenticated actor id. Vendor routes fill it from the session; pass it yourself when running `updateOffersWorkflow` or `upsertOfferPricesWorkflow` directly.
74
+ Each entry in `offers` has these fields:
41
75
 
42
- In `offerPricesUpserted`, `previous_prices` and `prices` are the offer's ladder before and after the write (`amount`, `currency_code`, `min_quantity`, `max_quantity`, `price_rules`). `created` and `deleted` hold the price ids added and removed; `updated` holds the ids rewritten in place whose amount, quantity bounds, or rules actually changed — an unchanged row resent with its id is not listed. The hook also fires when `updateOffersWorkflow` runs the price upsert for you, so a single handler on `upsertOfferPricesWorkflow` covers both entry points. Prices are hard-deleted and Medusa's pricing events carry ids only, so these payloads (and the `offersDeleted` snapshot) are the only place the previous values can be read.
76
+ - **`offer_id`**: the offer
77
+ - **`previous_prices`**: the offer’s prices before the write
78
+ - **`prices`**: the offer’s prices after the write
79
+ - **`created`**: ids of new prices
80
+ - **`updated`**: ids whose amount, currency, quantity bounds, or rules changed; an unchanged row resent with its `id` isn’t listed
81
+ - **`deleted`**: ids of removed prices
43
82
 
44
- ```ts
83
+ The hook also fires when `updateOffersWorkflow` runs the upsert, so one handler covers both entry points:
84
+
85
+ ```ts title="src/workflows/hooks/offer-prices-upserted.ts"
45
86
  import { upsertOfferPricesWorkflow } from "@mercurjs/core/workflows"
46
- import { StepResponse } from "@medusajs/framework/workflows-sdk"
47
87
 
48
88
  upsertOfferPricesWorkflow.hooks.offerPricesUpserted(
49
89
  async ({ offers, updated_by }, { container }) => {
50
- for (const { offer_id, previous_prices, prices, updated } of offers) {
51
- // record a price-history entry for every id in `updated`
90
+ const logger = container.resolve("logger")
91
+ for (const { offer_id, updated, deleted } of offers) {
92
+ logger.info(`${offer_id} by ${updated_by}: ${updated.length} changed`)
93
+ logger.info(`${offer_id}: ${deleted.length} removed`)
52
94
  }
53
- return new StepResponse(undefined)
54
95
  }
55
96
  )
56
97
  ```
57
98
 
58
- ## Behavior notes
99
+ Deleted prices are gone for good, so this payload is the only place to read their old values.
100
+
101
+ ## deleteOffersWorkflow
102
+
103
+ Soft-deletes offers. It has no step that removes their prices or inventory items.
104
+
105
+ | Input | Type | Description |
106
+ | --- | --- | --- |
107
+ | `ids` | `string[]` | The offers to delete |
108
+ | `additional_data` | `Record<string, unknown>` | Optional. Passed to `offersDeleted` |
109
+
110
+ | Hook | Payload |
111
+ | --- | --- |
112
+ | `offersDeleted` | `{ ids, offers, additional_data }` |
113
+
114
+ `offers` is a snapshot taken before the delete, with each offer’s `prices`.
115
+
116
+ ## batchOfferInventoryItemsWorkflow
117
+
118
+ Creates, updates, and removes `offer ↔ inventory_item` links on one offer. Returns `{ created, updated, deleted }`.
119
+
120
+ | Input | Type | Description |
121
+ | --- | --- | --- |
122
+ | `offer_id` | `string` | The offer to change |
123
+ | `create` | `{ inventory_item_id, required_quantity? }[]` | Optional. Items to link, `required_quantity` defaults to `1` |
124
+ | `update` | `{ inventory_item_id, required_quantity }[]` | Optional. New quantities for linked items |
125
+ | `delete` | `string[]` | Optional. Inventory item ids to unlink |
126
+ | `additional_data` | `Record<string, unknown>` | Optional. Passed to `offerInventoryItemsBatched` |
127
+
128
+ The run fails when the offer doesn’t exist, an item in `create` doesn’t exist, an item in `delete` isn’t linked, or an id repeats within or across the lists.
129
+
130
+ | Hook | Payload |
131
+ | --- | --- |
132
+ | `validate` | `{ input }` |
133
+ | `offerInventoryItemsBatched` | `{ offer_id, result, additional_data }` |
134
+
135
+ ## createOfferConditionsWorkflow
136
+
137
+ Creates offer conditions. Returns `OfferConditionDTO[]`.
138
+
139
+ | Input | Type | Description |
140
+ | --- | --- | --- |
141
+ | `offer_conditions` | `CreateOfferConditionDTO[]` | `code` and `label`, plus optional `is_active`, `rank`, and `metadata` |
142
+
143
+ | Hook | Payload |
144
+ | --- | --- |
145
+ | `offerConditionsCreated` | `{ offer_conditions }` |
146
+
147
+ ## updateOfferConditionsWorkflow
148
+
149
+ Updates offer conditions by `id`. Returns `OfferConditionDTO[]`.
150
+
151
+ | Input | Type | Description |
152
+ | --- | --- | --- |
153
+ | `offer_conditions` | `UpdateOfferConditionDTO[]` | `id`, plus any of `code`, `label`, `is_active`, `rank`, and `metadata` |
154
+
155
+ | Hook | Payload |
156
+ | --- | --- |
157
+ | `offerConditionsUpdated` | `{ offer_conditions }` |
158
+
159
+ ## deleteOfferConditionsWorkflow
160
+
161
+ Soft-deletes offer conditions. The run fails when an id doesn’t exist or when any offer still uses one of the conditions.
162
+
163
+ | Input | Type | Description |
164
+ | --- | --- | --- |
165
+ | `ids` | `string[]` | The conditions to delete |
166
+
167
+ | Hook | Payload |
168
+ | --- | --- |
169
+ | `offerConditionsDeleted` | `{ ids }` |
59
170
 
60
- - **Prices** are written on the master variant's shared price set, each row scoped by an `offer_id` `PriceRule`. On update, supplying `prices` replaces the ladder with the given rows; omitting it leaves prices untouched.
61
- - **Inventory** items passed to `createOffersWorkflow` are created and linked to the offer (and the store) in the same run. Every offer needs at least one.
62
- - Workflows run with compensation, so a failed create rolls back the inventory items, prices, and links it had already created.
171
+ ## Related
63
172
 
64
- To work with records directly instead of through a workflow, see the
65
- [Service reference](/platform/offer/reference/service). To run side effects when
66
- an offer changes, see the [Event reference](/platform/offer/reference/events).
173
+ - [Offer events](/platform/offer/reference/events)
174
+ - [Offer service methods](/platform/offer/reference/service)
175
+ - [Create an offer](/platform/offer/guides/create-an-offer)