@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,76 +1,124 @@
1
1
  ---
2
- title: "Confirm or decline a change"
2
+ title: "Confirm, decline, or cancel a product change"
3
3
  sidebarTitle: "Confirm or decline"
4
- description: "Resolve a pending product change from server code."
4
+ description: "Resolve a pending product change from server code and react to the result."
5
5
  ---
6
6
 
7
- In this guide, you'll learn how to resolve a pending change from your own server
8
- code. Each resolution has a dedicated workflow so the side effects (applying
9
- actions, events, compensation) run consistently.
7
+ Resolve a pending product change from your own code, for example in a custom review tool or an automated approval rule. Each resolution has its own workflow, so the status update, the product write, and the event always happen together.
8
+
9
+ ## Before you begin
10
+
11
+ You need the following in place:
12
+
13
+ - A Mercur server with `@mercurjs/core` installed through `withMercur()`
14
+ - A change in `pending` status. Changes stay `pending` only while the `product_request` feature flag is on, the default
15
+
16
+ ## Review and resolve a change
17
+
18
+ Find the pending change, then confirm or decline it.
19
+
20
+ <Steps>
21
+ <Step title="Find pending changes">
22
+ List the pending changes on a product with their actions, so a reviewer can see what each one does.
23
+
24
+ ```ts
25
+ const query = container.resolve("query")
26
+
27
+ const { data: changes } = await query.graph({
28
+ entity: "product_change",
29
+ fields: ["id", "created_by", "external_note", "actions.*"],
30
+ filters: {
31
+ product_id: "prod_01JD8Q1234567",
32
+ status: "pending",
33
+ },
34
+ })
35
+ ```
36
+
37
+ Each action’s `details` holds the proposed value and the previous one, as described in [Change actions](/platform/product-edit/concepts/change-actions).
38
+ </Step>
39
+ <Step title="Confirm the change">
40
+ `confirmProductChangeWorkflow` sets the changes to `confirmed`, applies their actions to the product, and emits `product-change.confirmed`.
41
+
42
+ ```ts title="src/api/admin/custom/changes/[id]/confirm/route.ts"
43
+ import type {
44
+ AuthenticatedMedusaRequest,
45
+ MedusaResponse,
46
+ } from "@medusajs/framework/http"
47
+ import { confirmProductChangeWorkflow } from "@mercurjs/core/workflows"
48
+
49
+ export async function POST(
50
+ req: AuthenticatedMedusaRequest,
51
+ res: MedusaResponse
52
+ ) {
53
+ await confirmProductChangeWorkflow(req.scope).run({
54
+ input: {
55
+ ids: [req.params.id],
56
+ confirmed_by: req.auth_context.actor_id,
57
+ },
58
+ })
59
+
60
+ res.json({ id: req.params.id, status: "confirmed" })
61
+ }
62
+ ```
63
+
64
+ The input takes an `ids` array. Pass `internal_note` or `external_note` to overwrite the change’s notes as you confirm it.
65
+ </Step>
66
+ <Step title="Or decline it">
67
+ `rejectProductChangeWorkflow` sets one change to `declined` and leaves the product untouched. Give the seller a reason in `declined_reason`.
68
+
69
+ ```ts
70
+ import { rejectProductChangeWorkflow } from "@mercurjs/core/workflows"
71
+
72
+ await rejectProductChangeWorkflow(container).run({
73
+ input: {
74
+ id: "prodch_01JD8Q1234567",
75
+ declined_by: "user_01JD8Q1234567",
76
+ declined_reason: "Photos don't meet the image guidelines",
77
+ },
78
+ })
79
+ ```
80
+
81
+ The workflow emits `product-change.declined` with the change id.
82
+ </Step>
83
+ </Steps>
10
84
 
11
- ## Confirm a change
12
-
13
- `confirmProductChangeWorkflow` marks the changes `confirmed`, applies their
14
- pending actions to the product, and emits `product-change.confirmed`.
15
-
16
- ```ts title="src/api/custom/confirm/route.ts"
17
- import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
18
- import { confirmProductChangeWorkflow } from "@mercurjs/core/workflows"
19
-
20
- export async function POST(req: MedusaRequest, res: MedusaResponse) {
21
- await confirmProductChangeWorkflow(req.scope).run({
22
- input: {
23
- ids: [req.params.id],
24
- confirmed_by: req.auth_context?.actor_id,
25
- },
26
- })
27
-
28
- res.sendStatus(200)
29
- }
30
- ```
31
-
32
- <Note>
33
- Confirmation only applies actions that aren't already `applied`, so re-running
34
- it never writes the same edit twice.
35
- </Note>
36
-
37
- ## Decline a change
85
+ ## Cancel a change
38
86
 
39
- `rejectProductChangeWorkflow` moves a `pending` change to `declined` without
40
- touching the product, and emits `product-change.declined`.
87
+ Cancel a change when it should be withdrawn rather than judged, for example when a seller retracts their own edit. Canceling frees the author to stage a new change on the product.
41
88
 
42
89
  ```ts
43
- import { rejectProductChangeWorkflow } from "@mercurjs/core/workflows"
90
+ import { cancelProductChangeWorkflow } from "@mercurjs/core/workflows"
44
91
 
45
- await rejectProductChangeWorkflow(container).run({
92
+ await cancelProductChangeWorkflow(container).run({
46
93
  input: {
47
- id: "prodch_123",
48
- declined_by: "user_123",
49
- declined_reason: "Images don't meet guidelines",
94
+ id: "prodch_01JD8Q1234567",
95
+ canceled_by: "sel_01JD8Q1234567",
50
96
  },
51
97
  })
52
98
  ```
53
99
 
54
- ## Cancel a change
100
+ The workflow sets `canceled_by` and `canceled_at`, then emits `product-change.canceled`.
55
101
 
56
- When the change should be withdrawn rather than judged (for example the vendor
57
- retracting their own submission), use `cancelProductChangeWorkflow`:
102
+ ## Confirm several changes at once
58
103
 
59
- ```ts
60
- import { cancelProductChangeWorkflow } from "@mercurjs/core/workflows"
104
+ `confirmProductChangeWorkflow` loads every id in `ids` before it writes anything. If one id doesn’t exist, it throws a `NOT_FOUND` error. If one change isn’t `pending`, it throws `NOT_ALLOWED`, and none of the changes are confirmed.
61
105
 
62
- await cancelProductChangeWorkflow(container).run({
63
- input: { id: "prodch_123", canceled_by: "user_123" },
64
- })
65
- ```
106
+ ## Handle a failed apply
107
+
108
+ Applying actions runs Medusa’s product workflows inside the confirm workflow. If one of them fails, for example because a variant was already deleted, the workflow compensates: it restores the change to `pending` and clears `confirmed_by` and `confirmed_at`. Fix the cause, then confirm again.
109
+
110
+ ## Run your own logic on a resolution
111
+
112
+ Each resolution workflow has a hook that receives the change ids and the `additional_data` you passed in:
113
+
114
+ - **`confirmProductChangeWorkflow`**: `productChangeConfirmed`, with `{ ids, additional_data }`
115
+ - **`rejectProductChangeWorkflow`**: `productChangeRejected`, with `{ id, additional_data }`
116
+ - **`cancelProductChangeWorkflow`**: `productChangeCanceled`, with `{ id, additional_data }`
66
117
 
67
- <Warning>
68
- Confirm, decline, and cancel all require the change to be `pending`. Resolving
69
- an already-resolved change fails validation. A change is resolved exactly once.
70
- </Warning>
118
+ Use a hook when your logic must succeed or fail together with the resolution. For side effects that can run later, such as an email to the seller, subscribe to the events listed in the [Events reference](/platform/product-edit/reference/events).
71
119
 
72
- ## React to resolutions
120
+ ## Next steps
73
121
 
74
- To run your own side effects when a change resolves, subscribe to the events
75
- these workflows emit rather than polling. See the
76
- [Event reference](/platform/product-edit/reference/events).
122
+ - [Send a proposed product back for a revision](/platform/product-edit/guides/request-a-revision)
123
+ - [How actions apply on confirm](/platform/product-edit/concepts/change-actions#how-actions-apply)
124
+ - [Workflow inputs and hooks](/platform/product-edit/reference/workflows)
@@ -1,74 +1,154 @@
1
1
  ---
2
- title: "Edit a product"
2
+ title: "Stage a product edit from server code"
3
3
  sidebarTitle: "Edit a product"
4
- description: "Stage a product change from server code with the edit workflows."
4
+ description: "Route a product edit through the review pipeline from a custom API route, a script, or a bulk tool."
5
5
  ---
6
6
 
7
- In this guide, you'll learn how to route a product edit through the change
8
- pipeline from your own server code, such as a custom API route or a bulk tool.
7
+ Stage a product edit as a `ProductChange` instead of writing to the product, so it goes through the same review as an edit from the Vendor panel. Use this when you build your own edit surface, such as a bulk editor or an import.
9
8
 
10
- Instead of writing to a product directly, you stage a `ProductChange`. Mercur
11
- exposes high-level edit workflows that diff your update against the current
12
- product and stage only the fields that actually changed.
9
+ ## Before you begin
13
10
 
14
- ## Update product fields
11
+ You need the following in place:
15
12
 
16
- `productEditUpdateProductWorkflow` diffs the `update` payload against the product
17
- and stages an `UPDATE` action per changed field.
13
+ - A Mercur server with `@mercurjs/core` installed through `withMercur()`
14
+ - A product id, and the id of the actor making the edit, such as a seller id (`sel_…`)
18
15
 
19
- ```ts title="src/api/custom/edit/route.ts"
20
- import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
21
- import { productEditUpdateProductWorkflow } from "@mercurjs/core/workflows"
16
+ ## Stage a field edit
17
+
18
+ `productEditUpdateProductWorkflow` compares your update with the product and stages one action per field that differs.
19
+
20
+ <Steps>
21
+ <Step title="Create the route">
22
+ Add a route under `/admin` so Medusa authenticates the caller, and pass the update to the workflow. The workflow returns the staged change.
23
+
24
+ ```ts title="src/api/admin/custom/products/[id]/edit/route.ts" {13-19}
25
+ import type {
26
+ AuthenticatedMedusaRequest,
27
+ MedusaResponse,
28
+ } from "@medusajs/framework/http"
29
+ import { productEditUpdateProductWorkflow } from "@mercurjs/core/workflows"
30
+
31
+ type Body = { title?: string; material?: string }
32
+
33
+ export async function POST(
34
+ req: AuthenticatedMedusaRequest<Body>,
35
+ res: MedusaResponse
36
+ ) {
37
+ const { result } = await productEditUpdateProductWorkflow(req.scope).run({
38
+ input: {
39
+ product_id: req.params.id,
40
+ created_by: req.auth_context.actor_id,
41
+ update: req.body,
42
+ },
43
+ })
44
+
45
+ res.status(202).json({ product_change: result })
46
+ }
47
+ ```
48
+
49
+ The route answers `202` because the edit isn’t on the product yet. With the default `product_request` flag on, `result.status` is `pending`.
50
+ </Step>
51
+ <Step title="Handle an open change">
52
+ If the same `created_by` already has a `pending` change on the product, the workflow throws a `NOT_ALLOWED` error before it stages anything. Medusa’s error handler turns it into a `400` response. Cancel the open change with `cancelProductChangeWorkflow`, or wait for an operator to resolve it.
53
+ </Step>
54
+ <Step title="Read the staged actions">
55
+ Load the change with its actions to show the seller what’s waiting for review.
56
+
57
+ ```ts
58
+ const query = req.scope.resolve("query")
59
+
60
+ const { data: [change] } = await query.graph({
61
+ entity: "product_change",
62
+ fields: ["id", "status", "actions.action", "actions.details"],
63
+ filters: { id: result.id },
64
+ })
65
+ ```
66
+
67
+ Each `UPDATE` action carries `field`, `value`, and `previous_value` in `details`.
68
+ </Step>
69
+ </Steps>
70
+
71
+ If no field differs, the workflow still stages a change, with no actions. Compare the input yourself first when an empty change would confuse your reviewers.
72
+
73
+ ## Stage variant and attribute edits
74
+
75
+ Variants and attributes have their own edit workflows, with the same pending check. `productEditUpdateVariantsWorkflow` takes a list of typed operations:
76
+
77
+ ```ts
78
+ import { productEditUpdateVariantsWorkflow } from "@mercurjs/core/workflows"
22
79
 
23
- export async function POST(req: MedusaRequest, res: MedusaResponse) {
24
- const { result } = await productEditUpdateProductWorkflow(req.scope).run({
25
- input: {
26
- product_id: req.params.id,
27
- created_by: req.auth_context?.actor_id,
28
- update: { title: "Updated title", material: "Cotton" },
29
- },
30
- })
31
-
32
- res.status(201).json({ product_change: result })
33
- }
80
+ await productEditUpdateVariantsWorkflow(container).run({
81
+ input: {
82
+ product_id: "prod_01JD8Q1234567",
83
+ created_by: "sel_01JD8Q1234567",
84
+ operations: [
85
+ {
86
+ type: "update",
87
+ variant_id: "variant_01JD8Q1234567",
88
+ fields: { sku: "LINEN-M" },
89
+ },
90
+ { type: "remove", variant_id: "variant_01JD8Q1234568" },
91
+ ],
92
+ },
93
+ })
34
94
  ```
35
95
 
36
- <Note>
37
- If a product already has a `pending` change, staging another one is rejected.
38
- Only one active change per product is allowed. Resolve or cancel the open
39
- change first.
40
- </Note>
96
+ Each operation becomes a `VARIANT_ADD`, `VARIANT_UPDATE`, or `VARIANT_REMOVE` action. An update whose fields all match the current variant stages nothing. For attributes, pass `add`, `remove`, and `update` arrays to `productEditUpdateAttributesWorkflow`, in the same shapes as the attribute batch.
41
97
 
42
98
  ## Stage actions directly
43
99
 
44
- For finer control (variants, attributes, mixed operations), stage the actions
45
- yourself with `stageProductChangeWorkflow`:
100
+ For a mixed change, build the actions yourself and pass them to `stageProductChangeWorkflow`. It skips the diff and the pending check, so check for an open change first.
46
101
 
47
102
  ```ts
103
+ import { ProductChangeActionType } from "@mercurjs/types"
48
104
  import { stageProductChangeWorkflow } from "@mercurjs/core/workflows"
49
105
 
50
106
  await stageProductChangeWorkflow(container).run({
51
107
  input: {
52
- product_id: "prod_123",
53
- created_by: "user_123",
108
+ product_id: "prod_01JD8Q1234567",
109
+ created_by: "sel_01JD8Q1234567",
110
+ external_note: "Spring catalog refresh",
54
111
  actions: [
55
- { product_id: "prod_123", action: "UPDATE", details: { field: "subtitle", value: "New" } },
56
- { product_id: "prod_123", action: "VARIANT_REMOVE", details: { variant_id: "variant_123" } },
112
+ {
113
+ product_id: "prod_01JD8Q1234567",
114
+ action: ProductChangeActionType.UPDATE,
115
+ details: { field: "subtitle", value: "Washed linen" },
116
+ },
57
117
  ],
58
118
  },
59
119
  })
60
120
  ```
61
121
 
62
- ## Auto-confirm
122
+ Every action needs its own `product_id`. The workflow fills in `product_change_id` for you.
123
+
124
+ ## Apply an edit without review
125
+
126
+ Pass `auto_confirm: true` to `stageProductChangeWorkflow` or `productEditDeleteProductWorkflow` to confirm and apply the change in the same run. Use it for trusted sources, such as an operator tool. The change still lands in the trail as `confirmed`, and `product-change.confirmed` still fires.
127
+
128
+ ## Attach custom data with additional_data
129
+
130
+ `productEditUpdateProductWorkflow` and `productEditUpdateAttributesWorkflow` accept `additional_data` and pass it to their `productChangeCreated` hook. Use the hook to store your own data on the change:
131
+
132
+ ```ts title="src/workflows/hooks/product-change-created.ts"
133
+ import { productEditUpdateProductWorkflow } from "@mercurjs/core/workflows"
134
+ import { MercurModules } from "@mercurjs/types"
135
+
136
+ productEditUpdateProductWorkflow.hooks.productChangeCreated(
137
+ async ({ product_change, additional_data }, { container }) => {
138
+ const productEdit = container.resolve(MercurModules.PRODUCT_EDIT)
139
+
140
+ await productEdit.updateProductChanges({
141
+ id: product_change.id,
142
+ metadata: { ticket_id: additional_data?.ticket_id },
143
+ })
144
+ }
145
+ )
146
+ ```
147
+
148
+ The hook runs after staging and auto-confirm, so `product_change` is the record the workflow returns.
63
149
 
64
- Both workflows run auto-confirm after staging: with review off the change
65
- applies immediately, with review on it stays `pending`. Pass `auto_confirm: true`
66
- to `stageProductChangeWorkflow` to force immediate application regardless of the
67
- review setting.
150
+ ## Next steps
68
151
 
69
- <Tip>
70
- Dedicated helpers exist for common shapes, such as
71
- `productEditUpdateVariantsWorkflow`, `productEditUpdateAttributesWorkflow`, and
72
- `productEditDeleteProductWorkflow`. Each stages the right action types for you.
73
- See the [Workflows reference](/platform/product-edit/reference/workflows).
74
- </Tip>
152
+ - [Confirm or decline the staged change](/platform/product-edit/guides/confirm-or-decline-a-change)
153
+ - [Every action type and its `details`](/platform/product-edit/concepts/change-actions)
154
+ - [Edit workflow inputs and hooks](/platform/product-edit/reference/workflows#edit-workflows)
@@ -1,54 +1,114 @@
1
1
  ---
2
- title: "Request a revision"
2
+ title: "Send a proposed product back for a revision"
3
3
  sidebarTitle: "Request a revision"
4
- description: "Send a product submission back to the vendor from server code."
4
+ description: "Ask a seller to rework a proposed product, record the request, and notify the seller."
5
5
  ---
6
6
 
7
- In this guide, you'll learn how to ask a vendor to rework a submission instead of
8
- approving or rejecting it outright.
7
+ Ask a seller to rework a product they’ve proposed, instead of publishing or rejecting it. The request leaves the product `proposed`, adds a `CHANGE_REQUESTED` entry to its history, and emits an event you can use to notify the seller.
9
8
 
10
- A revision request doesn't mutate the product. It records a `CHANGE_REQUESTED`
11
- action in the audit trail carrying your message, and emits
12
- `product.change-requested` so the vendor is notified. The product stays with the
13
- vendor to revise and resubmit.
9
+ ## Before you begin
14
10
 
15
- ## Run the workflow
11
+ You need the following in place:
16
12
 
17
- `requestProductChangeWorkflow` validates that the product is in `proposed`,
18
- records the audit action, and emits the event.
13
+ - A Mercur server with `@mercurjs/core` installed through `withMercur()`
14
+ - A product with status `proposed`. The workflow fails for any other status
19
15
 
20
- ```ts title="src/api/custom/request-revision/route.ts"
21
- import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
22
- import { requestProductChangeWorkflow } from "@mercurjs/core/workflows"
16
+ ## Request and notify
23
17
 
24
- export async function POST(req: MedusaRequest, res: MedusaResponse) {
25
- await requestProductChangeWorkflow(req.scope).run({
26
- input: {
27
- product_id: req.params.id,
28
- actor_id: req.auth_context?.actor_id,
29
- message: "Please add a size guide and sharper photos.",
30
- },
31
- })
18
+ Run the workflow from an admin route, then notify the seller from a subscriber.
32
19
 
33
- res.sendStatus(200)
34
- }
20
+ <Steps>
21
+ <Step title="Run the workflow">
22
+ `requestProductChangeWorkflow` checks the product status, records the audit change, and emits `product.change-requested`.
23
+
24
+ ```ts title="src/api/admin/custom/products/[id]/revise/route.ts"
25
+ import type {
26
+ AuthenticatedMedusaRequest,
27
+ MedusaResponse,
28
+ } from "@medusajs/framework/http"
29
+ import { requestProductChangeWorkflow } from "@mercurjs/core/workflows"
30
+
31
+ export async function POST(
32
+ req: AuthenticatedMedusaRequest<{ message: string }>,
33
+ res: MedusaResponse
34
+ ) {
35
+ await requestProductChangeWorkflow(req.scope).run({
36
+ input: {
37
+ product_id: req.params.id,
38
+ actor_id: req.auth_context.actor_id,
39
+ message: req.body.message,
40
+ },
41
+ })
42
+
43
+ res.json({ id: req.params.id })
44
+ }
45
+ ```
46
+
47
+ The message is optional. Without it, the action’s `details.message` is `null`.
48
+ </Step>
49
+ <Step title="Notify the seller">
50
+ Subscribe to `product.change-requested`. The payload carries the product id, not a change id, plus the message and the operator’s id.
51
+
52
+ ```ts title="src/subscribers/product-change-requested.ts"
53
+ import type { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"
54
+
55
+ type Payload = { id: string; message?: string; actor_id?: string }
56
+
57
+ export default async function handler({
58
+ event,
59
+ container,
60
+ }: SubscriberArgs<Payload>) {
61
+ const logger = container.resolve("logger")
62
+
63
+ logger.info(
64
+ `Revision requested on ${event.data.id}: ${event.data.message ?? ""}`
65
+ )
66
+ }
67
+
68
+ export const config: SubscriberConfig = {
69
+ event: "product.change-requested",
70
+ }
71
+ ```
72
+
73
+ Replace the log line with your notification, for example a call to Medusa’s Notification module.
74
+ </Step>
75
+ </Steps>
76
+
77
+ ## Where the request is stored
78
+
79
+ `requestProductChangeWorkflow` calls `recordProductAuditChangeWorkflow`, which stores the request as a change that starts out `confirmed`. The message is saved in two places:
80
+
81
+ - **`details.message`**: on the `CHANGE_REQUESTED` action
82
+ - **`external_note`**: on the change, where the seller-facing note lives
83
+
84
+ Read every revision request on a product by querying its actions:
85
+
86
+ ```ts
87
+ const { data: requests } = await query.graph({
88
+ entity: "product_change_action",
89
+ fields: ["details", "product_change.created_by", "created_at"],
90
+ filters: {
91
+ product_id: "prod_01JD8Q1234567",
92
+ action: "CHANGE_REQUESTED",
93
+ },
94
+ })
35
95
  ```
36
96
 
37
- <Note>
38
- The request is only valid while the product is `proposed`. The workflow
39
- validates the product status first and fails otherwise.
40
- </Note>
97
+ `product_change.created_by` is the operator who asked.
98
+
99
+ ## Publish or reject instead
100
+
101
+ When the product is ready or can’t be fixed, resolve it with a product workflow instead. Both require the product to be `proposed`, and both record a confirmed `STATUS_CHANGE` audit change:
102
+
103
+ - **`confirmProductsWorkflow`**: takes `product_ids`, sets them to `published`, and emits `product.published`
104
+ - **`rejectProductWorkflow`**: takes `product_id` and an optional `message`, sets it to `rejected`, and emits `product.rejected`
105
+
106
+ ## Attach custom data with additional_data
41
107
 
42
- ## Where the message goes
108
+ `requestProductChangeWorkflow` passes `additional_data` to its `productChangeRequested` hook, along with `product_id` and `message`. Use it to store, for example, the checklist items the seller has to fix.
43
109
 
44
- The `message` is stored in two places for durability: the `CHANGE_REQUESTED`
45
- action's `details.message`, and the parent change's `external_note` (the
46
- vendor-facing note). Because the audit change is created already `confirmed`, the
47
- request is a permanent entry in the product's history.
110
+ ## Next steps
48
111
 
49
- <Tip>
50
- Subscribe to `product.change-requested` to send the vendor a notification. The
51
- payload includes the `id` (product), the `message`, and the `actor_id` of the
52
- operator who asked. See the
53
- [Event reference](/platform/product-edit/reference/events).
54
- </Tip>
112
+ - [How audit changes fit the trail](/platform/product-edit/concepts/change-pipeline#audit-changes)
113
+ - [Revision requests and statuses](/platform/product-edit/concepts/status-and-auto-confirm#revision-requests)
114
+ - [Event payloads](/platform/product-edit/reference/events)