From f3eca7734e39c7622610343cdcc3c859291fc1ad Mon Sep 17 00:00:00 2001 From: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:09:35 +0100 Subject: [PATCH 1/5] fix(medusa): Missing metadata field on order (#10651) --- integration-tests/modules/__tests__/order/order.spec.ts | 6 ++++++ packages/medusa/src/api/admin/orders/query-config.ts | 8 +------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/integration-tests/modules/__tests__/order/order.spec.ts b/integration-tests/modules/__tests__/order/order.spec.ts index 8e0bc18afc55a..cbe798789f093 100644 --- a/integration-tests/modules/__tests__/order/order.spec.ts +++ b/integration-tests/modules/__tests__/order/order.spec.ts @@ -30,6 +30,9 @@ medusaIntegrationTestRunner({ const created = await orderModule.createOrders({ region_id: "test_region_id", email: "foo@bar.com", + metadata: { + foo: "bar", + }, items: [ { title: "Custom Item 2", @@ -108,6 +111,9 @@ medusaIntegrationTestRunner({ payment_status: "not_paid", region_id: "test_region_id", fulfillments: [], + metadata: { + foo: "bar", + }, fulfillment_status: "not_fulfilled", summary: expect.objectContaining({ // TODO: add all summary fields diff --git a/packages/medusa/src/api/admin/orders/query-config.ts b/packages/medusa/src/api/admin/orders/query-config.ts index d05a504951260..eb6efcd2323cc 100644 --- a/packages/medusa/src/api/admin/orders/query-config.ts +++ b/packages/medusa/src/api/admin/orders/query-config.ts @@ -10,12 +10,8 @@ export const defaultAdminOrderFields = [ ] export const defaultAdminRetrieveOrderFields = [ - "id", - "display_id", + ...defaultAdminOrderFields, "region_id", - "status", - "version", - "summary", "total", "subtotal", "tax_total", @@ -36,8 +32,6 @@ export const defaultAdminRetrieveOrderFields = [ "original_shipping_tax_total", "original_shipping_subtotal", "original_shipping_total", - "created_at", - "updated_at", "*items", "*items.tax_lines", "*items.adjustments", From bde4b82194f736ff6acdb0f3656ed0dbc42beae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frane=20Poli=C4=87?= <16856471+fPolic@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:16:26 +0100 Subject: [PATCH 2/5] feat(core-flows,dashboard,js-sdk,medusa,types): support Fulfillment Options (#10622) **What** - add a list point for fetching fulfillment options for a provider - add FO support on SO create & update on dashboard - pass `cart` and `stockLocation` to `validateFufillmentData` context --- CLOSES CMRC-789 CLOSES CMRC-790 --- .../src/hooks/api/fulfillment-providers.tsx | 28 + .../dashboard/src/hooks/use-combobox-data.tsx | 3 +- .../src/i18n/translations/$schema.json | 1976 +++++++++++++---- .../dashboard/src/i18n/translations/en.json | 3 +- .../create-shipping-option-details-form.tsx | 55 +- .../create-shipping-options-form.tsx | 21 +- .../create-shipping-options-form/schema.ts | 1 + .../edit-shipping-option-form.tsx | 50 +- .../steps/validate-shipping-methods-data.ts | 30 +- .../workflows/add-shipping-method-to-cart.ts | 15 +- .../list-shipping-options-for-cart.ts | 44 +- .../js-sdk/src/admin/fulfillment-provider.ts | 37 +- packages/core/js-sdk/src/store/index.ts | 17 + .../fulfillment-provider/admin/entities.ts | 8 +- .../fulfillment-provider/admin/responses.ts | 10 +- .../src/http/fulfillment-provider/common.ts | 11 + .../[id]/options/route.ts | 27 + .../fulfillment-providers/middlewares.ts | 5 + .../services/fulfillment-module-service.ts | 3 +- .../src/services/fulfillment-provider.ts | 3 +- 20 files changed, 1855 insertions(+), 492 deletions(-) create mode 100644 packages/medusa/src/api/admin/fulfillment-providers/[id]/options/route.ts diff --git a/packages/admin/dashboard/src/hooks/api/fulfillment-providers.tsx b/packages/admin/dashboard/src/hooks/api/fulfillment-providers.tsx index 5d17085c504b9..12129496ed0bd 100644 --- a/packages/admin/dashboard/src/hooks/api/fulfillment-providers.tsx +++ b/packages/admin/dashboard/src/hooks/api/fulfillment-providers.tsx @@ -9,6 +9,12 @@ export const fulfillmentProvidersQueryKeys = queryKeysFactory( FULFILLMENT_PROVIDERS_QUERY_KEY ) +const FULFILLMENT_PROVIDER_OPTIONS_QUERY_KEY = + "fulfillment_provider_options" as const +export const fulfillmentProviderOptionsQueryKeys = queryKeysFactory( + FULFILLMENT_PROVIDER_OPTIONS_QUERY_KEY +) + export const useFulfillmentProviders = ( query?: HttpTypes.AdminFulfillmentProviderListParams, options?: Omit< @@ -29,3 +35,25 @@ export const useFulfillmentProviders = ( return { ...data, ...rest } } + +export const useFulfillmentProviderOptions = ( + providerId: string, + options?: Omit< + UseQueryOptions< + HttpTypes.AdminFulfillmentProviderOptionsListResponse, + FetchError, + HttpTypes.AdminFulfillmentProviderOptionsListResponse, + QueryKey + >, + "queryFn" | "queryKey" + > +) => { + const { data, ...rest } = useQuery({ + queryFn: () => + sdk.admin.fulfillmentProvider.listFulfillmentOptions(providerId), + queryKey: fulfillmentProviderOptionsQueryKeys.list(providerId), + ...options, + }) + + return { ...data, ...rest } +} diff --git a/packages/admin/dashboard/src/hooks/use-combobox-data.tsx b/packages/admin/dashboard/src/hooks/use-combobox-data.tsx index 159bf0e9e2226..1488dec0c73b8 100644 --- a/packages/admin/dashboard/src/hooks/use-combobox-data.tsx +++ b/packages/admin/dashboard/src/hooks/use-combobox-data.tsx @@ -20,7 +20,7 @@ type ComboboxQueryParams = { export const useComboboxData = < TResponse extends ComboboxExternalData, - TParams extends ComboboxQueryParams + TParams extends ComboboxQueryParams, >({ queryKey, queryFn, @@ -50,7 +50,6 @@ export const useComboboxData = < enabled: !!defaultValue, }) - const { data, ...rest } = useInfiniteQuery({ queryKey: [...queryKey, query], queryFn: async ({ pageParam = 0 }) => { diff --git a/packages/admin/dashboard/src/i18n/translations/$schema.json b/packages/admin/dashboard/src/i18n/translations/$schema.json index 2579af1ee867d..d755ecd0d0e86 100644 --- a/packages/admin/dashboard/src/i18n/translations/$schema.json +++ b/packages/admin/dashboard/src/i18n/translations/$schema.json @@ -258,7 +258,11 @@ "type": "string" } }, - "required": ["header_one", "header_other", "description"], + "required": [ + "header_one", + "header_other", + "description" + ], "additionalProperties": false } }, @@ -307,7 +311,11 @@ "type": "string" } }, - "required": ["insertRowAbove", "insertRowBelow", "deleteRow"], + "required": [ + "insertRowAbove", + "insertRowBelow", + "deleteRow" + ], "additionalProperties": false }, "labels": { @@ -320,7 +328,10 @@ "type": "string" } }, - "required": ["key", "value"], + "required": [ + "key", + "value" + ], "additionalProperties": false }, "complexRow": { @@ -336,7 +347,11 @@ "type": "string" } }, - "required": ["label", "description", "tooltip"], + "required": [ + "label", + "description", + "tooltip" + ], "additionalProperties": false } }, @@ -351,7 +366,12 @@ "additionalProperties": false } }, - "required": ["header", "numberOfKeys_one", "numberOfKeys_other", "edit"], + "required": [ + "header", + "numberOfKeys_one", + "numberOfKeys_other", + "edit" + ], "additionalProperties": false }, "validation": { @@ -364,7 +384,10 @@ "type": "string" } }, - "required": ["mustBeInt", "mustBePositive"], + "required": [ + "mustBeInt", + "mustBePositive" + ], "additionalProperties": false }, "actions": { @@ -550,7 +573,9 @@ "type": "string" } }, - "required": ["in"], + "required": [ + "in" + ], "additionalProperties": false }, "app": { @@ -901,7 +926,12 @@ "type": "string" } }, - "required": ["label", "dark", "light", "system"], + "required": [ + "label", + "dark", + "light", + "system" + ], "additionalProperties": false } }, @@ -924,7 +954,10 @@ "type": "string" } }, - "required": ["label", "storeSettings"], + "required": [ + "label", + "storeSettings" + ], "additionalProperties": false }, "actions": { @@ -934,11 +967,17 @@ "type": "string" } }, - "required": ["logout"], + "required": [ + "logout" + ], "additionalProperties": false } }, - "required": ["user", "store", "actions"], + "required": [ + "user", + "store", + "actions" + ], "additionalProperties": false }, "nav": { @@ -954,7 +993,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false }, "common": { @@ -964,7 +1006,9 @@ "type": "string" } }, - "required": ["extensions"], + "required": [ + "extensions" + ], "additionalProperties": false }, "main": { @@ -977,7 +1021,10 @@ "type": "string" } }, - "required": ["store", "storeSettings"], + "required": [ + "store", + "storeSettings" + ], "additionalProperties": false }, "settings": { @@ -996,15 +1043,30 @@ "type": "string" } }, - "required": ["header", "general", "developer", "myAccount"], + "required": [ + "header", + "general", + "developer", + "myAccount" + ], "additionalProperties": false } }, - "required": ["accessibility", "common", "main", "settings"], + "required": [ + "accessibility", + "common", + "main", + "settings" + ], "additionalProperties": false } }, - "required": ["search", "keyboardShortcuts", "menus", "nav"], + "required": [ + "search", + "keyboardShortcuts", + "menus", + "nav" + ], "additionalProperties": false }, "dataGrid": { @@ -1023,7 +1085,11 @@ "type": "string" } }, - "required": ["view", "resetToDefault", "disabled"], + "required": [ + "view", + "resetToDefault", + "disabled" + ], "additionalProperties": false }, "shortcuts": { @@ -1117,7 +1183,10 @@ "additionalProperties": false } }, - "required": ["label", "commands"], + "required": [ + "label", + "commands" + ], "additionalProperties": false }, "errors": { @@ -1133,11 +1202,19 @@ "type": "string" } }, - "required": ["fixError", "count_one", "count_other"], + "required": [ + "fixError", + "count_one", + "count_other" + ], "additionalProperties": false } }, - "required": ["columns", "shortcuts", "errors"], + "required": [ + "columns", + "shortcuts", + "errors" + ], "additionalProperties": false }, "filters": { @@ -1223,7 +1300,11 @@ "type": "string" } }, - "required": ["date", "compare", "addFilter"], + "required": [ + "date", + "compare", + "addFilter" + ], "additionalProperties": false }, "errorBoundary": { @@ -1293,7 +1374,12 @@ "type": "string" } }, - "required": ["header", "editHeader", "editLabel", "label"], + "required": [ + "header", + "editHeader", + "editLabel", + "label" + ], "additionalProperties": false }, "billingAddress": { @@ -1352,7 +1438,11 @@ "type": "string" } }, - "required": ["editHeader", "editLabel", "label"], + "required": [ + "editHeader", + "editLabel", + "label" + ], "additionalProperties": false }, "transferOwnership": { @@ -1374,7 +1464,10 @@ "type": "string" } }, - "required": ["order", "draft"], + "required": [ + "order", + "draft" + ], "additionalProperties": false }, "currentOwner": { @@ -1387,7 +1480,10 @@ "type": "string" } }, - "required": ["label", "hint"], + "required": [ + "label", + "hint" + ], "additionalProperties": false }, "newOwner": { @@ -1400,7 +1496,10 @@ "type": "string" } }, - "required": ["label", "hint"], + "required": [ + "label", + "hint" + ], "additionalProperties": false }, "validation": { @@ -1413,7 +1512,10 @@ "type": "string" } }, - "required": ["mustBeDifferent", "required"], + "required": [ + "mustBeDifferent", + "required" + ], "additionalProperties": false } }, @@ -1434,7 +1536,9 @@ "type": "string" } }, - "required": ["availableIn"], + "required": [ + "availableIn" + ], "additionalProperties": false }, "products": { @@ -1450,7 +1554,9 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false }, "edit": { @@ -1466,7 +1572,11 @@ "type": "string" } }, - "required": ["header", "description", "successToast"], + "required": [ + "header", + "description", + "successToast" + ], "additionalProperties": false }, "create": { @@ -1497,7 +1607,12 @@ "type": "string" } }, - "required": ["details", "organize", "variants", "inventory"], + "required": [ + "details", + "organize", + "variants", + "inventory" + ], "additionalProperties": false }, "errors": { @@ -1513,7 +1628,11 @@ "type": "string" } }, - "required": ["variants", "options", "uniqueSku"], + "required": [ + "variants", + "options", + "uniqueSku" + ], "additionalProperties": false }, "inventory": { @@ -1559,7 +1678,9 @@ "type": "string" } }, - "required": ["placeholder"], + "required": [ + "placeholder" + ], "additionalProperties": false }, "optionValues": { @@ -1569,7 +1690,9 @@ "type": "string" } }, - "required": ["placeholder"], + "required": [ + "placeholder" + ], "additionalProperties": false }, "productVariants": { @@ -1588,7 +1711,12 @@ "type": "string" } }, - "required": ["label", "hint", "alert", "tip"], + "required": [ + "label", + "hint", + "alert", + "tip" + ], "additionalProperties": false }, "productOptions": { @@ -1601,7 +1729,10 @@ "type": "string" } }, - "required": ["label", "hint"], + "required": [ + "label", + "hint" + ], "additionalProperties": false } }, @@ -1651,7 +1782,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false }, "filters": { @@ -1664,7 +1798,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false }, "columns": { @@ -1677,7 +1814,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false } }, @@ -1715,7 +1855,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false }, "upload": { @@ -1756,7 +1899,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false } }, @@ -1850,7 +1996,11 @@ "type": "string" } }, - "required": ["header", "description", "action"], + "required": [ + "header", + "description", + "action" + ], "additionalProperties": false }, "successToast": { @@ -1909,7 +2059,12 @@ "type": "string" } }, - "required": ["draft", "published", "proposed", "rejected"], + "required": [ + "draft", + "published", + "proposed", + "rejected" + ], "additionalProperties": false }, "fields": { @@ -1925,7 +2080,10 @@ "type": "string" } }, - "required": ["label", "hint"], + "required": [ + "label", + "hint" + ], "additionalProperties": false }, "subtitle": { @@ -1935,7 +2093,9 @@ "type": "string" } }, - "required": ["label"], + "required": [ + "label" + ], "additionalProperties": false }, "handle": { @@ -1948,7 +2108,10 @@ "type": "string" } }, - "required": ["label", "tooltip"], + "required": [ + "label", + "tooltip" + ], "additionalProperties": false }, "description": { @@ -1961,7 +2124,10 @@ "type": "string" } }, - "required": ["label", "hint"], + "required": [ + "label", + "hint" + ], "additionalProperties": false }, "discountable": { @@ -1974,7 +2140,10 @@ "type": "string" } }, - "required": ["label", "hint"], + "required": [ + "label", + "hint" + ], "additionalProperties": false }, "type": { @@ -1984,7 +2153,9 @@ "type": "string" } }, - "required": ["label"], + "required": [ + "label" + ], "additionalProperties": false }, "collection": { @@ -1994,7 +2165,9 @@ "type": "string" } }, - "required": ["label"], + "required": [ + "label" + ], "additionalProperties": false }, "categories": { @@ -2004,7 +2177,9 @@ "type": "string" } }, - "required": ["label"], + "required": [ + "label" + ], "additionalProperties": false }, "tags": { @@ -2014,7 +2189,9 @@ "type": "string" } }, - "required": ["label"], + "required": [ + "label" + ], "additionalProperties": false }, "sales_channels": { @@ -2027,7 +2204,10 @@ "type": "string" } }, - "required": ["label", "hint"], + "required": [ + "label", + "hint" + ], "additionalProperties": false }, "countryOrigin": { @@ -2037,7 +2217,9 @@ "type": "string" } }, - "required": ["label"], + "required": [ + "label" + ], "additionalProperties": false }, "material": { @@ -2047,7 +2229,9 @@ "type": "string" } }, - "required": ["label"], + "required": [ + "label" + ], "additionalProperties": false }, "width": { @@ -2057,7 +2241,9 @@ "type": "string" } }, - "required": ["label"], + "required": [ + "label" + ], "additionalProperties": false }, "length": { @@ -2067,7 +2253,9 @@ "type": "string" } }, - "required": ["label"], + "required": [ + "label" + ], "additionalProperties": false }, "height": { @@ -2077,7 +2265,9 @@ "type": "string" } }, - "required": ["label"], + "required": [ + "label" + ], "additionalProperties": false }, "weight": { @@ -2087,7 +2277,9 @@ "type": "string" } }, - "required": ["label"], + "required": [ + "label" + ], "additionalProperties": false }, "options": { @@ -2136,7 +2328,10 @@ "type": "string" } }, - "required": ["label", "hint"], + "required": [ + "label", + "hint" + ], "additionalProperties": false }, "mid_code": { @@ -2146,7 +2341,9 @@ "type": "string" } }, - "required": ["label"], + "required": [ + "label" + ], "additionalProperties": false }, "hs_code": { @@ -2156,7 +2353,9 @@ "type": "string" } }, - "required": ["label"], + "required": [ + "label" + ], "additionalProperties": false } }, @@ -2197,7 +2396,10 @@ "type": "string" } }, - "required": ["header", "success"], + "required": [ + "header", + "success" + ], "additionalProperties": false }, "create": { @@ -2207,7 +2409,9 @@ "type": "string" } }, - "required": ["header"], + "required": [ + "header" + ], "additionalProperties": false }, "deleteWarning": { @@ -2253,7 +2457,10 @@ "type": "string" } }, - "required": ["inventoryItems", "inventoryKit"], + "required": [ + "inventoryItems", + "inventoryKit" + ], "additionalProperties": false }, "inventoryKit": { @@ -2272,7 +2479,10 @@ "type": "string" } }, - "required": ["itemId", "quantity"], + "required": [ + "itemId", + "quantity" + ], "additionalProperties": false }, "header": { @@ -2367,7 +2577,10 @@ "type": "string" } }, - "required": ["header", "successToast"], + "required": [ + "header", + "successToast" + ], "additionalProperties": false }, "create": { @@ -2380,14 +2593,22 @@ "type": "string" } }, - "required": ["header", "successToast"], + "required": [ + "header", + "successToast" + ], "additionalProperties": false }, "deleteWarning": { "type": "string" } }, - "required": ["header", "edit", "create", "deleteWarning"], + "required": [ + "header", + "edit", + "create", + "deleteWarning" + ], "additionalProperties": false }, "organization": { @@ -2409,15 +2630,23 @@ "type": "string" } }, - "required": ["success"], + "required": [ + "success" + ], "additionalProperties": false } }, - "required": ["header", "toasts"], + "required": [ + "header", + "toasts" + ], "additionalProperties": false } }, - "required": ["header", "edit"], + "required": [ + "header", + "edit" + ], "additionalProperties": false }, "toasts": { @@ -2436,7 +2665,10 @@ "type": "string" } }, - "required": ["header", "description"], + "required": [ + "header", + "description" + ], "additionalProperties": false }, "error": { @@ -2446,15 +2678,22 @@ "type": "string" } }, - "required": ["header"], + "required": [ + "header" + ], "additionalProperties": false } }, - "required": ["success", "error"], + "required": [ + "success", + "error" + ], "additionalProperties": false } }, - "required": ["delete"], + "required": [ + "delete" + ], "additionalProperties": false } }, @@ -2532,7 +2771,9 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false }, "add": { @@ -2545,7 +2786,10 @@ "type": "string" } }, - "required": ["successToast_one", "successToast_other"], + "required": [ + "successToast_one", + "successToast_other" + ], "additionalProperties": false }, "remove": { @@ -2558,11 +2802,18 @@ "type": "string" } }, - "required": ["successToast_one", "successToast_other"], + "required": [ + "successToast_one", + "successToast_other" + ], "additionalProperties": false } }, - "required": ["list", "add", "remove"], + "required": [ + "list", + "add", + "remove" + ], "additionalProperties": false } }, @@ -2610,14 +2861,22 @@ "type": "string" } }, - "required": ["details", "organize"], + "required": [ + "details", + "organize" + ], "additionalProperties": false }, "successToast": { "type": "string" } }, - "required": ["header", "hint", "tabs", "successToast"], + "required": [ + "header", + "hint", + "tabs", + "successToast" + ], "additionalProperties": false }, "edit": { @@ -2633,7 +2892,11 @@ "type": "string" } }, - "required": ["header", "description", "successToast"], + "required": [ + "header", + "description", + "successToast" + ], "additionalProperties": false }, "delete": { @@ -2646,7 +2909,10 @@ "type": "string" } }, - "required": ["confirmation", "successToast"], + "required": [ + "confirmation", + "successToast" + ], "additionalProperties": false }, "products": { @@ -2703,11 +2969,17 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false } }, - "required": ["add", "remove", "list"], + "required": [ + "add", + "remove", + "list" + ], "additionalProperties": false }, "organize": { @@ -2720,7 +2992,10 @@ "type": "string" } }, - "required": ["header", "action"], + "required": [ + "header", + "action" + ], "additionalProperties": false }, "fields": { @@ -2739,7 +3014,11 @@ "type": "string" } }, - "required": ["label", "internal", "public"], + "required": [ + "label", + "internal", + "public" + ], "additionalProperties": false }, "status": { @@ -2755,7 +3034,11 @@ "type": "string" } }, - "required": ["label", "active", "inactive"], + "required": [ + "label", + "active", + "inactive" + ], "additionalProperties": false }, "path": { @@ -2768,7 +3051,10 @@ "type": "string" } }, - "required": ["label", "tooltip"], + "required": [ + "label", + "tooltip" + ], "additionalProperties": false }, "children": { @@ -2778,7 +3064,9 @@ "type": "string" } }, - "required": ["label"], + "required": [ + "label" + ], "additionalProperties": false }, "new": { @@ -2788,11 +3076,19 @@ "type": "string" } }, - "required": ["label"], + "required": [ + "label" + ], "additionalProperties": false } }, - "required": ["visibility", "status", "path", "children", "new"], + "required": [ + "visibility", + "status", + "path", + "children", + "new" + ], "additionalProperties": false } }, @@ -2942,8 +3238,11 @@ "type": "string" } }, - "required": ["noAvaliableQuantity", "quantityOutOfRange"], - "additionalProperties": false + "required": [ + "noAvaliableQuantity", + "quantityOutOfRange" + ], + "additionalProperties": false } }, "required": [ @@ -2978,11 +3277,15 @@ "type": "string" } }, - "required": ["stockedQuantity"], + "required": [ + "stockedQuantity" + ], "additionalProperties": false } }, - "required": ["errors"], + "required": [ + "errors" + ], "additionalProperties": false }, "toast": { @@ -2998,7 +3301,11 @@ "type": "string" } }, - "required": ["updateLocations", "updateLevel", "updateItem"], + "required": [ + "updateLocations", + "updateLevel", + "updateItem" + ], "additionalProperties": false } }, @@ -3104,7 +3411,9 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false }, "create": { @@ -3120,7 +3429,11 @@ "type": "string" } }, - "required": ["header", "hint", "successToast"], + "required": [ + "header", + "hint", + "successToast" + ], "additionalProperties": false }, "groups": { @@ -3145,7 +3458,9 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false }, "add": { @@ -3161,11 +3476,16 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false } }, - "required": ["success", "list"], + "required": [ + "success", + "list" + ], "additionalProperties": false }, "removed": { @@ -3181,11 +3501,16 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false } }, - "required": ["success", "list"], + "required": [ + "success", + "list" + ], "additionalProperties": false } }, @@ -3213,7 +3538,11 @@ "type": "string" } }, - "required": ["header", "emailDisabledTooltip", "successToast"], + "required": [ + "header", + "emailDisabledTooltip", + "successToast" + ], "additionalProperties": false }, "delete": { @@ -3229,7 +3558,11 @@ "type": "string" } }, - "required": ["title", "description", "successToast"], + "required": [ + "title", + "description", + "successToast" + ], "additionalProperties": false }, "fields": { @@ -3245,7 +3578,11 @@ "type": "string" } }, - "required": ["guest", "registered", "groups"], + "required": [ + "guest", + "registered", + "groups" + ], "additionalProperties": false }, "registered": { @@ -3294,7 +3631,11 @@ "type": "string" } }, - "required": ["header", "hint", "successToast"], + "required": [ + "header", + "hint", + "successToast" + ], "additionalProperties": false }, "edit": { @@ -3307,7 +3648,10 @@ "type": "string" } }, - "required": ["header", "successToast"], + "required": [ + "header", + "successToast" + ], "additionalProperties": false }, "delete": { @@ -3323,7 +3667,11 @@ "type": "string" } }, - "required": ["title", "description", "successToast"], + "required": [ + "title", + "description", + "successToast" + ], "additionalProperties": false }, "customers": { @@ -3348,11 +3696,17 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false } }, - "required": ["successToast_one", "successToast_other", "list"], + "required": [ + "successToast_one", + "successToast_other", + "list" + ], "additionalProperties": false }, "remove": { @@ -3386,11 +3740,18 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false } }, - "required": ["alreadyAddedTooltip", "add", "remove", "list"], + "required": [ + "alreadyAddedTooltip", + "add", + "remove", + "list" + ], "additionalProperties": false } }, @@ -3432,7 +3793,9 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false }, "summary": { @@ -3724,7 +4087,10 @@ "type": "string" } }, - "required": ["title", "titlePending"], + "required": [ + "title", + "titlePending" + ], "additionalProperties": false }, "toast": { @@ -3737,7 +4103,10 @@ "type": "string" } }, - "required": ["canceledSuccessfully", "confirmedSuccessfully"], + "required": [ + "canceledSuccessfully", + "confirmedSuccessfully" + ], "additionalProperties": false }, "validation": { @@ -3747,7 +4116,9 @@ "type": "string" } }, - "required": ["quantityLowerThanFulfillment"], + "required": [ + "quantityLowerThanFulfillment" + ], "additionalProperties": false } }, @@ -3788,7 +4159,10 @@ "type": "string" } }, - "required": ["title", "requestSuccess"], + "required": [ + "title", + "requestSuccess" + ], "additionalProperties": false }, "shippingAddress": { @@ -3801,7 +4175,10 @@ "type": "string" } }, - "required": ["title", "requestSuccess"], + "required": [ + "title", + "requestSuccess" + ], "additionalProperties": false }, "billingAddress": { @@ -3814,11 +4191,18 @@ "type": "string" } }, - "required": ["title", "requestSuccess"], + "required": [ + "title", + "requestSuccess" + ], "additionalProperties": false } }, - "required": ["email", "shippingAddress", "billingAddress"], + "required": [ + "email", + "shippingAddress", + "billingAddress" + ], "additionalProperties": false }, "returns": { @@ -3924,7 +4308,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false }, "placeholders": { @@ -3940,7 +4327,10 @@ "type": "string" } }, - "required": ["title", "hint"], + "required": [ + "title", + "hint" + ], "additionalProperties": false }, "outboundShippingOptions": { @@ -3953,7 +4343,10 @@ "type": "string" } }, - "required": ["title", "hint"], + "required": [ + "title", + "hint" + ], "additionalProperties": false } }, @@ -4038,7 +4431,10 @@ "type": "string" } }, - "required": ["canceledSuccessfully", "confirmedSuccessfully"], + "required": [ + "canceledSuccessfully", + "confirmedSuccessfully" + ], "additionalProperties": false }, "panel": { @@ -4051,7 +4447,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false } }, @@ -4140,11 +4539,15 @@ "type": "string" } }, - "required": ["successToast"], + "required": [ + "successToast" + ], "additionalProperties": false } }, - "required": ["cancelClaim"], + "required": [ + "cancelClaim" + ], "additionalProperties": false }, "cancel": { @@ -4157,7 +4560,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false }, "tooltips": { @@ -4167,7 +4573,9 @@ "type": "string" } }, - "required": ["onlyReturnShippingOptions"], + "required": [ + "onlyReturnShippingOptions" + ], "additionalProperties": false }, "toast": { @@ -4180,7 +4588,10 @@ "type": "string" } }, - "required": ["canceledSuccessfully", "confirmedSuccessfully"], + "required": [ + "canceledSuccessfully", + "confirmedSuccessfully" + ], "additionalProperties": false }, "panel": { @@ -4193,7 +4604,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false } }, @@ -4263,11 +4677,15 @@ "type": "string" } }, - "required": ["successToast"], + "required": [ + "successToast" + ], "additionalProperties": false } }, - "required": ["cancelExchange"], + "required": [ + "cancelExchange" + ], "additionalProperties": false }, "cancel": { @@ -4280,7 +4698,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false }, "tooltips": { @@ -4290,7 +4711,9 @@ "type": "string" } }, - "required": ["onlyReturnShippingOptions"], + "required": [ + "onlyReturnShippingOptions" + ], "additionalProperties": false }, "toast": { @@ -4303,7 +4726,10 @@ "type": "string" } }, - "required": ["canceledSuccessfully", "confirmedSuccessfully"], + "required": [ + "canceledSuccessfully", + "confirmedSuccessfully" + ], "additionalProperties": false }, "panel": { @@ -4316,7 +4742,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false } }, @@ -4350,7 +4779,10 @@ "type": "string" } }, - "required": ["allocatedLabel", "notAllocatedLabel"], + "required": [ + "allocatedLabel", + "notAllocatedLabel" + ], "additionalProperties": false }, "allocateItems": { @@ -4387,7 +4819,9 @@ "type": "string" } }, - "required": ["created"], + "required": [ + "created" + ], "additionalProperties": false }, "error": { @@ -4397,7 +4831,9 @@ "type": "string" } }, - "required": ["quantityNotAllocated"], + "required": [ + "quantityNotAllocated" + ], "additionalProperties": false } }, @@ -4520,7 +4956,11 @@ "type": "string" } }, - "required": ["wrongQuantity", "wrongQuantity_other", "noItems"], + "required": [ + "wrongQuantity", + "wrongQuantity_other", + "noItems" + ], "additionalProperties": false }, "status": { @@ -4666,7 +5106,11 @@ "type": "string" } }, - "required": ["amountToLarge", "amountNegative", "reasonRequired"], + "required": [ + "amountToLarge", + "amountNegative", + "reasonRequired" + ], "additionalProperties": false } }, @@ -4762,7 +5206,10 @@ "type": "string" } }, - "required": ["toReturn", "toSend"], + "required": [ + "toReturn", + "toSend" + ], "additionalProperties": false }, "placed": { @@ -4775,7 +5222,10 @@ "type": "string" } }, - "required": ["title", "fromSalesChannel"], + "required": [ + "title", + "fromSalesChannel" + ], "additionalProperties": false }, "canceled": { @@ -4785,7 +5235,9 @@ "type": "string" } }, - "required": ["title"], + "required": [ + "title" + ], "additionalProperties": false }, "payment": { @@ -4804,7 +5256,12 @@ "type": "string" } }, - "required": ["awaiting", "captured", "canceled", "refunded"], + "required": [ + "awaiting", + "captured", + "canceled", + "refunded" + ], "additionalProperties": false }, "fulfillment": { @@ -4877,7 +5334,10 @@ "type": "string" } }, - "required": ["comment", "byLine"], + "required": [ + "comment", + "byLine" + ], "additionalProperties": false }, "claim": { @@ -4938,7 +5398,10 @@ "type": "string" } }, - "required": ["requested", "confirmed"], + "required": [ + "requested", + "confirmed" + ], "additionalProperties": false }, "transfer": { @@ -4954,7 +5417,11 @@ "type": "string" } }, - "required": ["requested", "confirmed", "declined"], + "required": [ + "requested", + "confirmed", + "declined" + ], "additionalProperties": false }, "update_order": { @@ -4970,7 +5437,11 @@ "type": "string" } }, - "required": ["shipping_address", "billing_address", "email"], + "required": [ + "shipping_address", + "billing_address", + "email" + ], "additionalProperties": false } }, @@ -5015,7 +5486,11 @@ "type": "string" } }, - "required": ["displayId", "refundableAmount", "returnableQuantity"], + "required": [ + "displayId", + "refundableAmount", + "returnableQuantity" + ], "additionalProperties": false } }, @@ -5074,7 +5549,11 @@ "type": "string" } }, - "required": ["label", "warningTitle", "warningDescription"], + "required": [ + "label", + "warningTitle", + "warningDescription" + ], "additionalProperties": false }, "status": { @@ -5087,7 +5566,10 @@ "type": "string" } }, - "required": ["open", "completed"], + "required": [ + "open", + "completed" + ], "additionalProperties": false }, "create": { @@ -5228,7 +5710,9 @@ "type": "string" } }, - "required": ["description"], + "required": [ + "description" + ], "additionalProperties": false }, "create": { @@ -5244,7 +5728,11 @@ "type": "string" } }, - "required": ["header", "hint", "successToast"], + "required": [ + "header", + "hint", + "successToast" + ], "additionalProperties": false }, "edit": { @@ -5260,7 +5748,11 @@ "type": "string" } }, - "required": ["header", "viewInventory", "successToast"], + "required": [ + "header", + "viewInventory", + "successToast" + ], "additionalProperties": false }, "delete": { @@ -5270,7 +5762,9 @@ "type": "string" } }, - "required": ["confirmation"], + "required": [ + "confirmation" + ], "additionalProperties": false }, "fulfillmentProviders": { @@ -5319,7 +5813,9 @@ "type": "string" } }, - "required": ["header"], + "required": [ + "header" + ], "additionalProperties": false }, "shipping": { @@ -5329,7 +5825,9 @@ "type": "string" } }, - "required": ["header"], + "required": [ + "header" + ], "additionalProperties": false }, "disable": { @@ -5345,7 +5843,11 @@ "type": "string" } }, - "required": ["confirmation", "pickup", "shipping"], + "required": [ + "confirmation", + "pickup", + "shipping" + ], "additionalProperties": false }, "enable": { @@ -5358,11 +5860,19 @@ "type": "string" } }, - "required": ["pickup", "shipping"], + "required": [ + "pickup", + "shipping" + ], "additionalProperties": false } }, - "required": ["pickup", "shipping", "disable", "enable"], + "required": [ + "pickup", + "shipping", + "disable", + "enable" + ], "additionalProperties": false }, "sidebar": { @@ -5381,11 +5891,17 @@ "type": "string" } }, - "required": ["label", "description"], + "required": [ + "label", + "description" + ], "additionalProperties": false } }, - "required": ["header", "shippingProfiles"], + "required": [ + "header", + "shippingProfiles" + ], "additionalProperties": false }, "salesChannels": { @@ -5442,7 +5958,12 @@ "type": "string" } }, - "required": ["header", "hint", "label", "successToast"], + "required": [ + "header", + "hint", + "label", + "successToast" + ], "additionalProperties": false }, "returns": { @@ -5461,7 +5982,12 @@ "type": "string" } }, - "required": ["header", "hint", "label", "successToast"], + "required": [ + "header", + "hint", + "label", + "successToast" + ], "additionalProperties": false }, "tabs": { @@ -5474,14 +6000,22 @@ "type": "string" } }, - "required": ["details", "prices"], + "required": [ + "details", + "prices" + ], "additionalProperties": false }, "action": { "type": "string" } }, - "required": ["shipping", "returns", "tabs", "action"], + "required": [ + "shipping", + "returns", + "tabs", + "action" + ], "additionalProperties": false }, "delete": { @@ -5494,7 +6028,10 @@ "type": "string" } }, - "required": ["confirmation", "successToast"], + "required": [ + "confirmation", + "successToast" + ], "additionalProperties": false }, "edit": { @@ -5510,7 +6047,11 @@ "type": "string" } }, - "required": ["header", "action", "successToast"], + "required": [ + "header", + "action", + "successToast" + ], "additionalProperties": false }, "pricing": { @@ -5520,7 +6061,9 @@ "type": "string" } }, - "required": ["action"], + "required": [ + "action" + ], "additionalProperties": false }, "conditionalPrices": { @@ -5539,7 +6082,9 @@ "type": "string" } }, - "required": ["cartItemTotal"], + "required": [ + "cartItemTotal" + ], "additionalProperties": false }, "summaries": { @@ -5555,7 +6100,11 @@ "type": "string" } }, - "required": ["range", "greaterThan", "lessThan"], + "required": [ + "range", + "greaterThan", + "lessThan" + ], "additionalProperties": false }, "actions": { @@ -5568,7 +6117,10 @@ "type": "string" } }, - "required": ["addPrice", "manageConditionalPrices"], + "required": [ + "addPrice", + "manageConditionalPrices" + ], "additionalProperties": false }, "rules": { @@ -5584,7 +6136,11 @@ "type": "string" } }, - "required": ["amount", "gte", "lte"], + "required": [ + "amount", + "gte", + "lte" + ], "additionalProperties": false }, "customRules": { @@ -5606,7 +6162,13 @@ "type": "string" } }, - "required": ["label", "tooltip", "eq", "gt", "lt"], + "required": [ + "label", + "tooltip", + "eq", + "gt", + "lt" + ], "additionalProperties": false }, "errors": { @@ -5696,7 +6258,10 @@ "type": "string" } }, - "required": ["label", "hint"], + "required": [ + "label", + "hint" + ], "additionalProperties": false }, "calculated": { @@ -5709,15 +6274,24 @@ "type": "string" } }, - "required": ["label", "hint"], + "required": [ + "label", + "hint" + ], "additionalProperties": false } }, - "required": ["fixed", "calculated"], + "required": [ + "fixed", + "calculated" + ], "additionalProperties": false } }, - "required": ["label", "options"], + "required": [ + "label", + "options" + ], "additionalProperties": false }, "enableInStore": { @@ -5730,7 +6304,10 @@ "type": "string" } }, - "required": ["label", "hint"], + "required": [ + "label", + "hint" + ], "additionalProperties": false }, "provider": { @@ -5738,6 +6315,9 @@ }, "profile": { "type": "string" + }, + "fulfillmentOption": { + "type": "string" } }, "required": [ @@ -5745,7 +6325,8 @@ "priceType", "enableInStore", "provider", - "profile" + "profile", + "fulfillmentOption" ], "additionalProperties": false } @@ -5797,7 +6378,10 @@ "type": "string" } }, - "required": ["header", "successToast"], + "required": [ + "header", + "successToast" + ], "additionalProperties": false }, "delete": { @@ -5810,7 +6394,10 @@ "type": "string" } }, - "required": ["confirmation", "successToast"], + "required": [ + "confirmation", + "successToast" + ], "additionalProperties": false }, "manageAreas": { @@ -5832,7 +6419,13 @@ "type": "string" } }, - "required": ["header", "action", "label", "hint", "successToast"], + "required": [ + "header", + "action", + "label", + "hint", + "successToast" + ], "additionalProperties": false }, "fields": { @@ -5845,11 +6438,20 @@ "type": "string" } }, - "required": ["noRecords", "tip"], + "required": [ + "noRecords", + "tip" + ], "additionalProperties": false } }, - "required": ["create", "edit", "delete", "manageAreas", "fields"], + "required": [ + "create", + "edit", + "delete", + "manageAreas", + "fields" + ], "additionalProperties": false } }, @@ -5890,7 +6492,11 @@ "type": "string" } }, - "required": ["header", "hint", "successToast"], + "required": [ + "header", + "hint", + "successToast" + ], "additionalProperties": false }, "delete": { @@ -5906,21 +6512,33 @@ "type": "string" } }, - "required": ["title", "description", "successToast"], - "additionalProperties": false - }, - "tooltip": { - "type": "object", + "required": [ + "title", + "description", + "successToast" + ], + "additionalProperties": false + }, + "tooltip": { + "type": "object", "properties": { "type": { "type": "string" } }, - "required": ["type"], + "required": [ + "type" + ], "additionalProperties": false } }, - "required": ["domain", "subtitle", "create", "delete", "tooltip"], + "required": [ + "domain", + "subtitle", + "create", + "delete", + "tooltip" + ], "additionalProperties": false }, "taxRegions": { @@ -5936,7 +6554,9 @@ "type": "string" } }, - "required": ["hint"], + "required": [ + "hint" + ], "additionalProperties": false }, "delete": { @@ -5949,7 +6569,10 @@ "type": "string" } }, - "required": ["confirmation", "successToast"], + "required": [ + "confirmation", + "successToast" + ], "additionalProperties": false }, "create": { @@ -5971,14 +6594,22 @@ "type": "string" } }, - "required": ["rateIsRequired", "nameIsRequired"], + "required": [ + "rateIsRequired", + "nameIsRequired" + ], "additionalProperties": false }, "successToast": { "type": "string" } }, - "required": ["header", "hint", "errors", "successToast"], + "required": [ + "header", + "hint", + "errors", + "successToast" + ], "additionalProperties": false }, "province": { @@ -5997,11 +6628,17 @@ "type": "string" } }, - "required": ["header", "hint"], + "required": [ + "header", + "hint" + ], "additionalProperties": false } }, - "required": ["header", "create"], + "required": [ + "header", + "create" + ], "additionalProperties": false }, "state": { @@ -6020,11 +6657,17 @@ "type": "string" } }, - "required": ["header", "hint"], + "required": [ + "header", + "hint" + ], "additionalProperties": false } }, - "required": ["header", "create"], + "required": [ + "header", + "create" + ], "additionalProperties": false }, "stateOrTerritory": { @@ -6043,11 +6686,17 @@ "type": "string" } }, - "required": ["header", "hint"], + "required": [ + "header", + "hint" + ], "additionalProperties": false } }, - "required": ["header", "create"], + "required": [ + "header", + "create" + ], "additionalProperties": false }, "county": { @@ -6066,11 +6715,17 @@ "type": "string" } }, - "required": ["header", "hint"], + "required": [ + "header", + "hint" + ], "additionalProperties": false } }, - "required": ["header", "create"], + "required": [ + "header", + "create" + ], "additionalProperties": false }, "region": { @@ -6089,11 +6744,17 @@ "type": "string" } }, - "required": ["header", "hint"], + "required": [ + "header", + "hint" + ], "additionalProperties": false } }, - "required": ["header", "create"], + "required": [ + "header", + "create" + ], "additionalProperties": false }, "department": { @@ -6112,11 +6773,17 @@ "type": "string" } }, - "required": ["header", "hint"], + "required": [ + "header", + "hint" + ], "additionalProperties": false } }, - "required": ["header", "create"], + "required": [ + "header", + "create" + ], "additionalProperties": false }, "territory": { @@ -6135,11 +6802,17 @@ "type": "string" } }, - "required": ["header", "hint"], + "required": [ + "header", + "hint" + ], "additionalProperties": false } }, - "required": ["header", "create"], + "required": [ + "header", + "create" + ], "additionalProperties": false }, "prefecture": { @@ -6158,11 +6831,17 @@ "type": "string" } }, - "required": ["header", "hint"], + "required": [ + "header", + "hint" + ], "additionalProperties": false } }, - "required": ["header", "create"], + "required": [ + "header", + "create" + ], "additionalProperties": false }, "district": { @@ -6181,11 +6860,17 @@ "type": "string" } }, - "required": ["header", "hint"], + "required": [ + "header", + "hint" + ], "additionalProperties": false } }, - "required": ["header", "create"], + "required": [ + "header", + "create" + ], "additionalProperties": false }, "governorate": { @@ -6204,11 +6889,17 @@ "type": "string" } }, - "required": ["header", "hint"], + "required": [ + "header", + "hint" + ], "additionalProperties": false } }, - "required": ["header", "create"], + "required": [ + "header", + "create" + ], "additionalProperties": false }, "canton": { @@ -6227,11 +6918,17 @@ "type": "string" } }, - "required": ["header", "hint"], + "required": [ + "header", + "hint" + ], "additionalProperties": false } }, - "required": ["header", "create"], + "required": [ + "header", + "create" + ], "additionalProperties": false }, "emirate": { @@ -6250,11 +6947,17 @@ "type": "string" } }, - "required": ["header", "hint"], + "required": [ + "header", + "hint" + ], "additionalProperties": false } }, - "required": ["header", "create"], + "required": [ + "header", + "create" + ], "additionalProperties": false }, "sublevel": { @@ -6273,11 +6976,17 @@ "type": "string" } }, - "required": ["header", "hint"], + "required": [ + "header", + "hint" + ], "additionalProperties": false } }, - "required": ["header", "create"], + "required": [ + "header", + "create" + ], "additionalProperties": false }, "taxOverrides": { @@ -6296,7 +7005,10 @@ "type": "string" } }, - "required": ["header", "hint"], + "required": [ + "header", + "hint" + ], "additionalProperties": false }, "edit": { @@ -6309,11 +7021,18 @@ "type": "string" } }, - "required": ["header", "hint"], + "required": [ + "header", + "hint" + ], "additionalProperties": false } }, - "required": ["header", "create", "edit"], + "required": [ + "header", + "create", + "edit" + ], "additionalProperties": false }, "taxRates": { @@ -6332,7 +7051,11 @@ "type": "string" } }, - "required": ["header", "hint", "successToast"], + "required": [ + "header", + "hint", + "successToast" + ], "additionalProperties": false }, "edit": { @@ -6348,7 +7071,11 @@ "type": "string" } }, - "required": ["header", "hint", "successToast"], + "required": [ + "header", + "hint", + "successToast" + ], "additionalProperties": false }, "delete": { @@ -6361,11 +7088,18 @@ "type": "string" } }, - "required": ["confirmation", "successToast"], + "required": [ + "confirmation", + "successToast" + ], "additionalProperties": false } }, - "required": ["create", "edit", "delete"], + "required": [ + "create", + "edit", + "delete" + ], "additionalProperties": false }, "fields": { @@ -6387,7 +7121,12 @@ "type": "string" } }, - "required": ["label", "hint", "true", "false"], + "required": [ + "label", + "hint", + "true", + "false" + ], "additionalProperties": false }, "defaultTaxRate": { @@ -6403,7 +7142,11 @@ "type": "string" } }, - "required": ["label", "tooltip", "action"], + "required": [ + "label", + "tooltip", + "action" + ], "additionalProperties": false }, "taxRate": { @@ -6462,7 +7205,11 @@ "type": "string" } }, - "required": ["in", "on", "and"], + "required": [ + "in", + "on", + "and" + ], "additionalProperties": false }, "placeholders": { @@ -6528,7 +7275,9 @@ "type": "string" } }, - "required": ["header"], + "required": [ + "header" + ], "additionalProperties": false }, "values_one": { @@ -6700,7 +7449,10 @@ "type": "string" } }, - "required": ["sublevel", "notPartOfCountry"], + "required": [ + "sublevel", + "notPartOfCountry" + ], "additionalProperties": false }, "alert": { @@ -6716,11 +7468,20 @@ "type": "string" } }, - "required": ["header", "description", "action"], + "required": [ + "header", + "description", + "action" + ], "additionalProperties": false } }, - "required": ["labels", "placeholders", "tooltips", "alert"], + "required": [ + "labels", + "placeholders", + "tooltips", + "alert" + ], "additionalProperties": false }, "noDefaultRate": { @@ -6733,7 +7494,10 @@ "type": "string" } }, - "required": ["label", "tooltip"], + "required": [ + "label", + "tooltip" + ], "additionalProperties": false } }, @@ -6786,7 +7550,9 @@ "type": "string" } }, - "required": ["details"], + "required": [ + "details" + ], "additionalProperties": false }, "tabs": { @@ -6802,7 +7568,11 @@ "type": "string" } }, - "required": ["template", "details", "campaign"], + "required": [ + "template", + "details", + "campaign" + ], "additionalProperties": false }, "fields": { @@ -6839,7 +7609,9 @@ "type": "string" } }, - "required": ["tooltip"], + "required": [ + "tooltip" + ], "additionalProperties": false }, "conditions": { @@ -6855,7 +7627,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false }, "target-rules": { @@ -6868,7 +7643,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false }, "buy-rules": { @@ -6881,11 +7659,18 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false } }, - "required": ["rules", "target-rules", "buy-rules"], + "required": [ + "rules", + "target-rules", + "buy-rules" + ], "additionalProperties": false } }, @@ -6910,7 +7695,9 @@ "type": "string" } }, - "required": ["campaignType"], + "required": [ + "campaignType" + ], "additionalProperties": false }, "errors": { @@ -6923,7 +7710,10 @@ "type": "string" } }, - "required": ["requiredField", "promotionTabError"], + "required": [ + "requiredField", + "promotionTabError" + ], "additionalProperties": false }, "toasts": { @@ -6933,7 +7723,9 @@ "type": "string" } }, - "required": ["promotionCreateSuccess"], + "required": [ + "promotionCreateSuccess" + ], "additionalProperties": false }, "create": { @@ -6955,7 +7747,9 @@ "type": "string" } }, - "required": ["title"], + "required": [ + "title" + ], "additionalProperties": false }, "target-rules": { @@ -6965,7 +7759,9 @@ "type": "string" } }, - "required": ["title"], + "required": [ + "title" + ], "additionalProperties": false }, "buy-rules": { @@ -6975,11 +7771,18 @@ "type": "string" } }, - "required": ["title"], + "required": [ + "title" + ], "additionalProperties": false } }, - "required": ["title", "rules", "target-rules", "buy-rules"], + "required": [ + "title", + "rules", + "target-rules", + "buy-rules" + ], "additionalProperties": false }, "campaign": { @@ -6998,7 +7801,10 @@ "type": "string" } }, - "required": ["header", "successToast"], + "required": [ + "header", + "successToast" + ], "additionalProperties": false }, "actions": { @@ -7008,11 +7814,17 @@ "type": "string" } }, - "required": ["goToCampaign"], + "required": [ + "goToCampaign" + ], "additionalProperties": false } }, - "required": ["header", "edit", "actions"], + "required": [ + "header", + "edit", + "actions" + ], "additionalProperties": false }, "campaign_currency": { @@ -7022,7 +7834,9 @@ "type": "string" } }, - "required": ["tooltip"], + "required": [ + "tooltip" + ], "additionalProperties": false }, "form": { @@ -7059,11 +7873,18 @@ "type": "string" } }, - "required": ["title", "desc"], + "required": [ + "title", + "desc" + ], "additionalProperties": false } }, - "required": ["title", "description", "placeholder"], + "required": [ + "title", + "description", + "placeholder" + ], "additionalProperties": false }, "new": { @@ -7076,7 +7897,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false }, "none": { @@ -7089,11 +7913,18 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false } }, - "required": ["existing", "new", "none"], + "required": [ + "existing", + "new", + "none" + ], "additionalProperties": false }, "status": { @@ -7103,7 +7934,9 @@ "type": "string" } }, - "required": ["title"], + "required": [ + "title" + ], "additionalProperties": false }, "method": { @@ -7122,7 +7955,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false }, "automatic": { @@ -7135,11 +7971,18 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false } }, - "required": ["label", "code", "automatic"], + "required": [ + "label", + "code", + "automatic" + ], "additionalProperties": false }, "max_quantity": { @@ -7152,7 +7995,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false }, "type": { @@ -7168,7 +8014,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false }, "buyget": { @@ -7181,11 +8030,17 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false } }, - "required": ["standard", "buyget"], + "required": [ + "standard", + "buyget" + ], "additionalProperties": false }, "allocation": { @@ -7201,7 +8056,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false }, "across": { @@ -7214,11 +8072,17 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false } }, - "required": ["each", "across"], + "required": [ + "each", + "across" + ], "additionalProperties": false }, "code": { @@ -7231,7 +8095,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false }, "value": { @@ -7241,7 +8108,9 @@ "type": "string" } }, - "required": ["title"], + "required": [ + "title" + ], "additionalProperties": false }, "value_type": { @@ -7257,7 +8126,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false }, "percentage": { @@ -7270,11 +8142,17 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false } }, - "required": ["fixed", "percentage"], + "required": [ + "fixed", + "percentage" + ], "additionalProperties": false } }, @@ -7316,11 +8194,16 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false } }, - "required": ["add", "list"], + "required": [ + "add", + "list" + ], "additionalProperties": false } }, @@ -7366,7 +8249,11 @@ "type": "string" } }, - "required": ["active", "expired", "scheduled"], + "required": [ + "active", + "expired", + "scheduled" + ], "additionalProperties": false }, "delete": { @@ -7382,7 +8269,11 @@ "type": "string" } }, - "required": ["title", "description", "successToast"], + "required": [ + "title", + "description", + "successToast" + ], "additionalProperties": false }, "edit": { @@ -7398,7 +8289,11 @@ "type": "string" } }, - "required": ["header", "description", "successToast"], + "required": [ + "header", + "description", + "successToast" + ], "additionalProperties": false }, "configuration": { @@ -7420,11 +8315,18 @@ "type": "string" } }, - "required": ["header", "description", "successToast"], + "required": [ + "header", + "description", + "successToast" + ], "additionalProperties": false } }, - "required": ["header", "edit"], + "required": [ + "header", + "edit" + ], "additionalProperties": false }, "create": { @@ -7486,7 +8388,9 @@ "type": "string" } }, - "required": ["hint"], + "required": [ + "hint" + ], "additionalProperties": false } }, @@ -7515,7 +8419,10 @@ "type": "string" } }, - "required": ["hint", "header"], + "required": [ + "hint", + "header" + ], "additionalProperties": false }, "details": { @@ -7537,7 +8444,12 @@ "type": "string" } }, - "required": ["type", "currency", "limit", "used"], + "required": [ + "type", + "currency", + "limit", + "used" + ], "additionalProperties": false }, "type": { @@ -7553,7 +8465,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false }, "usage": { @@ -7566,11 +8481,17 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false } }, - "required": ["spend", "usage"], + "required": [ + "spend", + "usage" + ], "additionalProperties": false }, "edit": { @@ -7580,11 +8501,19 @@ "type": "string" } }, - "required": ["header"], + "required": [ + "header" + ], "additionalProperties": false } }, - "required": ["create", "details", "fields", "type", "edit"], + "required": [ + "create", + "details", + "fields", + "type", + "edit" + ], "additionalProperties": false }, "promotions": { @@ -7600,7 +8529,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false }, "alreadyAdded": { @@ -7619,7 +8551,9 @@ "type": "string" } }, - "required": ["success"], + "required": [ + "success" + ], "additionalProperties": false }, "add": { @@ -7632,11 +8566,15 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false } }, - "required": ["list"], + "required": [ + "list" + ], "additionalProperties": false }, "list": { @@ -7646,7 +8584,9 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false } }, @@ -7703,7 +8643,10 @@ "type": "string" } }, - "required": ["confirmation", "successToast"], + "required": [ + "confirmation", + "successToast" + ], "additionalProperties": false }, "create": { @@ -7728,7 +8671,11 @@ "type": "string" } }, - "required": ["details", "products", "prices"], + "required": [ + "details", + "products", + "prices" + ], "additionalProperties": false }, "successToast": { @@ -7744,11 +8691,15 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false } }, - "required": ["list"], + "required": [ + "list" + ], "additionalProperties": false } }, @@ -7771,7 +8722,10 @@ "type": "string" } }, - "required": ["header", "successToast"], + "required": [ + "header", + "successToast" + ], "additionalProperties": false }, "configuration": { @@ -7793,11 +8747,18 @@ "type": "string" } }, - "required": ["header", "description", "successToast"], + "required": [ + "header", + "description", + "successToast" + ], "additionalProperties": false } }, - "required": ["header", "edit"], + "required": [ + "header", + "edit" + ], "additionalProperties": false }, "products": { @@ -7816,7 +8777,10 @@ "type": "string" } }, - "required": ["addProducts", "editPrices"], + "required": [ + "addProducts", + "editPrices" + ], "additionalProperties": false }, "delete": { @@ -7850,7 +8814,9 @@ "type": "string" } }, - "required": ["successToast"], + "required": [ + "successToast" + ], "additionalProperties": false }, "edit": { @@ -7860,11 +8826,19 @@ "type": "string" } }, - "required": ["successToast"], + "required": [ + "successToast" + ], "additionalProperties": false } }, - "required": ["header", "actions", "delete", "add", "edit"], + "required": [ + "header", + "actions", + "delete", + "add", + "edit" + ], "additionalProperties": false }, "fields": { @@ -7880,7 +8854,10 @@ "type": "string" } }, - "required": ["label", "header"], + "required": [ + "label", + "header" + ], "additionalProperties": false }, "status": { @@ -7905,11 +8882,19 @@ "type": "string" } }, - "required": ["active", "draft", "expired", "scheduled"], + "required": [ + "active", + "draft", + "expired", + "scheduled" + ], "additionalProperties": false } }, - "required": ["label", "options"], + "required": [ + "label", + "options" + ], "additionalProperties": false }, "type": { @@ -7934,7 +8919,10 @@ "type": "string" } }, - "required": ["label", "description"], + "required": [ + "label", + "description" + ], "additionalProperties": false }, "override": { @@ -7947,15 +8935,25 @@ "type": "string" } }, - "required": ["label", "description"], + "required": [ + "label", + "description" + ], "additionalProperties": false } }, - "required": ["sale", "override"], + "required": [ + "sale", + "override" + ], "additionalProperties": false } }, - "required": ["label", "hint", "options"], + "required": [ + "label", + "hint", + "options" + ], "additionalProperties": false }, "startsAt": { @@ -7968,7 +8966,10 @@ "type": "string" } }, - "required": ["label", "hint"], + "required": [ + "label", + "hint" + ], "additionalProperties": false }, "endsAt": { @@ -7981,7 +8982,10 @@ "type": "string" } }, - "required": ["label", "hint"], + "required": [ + "label", + "hint" + ], "additionalProperties": false }, "customerAvailability": { @@ -8055,7 +9059,10 @@ "type": "string" } }, - "required": ["languageLabel", "usageInsightsLabel"], + "required": [ + "languageLabel", + "usageInsightsLabel" + ], "additionalProperties": false }, "edit": { @@ -8089,7 +9096,9 @@ "type": "string" } }, - "required": ["edit"], + "required": [ + "edit" + ], "additionalProperties": false } }, @@ -8154,7 +9163,11 @@ "type": "string" } }, - "required": ["accepted", "pending", "expired"], + "required": [ + "accepted", + "pending", + "expired" + ], "additionalProperties": false }, "roles": { @@ -8170,7 +9183,11 @@ "type": "string" } }, - "required": ["admin", "developer", "member"], + "required": [ + "admin", + "developer", + "member" + ], "additionalProperties": false }, "deleteUserWarning": { @@ -8255,7 +9272,9 @@ "type": "string" } }, - "required": ["header"], + "required": [ + "header" + ], "additionalProperties": false }, "toast": { @@ -8377,7 +9396,9 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false }, "toast": { @@ -8396,7 +9417,12 @@ "type": "string" } }, - "required": ["delete", "edit", "create", "countries"], + "required": [ + "delete", + "edit", + "create", + "countries" + ], "additionalProperties": false }, "shippingOption": { @@ -8430,7 +9456,12 @@ "type": "string" } }, - "required": ["outbound", "outboundHint", "return", "returnHint"], + "required": [ + "outbound", + "outboundHint", + "return", + "returnHint" + ], "additionalProperties": false }, "priceType": { @@ -8446,7 +9477,11 @@ "type": "string" } }, - "required": ["label", "flatRate", "calculated"], + "required": [ + "label", + "flatRate", + "calculated" + ], "additionalProperties": false }, "availability": { @@ -8459,7 +9494,10 @@ "type": "string" } }, - "required": ["adminOnly", "adminOnlyHint"], + "required": [ + "adminOnly", + "adminOnlyHint" + ], "additionalProperties": false }, "taxInclusiveHint": { @@ -8475,7 +9513,10 @@ "type": "string" } }, - "required": ["label", "hint"], + "required": [ + "label", + "hint" + ], "additionalProperties": false } }, @@ -8537,7 +9578,9 @@ "type": "string" } }, - "required": ["taxCountriesHint"], + "required": [ + "taxCountriesHint" + ], "additionalProperties": false }, "settings": { @@ -8591,7 +9634,9 @@ "type": "string" } }, - "required": ["sectionTitle"], + "required": [ + "sectionTitle" + ], "additionalProperties": false }, "taxRate": { @@ -8741,7 +9786,11 @@ "type": "string" } }, - "required": ["create", "update", "removeChannel"], + "required": [ + "create", + "update", + "removeChannel" + ], "additionalProperties": false } }, @@ -8771,7 +9820,11 @@ "type": "string" } }, - "required": ["domain", "subtitle", "deleteWarning"], + "required": [ + "domain", + "subtitle", + "deleteWarning" + ], "additionalProperties": false }, "salesChannels": { @@ -8823,7 +9876,11 @@ "type": "string" } }, - "required": ["create", "update", "delete"], + "required": [ + "create", + "update", + "delete" + ], "additionalProperties": false }, "tooltip": { @@ -8833,7 +9890,9 @@ "type": "string" } }, - "required": ["cannotDeleteDefault"], + "required": [ + "cannotDeleteDefault" + ], "additionalProperties": false }, "products": { @@ -8846,7 +9905,9 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false }, "add": { @@ -8859,15 +9920,22 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false } }, - "required": ["list"], + "required": [ + "list" + ], "additionalProperties": false } }, - "required": ["list", "add"], + "required": [ + "list", + "add" + ], "additionalProperties": false } }, @@ -8902,7 +9970,10 @@ "type": "string" } }, - "required": ["publishable", "secret"], + "required": [ + "publishable", + "secret" + ], "additionalProperties": false }, "subtitle": { @@ -8915,7 +9986,10 @@ "type": "string" } }, - "required": ["publishable", "secret"], + "required": [ + "publishable", + "secret" + ], "additionalProperties": false }, "status": { @@ -8928,7 +10002,10 @@ "type": "string" } }, - "required": ["active", "revoked"], + "required": [ + "active", + "revoked" + ], "additionalProperties": false }, "type": { @@ -8941,7 +10018,10 @@ "type": "string" } }, - "required": ["publishable", "secret"], + "required": [ + "publishable", + "secret" + ], "additionalProperties": false }, "create": { @@ -9001,7 +10081,11 @@ "type": "string" } }, - "required": ["header", "description", "successToast"], + "required": [ + "header", + "description", + "successToast" + ], "additionalProperties": false }, "salesChannels": { @@ -9029,7 +10113,9 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false } }, @@ -9053,7 +10139,10 @@ "type": "string" } }, - "required": ["warning", "successToast"], + "required": [ + "warning", + "successToast" + ], "additionalProperties": false }, "revoke": { @@ -9066,7 +10155,10 @@ "type": "string" } }, - "required": ["warning", "successToast"], + "required": [ + "warning", + "successToast" + ], "additionalProperties": false }, "addSalesChannels": { @@ -9079,11 +10171,15 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false } }, - "required": ["list"], + "required": [ + "list" + ], "additionalProperties": false }, "removeSalesChannel": { @@ -9131,7 +10227,11 @@ "type": "string" } }, - "required": ["revoke", "copy", "copySuccessToast"], + "required": [ + "revoke", + "copy", + "copySuccessToast" + ], "additionalProperties": false }, "table": { @@ -9144,7 +10244,10 @@ "type": "string" } }, - "required": ["lastUsedAtHeader", "createdAtHeader"], + "required": [ + "lastUsedAtHeader", + "createdAtHeader" + ], "additionalProperties": false }, "fields": { @@ -9221,7 +10324,12 @@ "type": "string" } }, - "required": ["header", "subtitle", "hint", "successToast"], + "required": [ + "header", + "subtitle", + "hint", + "successToast" + ], "additionalProperties": false }, "edit": { @@ -9237,7 +10345,11 @@ "type": "string" } }, - "required": ["header", "subtitle", "successToast"], + "required": [ + "header", + "subtitle", + "successToast" + ], "additionalProperties": false }, "delete": { @@ -9250,7 +10362,10 @@ "type": "string" } }, - "required": ["confirmation", "successToast"], + "required": [ + "confirmation", + "successToast" + ], "additionalProperties": false }, "fields": { @@ -9269,7 +10384,11 @@ "type": "string" } }, - "required": ["label", "placeholder", "tooltip"], + "required": [ + "label", + "placeholder", + "tooltip" + ], "additionalProperties": false }, "label": { @@ -9282,7 +10401,10 @@ "type": "string" } }, - "required": ["label", "placeholder"], + "required": [ + "label", + "placeholder" + ], "additionalProperties": false }, "description": { @@ -9295,11 +10417,18 @@ "type": "string" } }, - "required": ["label", "placeholder"], + "required": [ + "label", + "placeholder" + ], "additionalProperties": false } }, - "required": ["value", "label", "description"], + "required": [ + "value", + "label", + "description" + ], "additionalProperties": false } }, @@ -9328,7 +10457,11 @@ "type": "string" } }, - "required": ["forgotPassword", "title", "hint"], + "required": [ + "forgotPassword", + "title", + "hint" + ], "additionalProperties": false }, "invite": { @@ -9380,7 +10513,9 @@ "type": "string" } }, - "required": ["accepted"], + "required": [ + "accepted" + ], "additionalProperties": false } }, @@ -9525,7 +10660,9 @@ "type": "string" } }, - "required": ["noRecordsMessage"], + "required": [ + "noRecordsMessage" + ], "additionalProperties": false }, "history": { @@ -9622,11 +10759,15 @@ "type": "string" } }, - "required": ["waitingToCompensate"], + "required": [ + "waitingToCompensate" + ], "additionalProperties": false } }, - "required": ["state"], + "required": [ + "state" + ], "additionalProperties": false }, "step": { @@ -9648,11 +10789,18 @@ "type": "string" } }, - "required": ["skipped", "skippedFailure", "dormant", "timeout"], + "required": [ + "skipped", + "skippedFailure", + "dormant", + "timeout" + ], "additionalProperties": false } }, - "required": ["state"], + "required": [ + "state" + ], "additionalProperties": false } }, @@ -9694,7 +10842,11 @@ "type": "string" } }, - "required": ["header", "hint", "successToast"], + "required": [ + "header", + "hint", + "successToast" + ], "additionalProperties": false }, "edit": { @@ -9707,7 +10859,10 @@ "type": "string" } }, - "required": ["header", "successToast"], + "required": [ + "header", + "successToast" + ], "additionalProperties": false }, "delete": { @@ -9720,7 +10875,10 @@ "type": "string" } }, - "required": ["confirmation", "successToast"], + "required": [ + "confirmation", + "successToast" + ], "additionalProperties": false }, "fields": { @@ -9730,11 +10888,20 @@ "type": "string" } }, - "required": ["value"], + "required": [ + "value" + ], "additionalProperties": false } }, - "required": ["domain", "subtitle", "create", "edit", "delete", "fields"], + "required": [ + "domain", + "subtitle", + "create", + "edit", + "delete", + "fields" + ], "additionalProperties": false }, "productTags": { @@ -9756,7 +10923,11 @@ "type": "string" } }, - "required": ["header", "subtitle", "successToast"], + "required": [ + "header", + "subtitle", + "successToast" + ], "additionalProperties": false }, "edit": { @@ -9772,7 +10943,11 @@ "type": "string" } }, - "required": ["header", "subtitle", "successToast"], + "required": [ + "header", + "subtitle", + "successToast" + ], "additionalProperties": false }, "delete": { @@ -9785,7 +10960,10 @@ "type": "string" } }, - "required": ["confirmation", "successToast"], + "required": [ + "confirmation", + "successToast" + ], "additionalProperties": false }, "fields": { @@ -9795,11 +10973,19 @@ "type": "string" } }, - "required": ["value"], + "required": [ + "value" + ], "additionalProperties": false } }, - "required": ["domain", "create", "edit", "delete", "fields"], + "required": [ + "domain", + "create", + "edit", + "delete", + "fields" + ], "additionalProperties": false }, "notifications": { @@ -9818,7 +11004,10 @@ "type": "string" } }, - "required": ["title", "description"], + "required": [ + "title", + "description" + ], "additionalProperties": false }, "accessibility": { @@ -9828,11 +11017,17 @@ "type": "string" } }, - "required": ["description"], + "required": [ + "description" + ], "additionalProperties": false } }, - "required": ["domain", "emptyState", "accessibility"], + "required": [ + "domain", + "emptyState", + "accessibility" + ], "additionalProperties": false }, "errors": { @@ -9845,7 +11040,10 @@ "type": "string" } }, - "required": ["serverError", "invalidCredentials"], + "required": [ + "serverError", + "invalidCredentials" + ], "additionalProperties": false }, "statuses": { @@ -9867,7 +11065,13 @@ "type": "string" } }, - "required": ["scheduled", "expired", "active", "enabled", "disabled"], + "required": [ + "scheduled", + "expired", + "active", + "enabled", + "disabled" + ], "additionalProperties": false }, "labels": { @@ -10662,4 +11866,4 @@ "dateTime" ], "additionalProperties": false -} +} \ No newline at end of file diff --git a/packages/admin/dashboard/src/i18n/translations/en.json b/packages/admin/dashboard/src/i18n/translations/en.json index d1889efe557c7..b77d2de08eed6 100644 --- a/packages/admin/dashboard/src/i18n/translations/en.json +++ b/packages/admin/dashboard/src/i18n/translations/en.json @@ -1523,7 +1523,8 @@ "hint": "Whether customers can use this option during checkout." }, "provider": "Fulfillment provider", - "profile": "Shipping profile" + "profile": "Shipping profile", + "fulfillmentOption": "Fulfillment option" } }, "serviceZones": { diff --git a/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-option-details-form.tsx b/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-option-details-form.tsx index dd95fb5f1c6f4..6fba5c25ef619 100644 --- a/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-option-details-form.tsx +++ b/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-option-details-form.tsx @@ -1,8 +1,9 @@ -import { Heading, Input, RadioGroup, Text } from "@medusajs/ui" +import { Heading, Input, RadioGroup, Select, Text } from "@medusajs/ui" import { UseFormReturn } from "react-hook-form" import { useTranslation } from "react-i18next" import { HttpTypes } from "@medusajs/types" + import { Divider } from "../../../../../components/common/divider" import { Form } from "../../../../../components/common/form" import { SwitchBox } from "../../../../../components/common/switch-box" @@ -18,6 +19,8 @@ type CreateShippingOptionDetailsFormProps = { isReturn?: boolean zone: HttpTypes.AdminServiceZone locationId: string + fulfillmentProviderOptions: HttpTypes.AdminFulfillmentProviderOption[] + selectedProviderId?: string } export const CreateShippingOptionDetailsForm = ({ @@ -25,6 +28,8 @@ export const CreateShippingOptionDetailsForm = ({ isReturn = false, zone, locationId, + fulfillmentProviderOptions, + selectedProviderId, }: CreateShippingOptionDetailsFormProps) => { const { t } = useTranslation() @@ -134,9 +139,6 @@ export const CreateShippingOptionDetailsForm = ({ ) }} /> - - -
+
+
{ + field.onChange(e) + form.setValue("fulfillment_option_id", "") + }} options={fulfillmentProviders.options} searchValue={fulfillmentProviders.searchValue} onSearchValueChange={ @@ -190,6 +198,45 @@ export const CreateShippingOptionDetailsForm = ({ ) }} /> + + { + return ( + + + {t( + "stockLocations.shippingOptions.fields.fulfillmentOption" + )} + + + + + + + ) + }} + />
diff --git a/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-options-form.tsx b/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-options-form.tsx index 1ed449e6c8533..838fae3740ead 100644 --- a/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-options-form.tsx +++ b/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-options-form.tsx @@ -1,7 +1,7 @@ import { zodResolver } from "@hookform/resolvers/zod" import { HttpTypes } from "@medusajs/types" import { Button, ProgressStatus, ProgressTabs, toast } from "@medusajs/ui" -import { useForm } from "react-hook-form" +import { useForm, useWatch } from "react-hook-form" import { useTranslation } from "react-i18next" import { useState } from "react" @@ -20,6 +20,7 @@ import { CreateShippingOptionDetailsSchema, CreateShippingOptionSchema, } from "./schema" +import { useFulfillmentProviderOptions } from "../../../../../hooks/api" enum Tab { DETAILS = "details", @@ -50,6 +51,7 @@ export function CreateShippingOptionsForm({ enabled_in_store: true, shipping_profile_id: "", provider_id: "", + fulfillment_option_id: "", region_prices: {}, currency_prices: {}, conditional_region_prices: {}, @@ -58,6 +60,16 @@ export function CreateShippingOptionsForm({ resolver: zodResolver(CreateShippingOptionSchema), }) + const selectedProviderId = useWatch({ + control: form.control, + name: "provider_id", + }) + + const { fulfillment_options: fulfillmentProviderOptions } = + useFulfillmentProviderOptions(selectedProviderId, { + enabled: !!selectedProviderId, + }) + const isCalculatedPriceType = form.watch("price_type") === ShippingOptionPriceType.Calculated @@ -123,6 +135,10 @@ export function CreateShippingOptionsForm({ ...conditionalRegionPrices, ] + const fulfillmentOptionData = fulfillmentProviderOptions?.find( + (fo) => fo.id === data.fulfillment_option_id + )! + await mutateAsync( { name: data.name, @@ -131,6 +147,7 @@ export function CreateShippingOptionsForm({ shipping_profile_id: data.shipping_profile_id, provider_id: data.provider_id, prices: allPrices, + data: fulfillmentOptionData as unknown as Record, rules: [ { // eslint-disable-next-line @@ -293,6 +310,8 @@ export function CreateShippingOptionsForm({ zone={zone} isReturn={isReturn} locationId={locationId} + fulfillmentProviderOptions={fulfillmentProviderOptions || []} + selectedProviderId={selectedProviderId} /> diff --git a/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/schema.ts b/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/schema.ts index 442e2920dc1ef..34f188a3cd796 100644 --- a/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/schema.ts +++ b/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/schema.ts @@ -12,6 +12,7 @@ export const CreateShippingOptionDetailsSchema = z.object({ enabled_in_store: z.boolean(), shipping_profile_id: z.string().min(1), provider_id: z.string().min(1), + fulfillment_option_id: z.string().min(1), }) export const ShippingOptionConditionalPriceSchema = z.object({ diff --git a/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-edit/components/edit-region-form/edit-shipping-option-form.tsx b/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-edit/components/edit-region-form/edit-shipping-option-form.tsx index 3d31e7efac25d..3df1f5be5564a 100644 --- a/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-edit/components/edit-region-form/edit-shipping-option-form.tsx +++ b/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-edit/components/edit-region-form/edit-shipping-option-form.tsx @@ -2,6 +2,7 @@ import { HttpTypes } from "@medusajs/types" import { Button, Input, RadioGroup, toast } from "@medusajs/ui" import { useForm } from "react-hook-form" import { useTranslation } from "react-i18next" +import { zodResolver } from "@hookform/resolvers/zod" import * as zod from "zod" import { Divider } from "../../../../../components/common/divider" @@ -14,7 +15,6 @@ import { useUpdateShippingOptions } from "../../../../../hooks/api/shipping-opti import { useComboboxData } from "../../../../../hooks/use-combobox-data" import { sdk } from "../../../../../lib/client" import { pick } from "../../../../../lib/common" -import { formatProvider } from "../../../../../lib/format-provider" import { isOptionEnabledInStore } from "../../../../../lib/shipping-options" import { ShippingOptionPriceType } from "../../../common/constants" @@ -28,7 +28,6 @@ const EditShippingOptionSchema = zod.object({ price_type: zod.nativeEnum(ShippingOptionPriceType), enabled_in_store: zod.boolean().optional(), shipping_profile_id: zod.string(), - provider_id: zod.string(), }) export const EditShippingOptionForm = ({ @@ -49,29 +48,14 @@ export const EditShippingOptionForm = ({ defaultValue: shippingOption.shipping_profile_id, }) - const fulfillmentProviders = useComboboxData({ - queryFn: (params) => - sdk.admin.fulfillmentProvider.list({ - ...params, - stock_location_id: locationId, - }), - queryKey: ["fulfillment_providers"], - getOptions: (data) => - data.fulfillment_providers.map((provider) => ({ - label: formatProvider(provider.id), - value: provider.id, - })), - defaultValue: shippingOption.provider_id, - }) - const form = useForm>({ defaultValues: { name: shippingOption.name, price_type: shippingOption.price_type as ShippingOptionPriceType, enabled_in_store: isOptionEnabledInStore(shippingOption), shipping_profile_id: shippingOption.shipping_profile_id, - provider_id: shippingOption.provider_id, }, + resolver: zodResolver(EditShippingOptionSchema), }) const { mutateAsync, isPending: isLoading } = useUpdateShippingOptions( @@ -101,7 +85,6 @@ export const EditShippingOptionForm = ({ name: values.name, price_type: values.price_type, shipping_profile_id: values.shipping_profile_id, - provider_id: values.provider_id, rules, }, { @@ -209,35 +192,6 @@ export const EditShippingOptionForm = ({ ) }} /> - { - return ( - - - {t("stockLocations.shippingOptions.fields.provider")} - - - - - - - ) - }} - /> diff --git a/packages/core/core-flows/src/cart/steps/validate-shipping-methods-data.ts b/packages/core/core-flows/src/cart/steps/validate-shipping-methods-data.ts index eec94e9f8730e..ca78740559802 100644 --- a/packages/core/core-flows/src/cart/steps/validate-shipping-methods-data.ts +++ b/packages/core/core-flows/src/cart/steps/validate-shipping-methods-data.ts @@ -1,16 +1,18 @@ import { Modules, promiseAll } from "@medusajs/framework/utils" -import { IFulfillmentModuleService } from "@medusajs/types" +import { + CartDTO, + IFulfillmentModuleService, + StockLocationDTO, +} from "@medusajs/types" import { createStep, StepResponse } from "@medusajs/workflows-sdk" -export interface ValidateShippingMethodsDataInput { - context: Record - options_to_validate: { - id: string - provider_id: string - option_data: Record - method_data: Record - }[] -} +export type ValidateShippingMethodsDataInput = { + id: string + provider_id: string + option_data: Record + method_data: Record + context: CartDTO & { from_location: StockLocationDTO; [k: string]: unknown } +}[] export const validateAndReturnShippingMethodsDataStepId = "validate-and-return-shipping-methods-data" @@ -20,9 +22,9 @@ export const validateAndReturnShippingMethodsDataStepId = export const validateAndReturnShippingMethodsDataStep = createStep( validateAndReturnShippingMethodsDataStepId, async (data: ValidateShippingMethodsDataInput, { container }) => { - const { options_to_validate = [] } = data + const optionsToValidate = data ?? [] - if (!options_to_validate.length) { + if (!optionsToValidate.length) { return new StepResponse(void 0) } @@ -31,12 +33,12 @@ export const validateAndReturnShippingMethodsDataStep = createStep( ) const validatedData = await promiseAll( - options_to_validate.map(async (option) => { + optionsToValidate.map(async (option) => { const validated = await fulfillmentModule.validateFulfillmentData( option.provider_id, option.option_data, option.method_data, - data.context + option.context ) return { diff --git a/packages/core/core-flows/src/cart/workflows/add-shipping-method-to-cart.ts b/packages/core/core-flows/src/cart/workflows/add-shipping-method-to-cart.ts index f49da2c7fafc2..65329c7a6549e 100644 --- a/packages/core/core-flows/src/cart/workflows/add-shipping-method-to-cart.ts +++ b/packages/core/core-flows/src/cart/workflows/add-shipping-method-to-cart.ts @@ -68,8 +68,8 @@ export const addShippingMethodToCartWorkflow = createWorkflow( validateCartShippingOptionsPriceStep({ shippingOptions }) const validateShippingMethodsDataInput = transform( - { input, shippingOptions }, - ({ input, shippingOptions }) => { + { input, shippingOptions, cart }, + ({ input, shippingOptions, cart }) => { return input.options.map((inputOption) => { const shippingOption = shippingOptions.find( (so) => so.id === inputOption.id @@ -80,15 +80,18 @@ export const addShippingMethodToCartWorkflow = createWorkflow( provider_id: shippingOption?.provider_id, option_data: shippingOption?.data ?? {}, method_data: inputOption.data ?? {}, + context: { + ...cart, + from_location: shippingOption?.stock_location ?? {}, + }, } }) } ) - const validatedMethodData = validateAndReturnShippingMethodsDataStep({ - options_to_validate: validateShippingMethodsDataInput, - context: {}, // TODO: Add cart, when we have a better idea about what's appropriate to pass - }) + const validatedMethodData = validateAndReturnShippingMethodsDataStep( + validateShippingMethodsDataInput + ) const shippingMethodInput = transform( { input, shippingOptions, validatedMethodData }, diff --git a/packages/core/core-flows/src/cart/workflows/list-shipping-options-for-cart.ts b/packages/core/core-flows/src/cart/workflows/list-shipping-options-for-cart.ts index f4a897dfb699d..ac41df245b479 100644 --- a/packages/core/core-flows/src/cart/workflows/list-shipping-options-for-cart.ts +++ b/packages/core/core-flows/src/cart/workflows/list-shipping-options-for-cart.ts @@ -1,4 +1,3 @@ -import { deepFlatMap } from "@medusajs/framework/utils" import { createWorkflow, transform, @@ -41,7 +40,12 @@ export const listShippingOptionsForCartWorkflow = createWorkflow( const scFulfillmentSetQuery = useQueryGraphStep({ entity: "sales_channels", filters: { id: cart.sales_channel_id }, - fields: ["stock_locations.fulfillment_sets.id"], + fields: [ + "stock_locations.fulfillment_sets.id", + "stock_locations.id", + "stock_locations.name", + "stock_locations.address.*", + ], }).config({ name: "sales_channels-fulfillment-query" }) const scFulfillmentSets = transform( @@ -49,22 +53,23 @@ export const listShippingOptionsForCartWorkflow = createWorkflow( ({ scFulfillmentSetQuery }) => scFulfillmentSetQuery.data[0] ) - const fulfillmentSetIds = transform( - { options: scFulfillmentSets }, - (data) => { + const { fulfillmentSetIds, fulfillmentSetLocationMap } = transform( + { scFulfillmentSets }, + ({ scFulfillmentSets }) => { const fulfillmentSetIds = new Set() + const fulfillmentSetLocationMap = {} - deepFlatMap( - data.options, - "stock_locations.fulfillment_sets", - ({ fulfillment_sets: fulfillmentSet }) => { - if (fulfillmentSet?.id) { - fulfillmentSetIds.add(fulfillmentSet.id) - } - } - ) + scFulfillmentSets.stock_locations.forEach((stockLocation) => { + stockLocation.fulfillment_sets.forEach((fulfillmentSet) => { + fulfillmentSetLocationMap[fulfillmentSet.id] = stockLocation + fulfillmentSetIds.add(fulfillmentSet.id) + }) + }) - return Array.from(fulfillmentSetIds) + return { + fulfillmentSetIds: Array.from(fulfillmentSetIds), + fulfillmentSetLocationMap, + } } ) @@ -103,6 +108,7 @@ export const listShippingOptionsForCartWorkflow = createWorkflow( "shipping_profile_id", "provider_id", "data", + "service_zone.fulfillment_set_id", "type.id", "type.label", @@ -124,15 +130,19 @@ export const listShippingOptionsForCartWorkflow = createWorkflow( }).config({ name: "shipping-options-query" }) const shippingOptionsWithPrice = transform( - { shippingOptions }, - ({ shippingOptions }) => + { shippingOptions, fulfillmentSetLocationMap }, + ({ shippingOptions, fulfillmentSetLocationMap }) => shippingOptions.map((shippingOption) => { const price = shippingOption.calculated_price + const fulfillmentSetId = + shippingOption.service_zone.fulfillment_set_id + const stockLocation = fulfillmentSetLocationMap[fulfillmentSetId] return { ...shippingOption, amount: price?.calculated_amount, is_tax_inclusive: !!price?.is_calculated_price_tax_inclusive, + stock_location: stockLocation, } }) ) diff --git a/packages/core/js-sdk/src/admin/fulfillment-provider.ts b/packages/core/js-sdk/src/admin/fulfillment-provider.ts index c18ff9214b83d..dce206e026b9e 100644 --- a/packages/core/js-sdk/src/admin/fulfillment-provider.ts +++ b/packages/core/js-sdk/src/admin/fulfillment-provider.ts @@ -18,25 +18,25 @@ export class FulfillmentProvider { * This method retrieves a paginated list of fulfillment providers. It sends a request to the * [List Fulfillment Providers](https://docs.medusajs.com/api/admin#fulfillment-providers_getfulfillmentproviders) * API route. - * + * * @param query - Filters and pagination configurations. * @param headers - Headers to pass in the request. * @returns The paginated list of providers. - * + * * @example * To retrieve the list of fulfillment providers: - * + * * ```ts * sdk.admin.fulfillmentProvider.list() * .then(({ fulfillment_providers, count, limit, offset }) => { * console.log(fulfillment_providers) * }) * ``` - * + * * To configure the pagination, pass the `limit` and `offset` query parameters. - * + * * For example, to retrieve only 10 items and skip 10 items: - * + * * ```ts * sdk.admin.fulfillmentProvider.list({ * limit: 10, @@ -46,10 +46,10 @@ export class FulfillmentProvider { * console.log(fulfillment_providers) * }) * ``` - * + * * Using the `fields` query parameter, you can specify the fields and relations to retrieve * in each fulfillment provider: - * + * * ```ts * sdk.admin.fulfillmentProvider.list({ * fields: "id" @@ -58,7 +58,7 @@ export class FulfillmentProvider { * console.log(fulfillment_providers) * }) * ``` - * + * * Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/store#select-fields-and-relations). */ async list( @@ -74,4 +74,23 @@ export class FulfillmentProvider { } ) } + + /** + * This method retrieves a list of fulfillment options for a given fulfillment provider. It sends a request to the + * [List Fulfillment Options](https://docs.medusajs.com/api/admin#fulfillment-providers_getfulfillmentprovideroptions) + * API route. + * + * @param id - The ID of the fulfillment provider. + * @param headers - Headers to pass in the request. + * @returns The list of fulfillment options. + */ + async listFulfillmentOptions(id: string, headers?: ClientHeaders) { + return await this.client.fetch( + `/admin/fulfillment-providers/${id}/options`, + { + method: "GET", + headers, + } + ) + } } diff --git a/packages/core/js-sdk/src/store/index.ts b/packages/core/js-sdk/src/store/index.ts index d475427e905b2..c08786f11de57 100644 --- a/packages/core/js-sdk/src/store/index.ts +++ b/packages/core/js-sdk/src/store/index.ts @@ -834,6 +834,23 @@ export class Store { } ) }, + + calculate: async ( + id: string, + body: HttpTypes.StoreCalculateShippingOptionPrice, + query?: HttpTypes.SelectParams, + headers?: ClientHeaders + ) => { + return await this.client.fetch( + `/store/shipping-options/${id}/calculate`, + { + method: "POST", + headers, + body, + query, + } + ) + }, } public payment = { diff --git a/packages/core/types/src/http/fulfillment-provider/admin/entities.ts b/packages/core/types/src/http/fulfillment-provider/admin/entities.ts index 1f56c491fcd63..fa0f14769e04c 100644 --- a/packages/core/types/src/http/fulfillment-provider/admin/entities.ts +++ b/packages/core/types/src/http/fulfillment-provider/admin/entities.ts @@ -1,3 +1,9 @@ -import { BaseFulfillmentProvider } from "../common" +import { + BaseFulfillmentProvider, + BaseFulfillmentProviderOption, +} from "../common" export interface AdminFulfillmentProvider extends BaseFulfillmentProvider {} + +export interface AdminFulfillmentProviderOption + extends BaseFulfillmentProviderOption {} diff --git a/packages/core/types/src/http/fulfillment-provider/admin/responses.ts b/packages/core/types/src/http/fulfillment-provider/admin/responses.ts index 6d88a194c304d..979700e0e053d 100644 --- a/packages/core/types/src/http/fulfillment-provider/admin/responses.ts +++ b/packages/core/types/src/http/fulfillment-provider/admin/responses.ts @@ -1,5 +1,8 @@ import { PaginatedResponse } from "../../common" -import { AdminFulfillmentProvider } from "./entities" +import { + AdminFulfillmentProvider, + AdminFulfillmentProviderOption, +} from "./entities" export interface AdminFulfillmentProviderListResponse extends PaginatedResponse<{ @@ -8,3 +11,8 @@ export interface AdminFulfillmentProviderListResponse */ fulfillment_providers: AdminFulfillmentProvider[] }> {} + +export interface AdminFulfillmentProviderOptionsListResponse + extends PaginatedResponse<{ + fulfillment_options: AdminFulfillmentProviderOption[] + }> {} diff --git a/packages/core/types/src/http/fulfillment-provider/common.ts b/packages/core/types/src/http/fulfillment-provider/common.ts index a89e79175c6ce..b54102ecc3301 100644 --- a/packages/core/types/src/http/fulfillment-provider/common.ts +++ b/packages/core/types/src/http/fulfillment-provider/common.ts @@ -8,3 +8,14 @@ export interface BaseFulfillmentProvider { */ is_enabled: boolean } + +export interface BaseFulfillmentProviderOption { + /** + * The fulfillment provider option's ID. + */ + id: string + /** + * Whether the fulfillment provider option can be used for returns. + */ + is_return: boolean +} diff --git a/packages/medusa/src/api/admin/fulfillment-providers/[id]/options/route.ts b/packages/medusa/src/api/admin/fulfillment-providers/[id]/options/route.ts new file mode 100644 index 0000000000000..5b5b1ec946fca --- /dev/null +++ b/packages/medusa/src/api/admin/fulfillment-providers/[id]/options/route.ts @@ -0,0 +1,27 @@ +import { + AdminFulfillmentProviderOption, + HttpTypes, +} from "@medusajs/framework/types" +import { Modules } from "@medusajs/framework/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" + +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const fulfillmentProviderService = req.scope.resolve(Modules.FULFILLMENT) + + const fulfillmentOptions = + await fulfillmentProviderService.retrieveFulfillmentOptions(req.params.id) + + res.json({ + fulfillment_options: + fulfillmentOptions as unknown as AdminFulfillmentProviderOption[], + count: fulfillmentOptions.length, + limit: fulfillmentOptions.length, + offset: 0, + }) +} diff --git a/packages/medusa/src/api/admin/fulfillment-providers/middlewares.ts b/packages/medusa/src/api/admin/fulfillment-providers/middlewares.ts index 82b3a22d47db1..ea5875f280997 100644 --- a/packages/medusa/src/api/admin/fulfillment-providers/middlewares.ts +++ b/packages/medusa/src/api/admin/fulfillment-providers/middlewares.ts @@ -20,4 +20,9 @@ export const adminFulfillmentProvidersRoutesMiddlewares: MiddlewareRoute[] = [ }), ], }, + { + method: ["GET"], + matcher: "/admin/fulfillment-providers/:id/options", + middlewares: [], + }, ] diff --git a/packages/modules/fulfillment/src/services/fulfillment-module-service.ts b/packages/modules/fulfillment/src/services/fulfillment-module-service.ts index 741db5405ea64..63e6513e0d134 100644 --- a/packages/modules/fulfillment/src/services/fulfillment-module-service.ts +++ b/packages/modules/fulfillment/src/services/fulfillment-module-service.ts @@ -5,6 +5,7 @@ import { FilterableFulfillmentSetProps, FindConfig, FulfillmentDTO, + FulfillmentOption, FulfillmentTypes, IFulfillmentModuleService, InternalModuleDeclaration, @@ -1946,7 +1947,7 @@ export default class FulfillmentModuleService async retrieveFulfillmentOptions( providerId: string - ): Promise[]> { + ): Promise { return await this.fulfillmentProviderService_.getFulfillmentOptions( providerId ) diff --git a/packages/modules/fulfillment/src/services/fulfillment-provider.ts b/packages/modules/fulfillment/src/services/fulfillment-provider.ts index ea205a95f75e1..f61a6625b473f 100644 --- a/packages/modules/fulfillment/src/services/fulfillment-provider.ts +++ b/packages/modules/fulfillment/src/services/fulfillment-provider.ts @@ -2,6 +2,7 @@ import { CalculateShippingOptionPriceDTO, Constructor, DAL, + FulfillmentOption, FulfillmentTypes, IFulfillmentProvider, Logger, @@ -81,7 +82,7 @@ export default class FulfillmentProviderService extends ModulesSdkUtils.MedusaIn async getFulfillmentOptions( providerId: string - ): Promise[]> { + ): Promise { const provider = this.retrieveProviderRegistration(providerId) return await provider.getFulfillmentOptions() } From c9b8db04c1b35f1cf129bb9ad74789fbc2881815 Mon Sep 17 00:00:00 2001 From: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> Date: Wed, 18 Dec 2024 12:53:57 +0100 Subject: [PATCH 3/5] feat: Custom line items (#10408) * feat: Custom line items * fix tests * fix migration * Allow custom items in update line item workflow * throw if line item doesn't have a price * minor things * wip * fix flows * fix test * add default * add to type --- .changeset/gorgeous-tools-enjoy.md | 9 + .../http/__tests__/order/admin/order.spec.ts | 2 +- .../cart/store/cart.workflows.spec.ts | 1138 ++++++++++++++--- .../__tests__/order/draft-order.spec.ts | 8 +- .../cart/steps/validate-line-item-prices.ts | 36 + .../src/cart/steps/validate-variant-prices.ts | 4 + .../src/cart/utils/prepare-line-item-data.ts | 149 ++- .../src/cart/workflows/add-to-cart.ts | 68 +- .../src/cart/workflows/complete-cart.ts | 16 +- .../src/cart/workflows/create-carts.ts | 76 +- .../src/cart/workflows/refresh-cart-items.ts | 53 +- .../src/cart/workflows/update-cart.ts | 45 +- .../workflows/update-line-item-in-cart.ts | 63 +- .../src/common/steps/use-remote-query.ts | 4 +- .../utils/prepare-custom-line-item-data.ts | 68 - .../src/order/workflows/add-line-items.ts | 86 +- .../src/order/workflows/create-order.ts | 78 +- packages/core/types/src/cart/common.ts | 5 + packages/core/types/src/cart/mutations.ts | 5 + packages/core/types/src/cart/workflows.ts | 2 +- .../core/utils/src/common/deep-flat-map.ts | 2 +- .../src/api/admin/draft-orders/validators.ts | 36 +- .../services/cart-module/index.spec.ts | 2 + .../src/migrations/.snapshot-medusa-cart.json | 10 + .../src/migrations/Migration20241218091938.ts | 13 + packages/modules/cart/src/models/line-item.ts | 3 +- 26 files changed, 1470 insertions(+), 511 deletions(-) create mode 100644 .changeset/gorgeous-tools-enjoy.md create mode 100644 packages/core/core-flows/src/cart/steps/validate-line-item-prices.ts delete mode 100644 packages/core/core-flows/src/order/utils/prepare-custom-line-item-data.ts create mode 100644 packages/modules/cart/src/migrations/Migration20241218091938.ts diff --git a/.changeset/gorgeous-tools-enjoy.md b/.changeset/gorgeous-tools-enjoy.md new file mode 100644 index 0000000000000..f3c99bd03f047 --- /dev/null +++ b/.changeset/gorgeous-tools-enjoy.md @@ -0,0 +1,9 @@ +--- +"@medusajs/core-flows": patch +"@medusajs/cart": patch +"@medusajs/types": patch +"@medusajs/utils": patch +"@medusajs/medusa": patch +--- + +chore: Support custom line items diff --git a/integration-tests/http/__tests__/order/admin/order.spec.ts b/integration-tests/http/__tests__/order/admin/order.spec.ts index 1bb66dd8f5a42..092b033f228bb 100644 --- a/integration-tests/http/__tests__/order/admin/order.spec.ts +++ b/integration-tests/http/__tests__/order/admin/order.spec.ts @@ -1,5 +1,5 @@ -import { ModuleRegistrationName } from "@medusajs/utils" import { medusaIntegrationTestRunner } from "@medusajs/test-utils" +import { ModuleRegistrationName } from "@medusajs/utils" import { adminHeaders, createAdminUser, diff --git a/integration-tests/modules/__tests__/cart/store/cart.workflows.spec.ts b/integration-tests/modules/__tests__/cart/store/cart.workflows.spec.ts index c60fef4d6d7c0..9500c804f73af 100644 --- a/integration-tests/modules/__tests__/cart/store/cart.workflows.spec.ts +++ b/integration-tests/modules/__tests__/cart/store/cart.workflows.spec.ts @@ -1,13 +1,16 @@ import { addShippingMethodToCartWorkflow, addToCartWorkflow, + completeCartWorkflow, createCartWorkflow, createPaymentCollectionForCartWorkflow, + createPaymentSessionsWorkflow, deleteLineItemsStepId, deleteLineItemsWorkflow, findOrCreateCustomerStepId, listShippingOptionsForCartWorkflow, refreshPaymentCollectionForCartWorkflow, + updateCartWorkflow, updateLineItemInCartWorkflow, updateLineItemsStepId, updatePaymentCollectionStepId, @@ -61,7 +64,8 @@ medusaIntegrationTestRunner({ let stockLocationModule: IStockLocationService let inventoryModule: IInventoryService let fulfillmentModule: IFulfillmentModuleService - let remoteLink, remoteQuery, storeHeaders + let remoteLink, remoteQuery, query + let storeHeaders let salesChannel let defaultRegion let customer, storeHeadersWithCustomer @@ -82,6 +86,7 @@ medusaIntegrationTestRunner({ remoteQuery = appContainer.resolve( ContainerRegistrationKeys.REMOTE_QUERY ) + query = appContainer.resolve(ContainerRegistrationKeys.QUERY) }) beforeEach(async () => { @@ -695,6 +700,336 @@ medusaIntegrationTestRunner({ }) }) + describe("CompleteCartWorkflow", () => { + it("should complete cart with custom item", async () => { + const salesChannel = await scModuleService.createSalesChannels({ + name: "Webshop", + }) + + const location = await stockLocationModule.createStockLocations({ + name: "Warehouse", + }) + + const region = await regionModuleService.createRegions({ + name: "US", + currency_code: "usd", + }) + + let cart = await cartModuleService.createCarts({ + currency_code: "usd", + sales_channel_id: salesChannel.id, + region_id: region.id, + }) + + await remoteLink.create([ + { + [Modules.SALES_CHANNEL]: { + sales_channel_id: salesChannel.id, + }, + [Modules.STOCK_LOCATION]: { + stock_location_id: location.id, + }, + }, + ]) + + cart = await cartModuleService.retrieveCart(cart.id, { + select: ["id", "region_id", "currency_code", "sales_channel_id"], + }) + + await addToCartWorkflow(appContainer).run({ + input: { + items: [ + { + title: "Test item", + subtitle: "Test subtitle", + thumbnail: "some-url", + requires_shipping: true, + is_discountable: false, + is_tax_inclusive: false, + unit_price: 3000, + metadata: { + foo: "bar", + }, + quantity: 1, + }, + ], + cart_id: cart.id, + }, + }) + + cart = await cartModuleService.retrieveCart(cart.id, { + relations: ["items"], + }) + + await createPaymentCollectionForCartWorkflow(appContainer).run({ + input: { + cart_id: cart.id, + }, + }) + + const [paymentCollection] = + await paymentModule.listPaymentCollections({}) + + await createPaymentSessionsWorkflow(appContainer).run({ + input: { + payment_collection_id: paymentCollection.id, + provider_id: "pp_system_default", + context: {}, + data: {}, + }, + }) + + await completeCartWorkflow(appContainer).run({ + input: { + id: cart.id, + }, + }) + + const { data } = await query.graph({ + entity: "cart", + filters: { + id: cart.id, + }, + fields: ["id", "currency_code", "completed_at", "items.*"], + }) + + expect(data[0]).toEqual( + expect.objectContaining({ + id: cart.id, + currency_code: "usd", + completed_at: expect.any(Date), + items: [ + { + cart_id: cart.id, + compare_at_unit_price: null, + created_at: expect.any(Date), + deleted_at: null, + id: expect.any(String), + is_discountable: false, + is_tax_inclusive: false, + is_custom_price: true, + metadata: { + foo: "bar", + }, + product_collection: null, + product_description: null, + product_handle: null, + product_id: null, + product_subtitle: null, + product_title: null, + product_type: null, + product_type_id: null, + quantity: 1, + raw_compare_at_unit_price: null, + raw_unit_price: { + precision: 20, + value: "3000", + }, + requires_shipping: true, + subtitle: "Test subtitle", + thumbnail: "some-url", + title: "Test item", + unit_price: 3000, + updated_at: expect.any(Date), + variant_barcode: null, + variant_id: null, + variant_option_values: null, + variant_sku: null, + variant_title: null, + }, + ], + }) + ) + }) + }) + + describe("UpdateCartWorkflow", () => { + it("should remove item with custom price when region is updated", async () => { + const salesChannel = await scModuleService.createSalesChannels({ + name: "Webshop", + }) + + const location = await stockLocationModule.createStockLocations({ + name: "Warehouse", + }) + + const regions = await regionModuleService.createRegions([ + { + name: "US", + currency_code: "usd", + }, + { + name: "EU", + currency_code: "eur", + }, + ]) + + let cart = await cartModuleService.createCarts({ + currency_code: "usd", + sales_channel_id: salesChannel.id, + region_id: regions.find((r) => r.currency_code === "usd")!.id, + }) + + const [product] = await productModule.createProducts([ + { + title: "Test product", + variants: [ + { + title: "Test variant", + manage_inventory: false, + }, + ], + }, + ]) + + const priceSet = await pricingModule.createPriceSets({ + prices: [ + { + amount: 3000, + currency_code: "usd", + }, + { + amount: 2000, + currency_code: "eur", + }, + ], + }) + + await pricingModule.createPricePreferences([ + { + attribute: "currency_code", + value: "usd", + is_tax_inclusive: true, + }, + ]) + + await remoteLink.create([ + { + [Modules.PRODUCT]: { + variant_id: product.variants[0].id, + }, + [Modules.PRICING]: { + price_set_id: priceSet.id, + }, + }, + { + [Modules.SALES_CHANNEL]: { + sales_channel_id: salesChannel.id, + }, + [Modules.STOCK_LOCATION]: { + stock_location_id: location.id, + }, + }, + ]) + + cart = await cartModuleService.retrieveCart(cart.id, { + select: ["id", "region_id", "currency_code", "sales_channel_id"], + }) + + await addToCartWorkflow(appContainer).run({ + input: { + items: [ + { + variant_id: product.variants[0].id, + quantity: 1, + }, + { + title: "Test item", + subtitle: "Test subtitle", + thumbnail: "some-url", + requires_shipping: true, + is_discountable: false, + is_tax_inclusive: false, + unit_price: 1500, + metadata: { + foo: "bar", + }, + quantity: 1, + }, + ], + cart_id: cart.id, + }, + }) + + cart = await cartModuleService.retrieveCart(cart.id, { + relations: ["items"], + }) + + expect(cart).toEqual( + expect.objectContaining({ + id: cart.id, + currency_code: "usd", + items: expect.arrayContaining([ + expect.objectContaining({ + // Regular line item + id: expect.any(String), + is_discountable: true, + is_tax_inclusive: true, + is_custom_price: false, + quantity: 1, + requires_shipping: true, + subtitle: "Test product", + title: "Test variant", + unit_price: 3000, + updated_at: expect.any(Date), + }), + expect.objectContaining({ + // Custom line item + id: expect.any(String), + is_discountable: false, + is_tax_inclusive: false, + is_custom_price: true, + quantity: 1, + metadata: { + foo: "bar", + }, + requires_shipping: true, + subtitle: "Test subtitle", + thumbnail: "some-url", + title: "Test item", + unit_price: 1500, + updated_at: expect.any(Date), + }), + ]), + }) + ) + + await updateCartWorkflow(appContainer).run({ + input: { + id: cart.id, + region_id: regions.find((r) => r.currency_code === "eur")!.id, + }, + }) + + cart = await cartModuleService.retrieveCart(cart.id, { + relations: ["items"], + }) + + expect(cart).toEqual( + expect.objectContaining({ + id: cart.id, + currency_code: "eur", + items: expect.arrayContaining([ + expect.objectContaining({ + // Regular line item + id: expect.any(String), + is_discountable: true, + is_tax_inclusive: false, + is_custom_price: false, + quantity: 1, + requires_shipping: true, + subtitle: "Test product", + title: "Test variant", + unit_price: 2000, + updated_at: expect.any(Date), + }), + ]), + }) + ) + expect(cart.items?.length).toEqual(1) + }) + }) + describe("AddToCartWorkflow", () => { it("should add item to cart", async () => { const salesChannel = await scModuleService.createSalesChannels({ @@ -780,39 +1115,492 @@ medusaIntegrationTestRunner({ select: ["id", "region_id", "currency_code", "sales_channel_id"], }) - await addToCartWorkflow(appContainer).run({ + await addToCartWorkflow(appContainer).run({ + input: { + items: [ + { + variant_id: product.variants[0].id, + quantity: 1, + }, + ], + cart_id: cart.id, + }, + }) + + cart = await cartModuleService.retrieveCart(cart.id, { + relations: ["items"], + }) + + expect(cart).toEqual( + expect.objectContaining({ + id: cart.id, + currency_code: "usd", + items: expect.arrayContaining([ + expect.objectContaining({ + unit_price: 3000, + is_tax_inclusive: true, + quantity: 1, + title: "Test variant", + }), + ]), + }) + ) + }) + + it("should add custom item to cart", async () => { + const salesChannel = await scModuleService.createSalesChannels({ + name: "Webshop", + }) + + const location = await stockLocationModule.createStockLocations({ + name: "Warehouse", + }) + + let cart = await cartModuleService.createCarts({ + currency_code: "usd", + sales_channel_id: salesChannel.id, + }) + + await remoteLink.create([ + { + [Modules.SALES_CHANNEL]: { + sales_channel_id: salesChannel.id, + }, + [Modules.STOCK_LOCATION]: { + stock_location_id: location.id, + }, + }, + ]) + + cart = await cartModuleService.retrieveCart(cart.id, { + select: ["id", "region_id", "currency_code", "sales_channel_id"], + }) + + await addToCartWorkflow(appContainer).run({ + input: { + items: [ + { + title: "Test item", + subtitle: "Test subtitle", + thumbnail: "some-url", + requires_shipping: true, + is_discountable: false, + is_tax_inclusive: false, + unit_price: 3000, + metadata: { + foo: "bar", + }, + quantity: 1, + }, + ], + cart_id: cart.id, + }, + }) + + cart = await cartModuleService.retrieveCart(cart.id, { + relations: ["items"], + }) + + expect(cart).toEqual( + expect.objectContaining({ + id: cart.id, + currency_code: "usd", + items: [ + { + cart_id: expect.any(String), + compare_at_unit_price: null, + created_at: expect.any(Date), + deleted_at: null, + id: expect.any(String), + is_discountable: false, + is_tax_inclusive: false, + is_custom_price: true, + metadata: { + foo: "bar", + }, + product_collection: null, + product_description: null, + product_handle: null, + product_id: null, + product_subtitle: null, + product_title: null, + product_type: null, + product_type_id: null, + quantity: 1, + raw_compare_at_unit_price: null, + raw_unit_price: { + precision: 20, + value: "3000", + }, + requires_shipping: true, + subtitle: "Test subtitle", + thumbnail: "some-url", + title: "Test item", + unit_price: 3000, + updated_at: expect.any(Date), + variant_barcode: null, + variant_id: null, + variant_option_values: null, + variant_sku: null, + variant_title: null, + }, + ], + }) + ) + }) + + it("should add item to cart with price list", async () => { + const salesChannel = await scModuleService.createSalesChannels({ + name: "Webshop", + }) + + const customer = await customerModule.createCustomers({ + first_name: "Test", + last_name: "Test", + }) + + const customer_group = await customerModule.createCustomerGroups({ + name: "Test Group", + }) + + await customerModule.addCustomerToGroup({ + customer_id: customer.id, + customer_group_id: customer_group.id, + }) + + const location = await stockLocationModule.createStockLocations({ + name: "Warehouse", + }) + + let cart = await cartModuleService.createCarts({ + currency_code: "usd", + sales_channel_id: salesChannel.id, + customer_id: customer.id, + }) + + const [product] = await productModule.createProducts([ + { + title: "Test product", + variants: [ + { + title: "Test variant", + }, + ], + }, + ]) + + const inventoryItem = await inventoryModule.createInventoryItems({ + sku: "inv-1234", + }) + + await inventoryModule.createInventoryLevels([ + { + inventory_item_id: inventoryItem.id, + location_id: location.id, + stocked_quantity: 2, + }, + ]) + + const priceSet = await pricingModule.createPriceSets({ + prices: [ + { + amount: 3000, + currency_code: "usd", + }, + ], + }) + + await pricingModule.createPricePreferences({ + attribute: "currency_code", + value: "usd", + is_tax_inclusive: true, + }) + + await pricingModule.createPriceLists([ + { + title: "test price list", + description: "test", + status: PriceListStatus.ACTIVE, + type: PriceListType.OVERRIDE, + prices: [ + { + amount: 1500, + currency_code: "usd", + price_set_id: priceSet.id, + }, + ], + rules: { + "customer.groups.id": [customer_group.id], + }, + }, + ]) + + await remoteLink.create([ + { + [Modules.PRODUCT]: { + variant_id: product.variants[0].id, + }, + [Modules.PRICING]: { + price_set_id: priceSet.id, + }, + }, + { + [Modules.SALES_CHANNEL]: { + sales_channel_id: salesChannel.id, + }, + [Modules.STOCK_LOCATION]: { + stock_location_id: location.id, + }, + }, + { + [Modules.PRODUCT]: { + variant_id: product.variants[0].id, + }, + [Modules.INVENTORY]: { + inventory_item_id: inventoryItem.id, + }, + }, + ]) + + cart = await cartModuleService.retrieveCart(cart.id, { + select: ["id", "region_id", "currency_code", "sales_channel_id"], + }) + + await addToCartWorkflow(appContainer).run({ + input: { + items: [ + { + variant_id: product.variants[0].id, + quantity: 1, + }, + ], + cart_id: cart.id, + }, + }) + + cart = await cartModuleService.retrieveCart(cart.id, { + relations: ["items"], + }) + + expect(cart).toEqual( + expect.objectContaining({ + id: cart.id, + currency_code: "usd", + items: expect.arrayContaining([ + expect.objectContaining({ + unit_price: 1500, + is_tax_inclusive: true, + quantity: 1, + title: "Test variant", + }), + ]), + }) + ) + }) + + it("should throw if no price sets for variant exist", async () => { + const salesChannel = await scModuleService.createSalesChannels({ + name: "Webshop", + }) + + const location = await stockLocationModule.createStockLocations({ + name: "Warehouse", + }) + + let cart = await cartModuleService.createCarts({ + currency_code: "usd", + sales_channel_id: salesChannel.id, + }) + + const [product] = await productModule.createProducts([ + { + title: "Test product", + variants: [ + { + title: "Test variant", + }, + ], + }, + ]) + + const inventoryItem = await inventoryModule.createInventoryItems({ + sku: "inv-1234", + }) + + await inventoryModule.createInventoryLevels([ + { + inventory_item_id: inventoryItem.id, + location_id: location.id, + stocked_quantity: 2, + reserved_quantity: 0, + }, + ]) + + await remoteLink.create([ + { + [Modules.SALES_CHANNEL]: { + sales_channel_id: salesChannel.id, + }, + [Modules.STOCK_LOCATION]: { + stock_location_id: location.id, + }, + }, + { + [Modules.PRODUCT]: { + variant_id: product.variants[0].id, + }, + [Modules.INVENTORY]: { + inventory_item_id: inventoryItem.id, + }, + }, + ]) + + const { errors } = await addToCartWorkflow(appContainer).run({ + input: { + items: [ + { + variant_id: product.variants[0].id, + quantity: 1, + }, + ], + cart_id: cart.id, + }, + throwOnError: false, + }) + + expect(errors).toEqual([ + { + action: "validate-variant-prices", + handlerType: "invoke", + error: expect.objectContaining({ + message: expect.stringContaining( + `Variants with IDs ${product.variants[0].id} do not have a price` + ), + }), + }, + ]) + }) + }) + + describe("updateLineItemInCartWorkflow", () => { + it("should update item in cart", async () => { + const salesChannel = await scModuleService.createSalesChannels({ + name: "Webshop", + }) + + const location = await stockLocationModule.createStockLocations({ + name: "Warehouse", + }) + + const [product] = await productModule.createProducts([ + { + title: "Test product", + variants: [ + { + title: "Test variant", + }, + ], + }, + ]) + + const inventoryItem = await inventoryModule.createInventoryItems({ + sku: "inv-1234", + }) + + await inventoryModule.createInventoryLevels([ + { + inventory_item_id: inventoryItem.id, + location_id: location.id, + stocked_quantity: 2, + reserved_quantity: 0, + }, + ]) + + const priceSet = await pricingModule.createPriceSets({ + prices: [ + { + amount: 3000, + currency_code: "usd", + }, + ], + }) + + await remoteLink.create([ + { + [Modules.PRODUCT]: { + variant_id: product.variants[0].id, + }, + [Modules.PRICING]: { + price_set_id: priceSet.id, + }, + }, + { + [Modules.SALES_CHANNEL]: { + sales_channel_id: salesChannel.id, + }, + [Modules.STOCK_LOCATION]: { + stock_location_id: location.id, + }, + }, + { + [Modules.PRODUCT]: { + variant_id: product.variants[0].id, + }, + [Modules.INVENTORY]: { + inventory_item_id: inventoryItem.id, + }, + }, + ]) + + let cart = await cartModuleService.createCarts({ + currency_code: "usd", + sales_channel_id: salesChannel.id, + items: [ + { + variant_id: product.variants[0].id, + quantity: 1, + unit_price: 5000, + is_custom_price: true, + title: "Test variant", + }, + ], + }) + + cart = await cartModuleService.retrieveCart(cart.id, { + select: ["id", "region_id", "currency_code"], + relations: ["items", "items.variant_id", "items.metadata"], + }) + + const item = cart.items?.[0]! + + await updateLineItemInCartWorkflow(appContainer).run({ input: { - items: [ - { - variant_id: product.variants[0].id, - quantity: 1, + item_id: item.id, + update: { + metadata: { + foo: "bar", }, - ], + quantity: 2, + }, cart_id: cart.id, }, + throwOnError: false, }) - cart = await cartModuleService.retrieveCart(cart.id, { - relations: ["items"], - }) + const updatedItem = await cartModuleService.retrieveLineItem(item.id) - expect(cart).toEqual( + expect(updatedItem).toEqual( expect.objectContaining({ - id: cart.id, - currency_code: "usd", - items: expect.arrayContaining([ - expect.objectContaining({ - unit_price: 3000, - is_tax_inclusive: true, - quantity: 1, - title: "Test variant", - }), - ]), + id: item.id, + unit_price: 5000, + is_custom_price: true, + quantity: 2, + title: "Test variant", }) ) }) - it("should throw if no price sets for variant exist", async () => { + it("should update custom item in cart", async () => { const salesChannel = await scModuleService.createSalesChannels({ name: "Webshop", }) @@ -824,32 +1612,25 @@ medusaIntegrationTestRunner({ let cart = await cartModuleService.createCarts({ currency_code: "usd", sales_channel_id: salesChannel.id, - }) - - const [product] = await productModule.createProducts([ - { - title: "Test product", - variants: [ - { - title: "Test variant", + items: [ + { + title: "Test item", + subtitle: "Test subtitle", + thumbnail: "some-url", + requires_shipping: true, + is_discountable: false, + is_tax_inclusive: false, + is_custom_price: true, + variant_id: "some_random_id", + unit_price: 3000, + metadata: { + foo: "bar", }, - ], - }, - ]) - - const inventoryItem = await inventoryModule.createInventoryItems({ - sku: "inv-1234", + quantity: 1, + }, + ], }) - await inventoryModule.createInventoryLevels([ - { - inventory_item_id: inventoryItem.id, - location_id: location.id, - stocked_quantity: 2, - reserved_quantity: 0, - }, - ]) - await remoteLink.create([ { [Modules.SALES_CHANNEL]: { @@ -859,100 +1640,146 @@ medusaIntegrationTestRunner({ stock_location_id: location.id, }, }, - { - [Modules.PRODUCT]: { - variant_id: product.variants[0].id, - }, - [Modules.INVENTORY]: { - inventory_item_id: inventoryItem.id, - }, - }, ]) - const { errors } = await addToCartWorkflow(appContainer).run({ + cart = await cartModuleService.retrieveCart(cart.id, { + select: ["id", "region_id", "currency_code", "sales_channel_id"], + relations: ["items"], + }) + + await updateLineItemInCartWorkflow(appContainer).run({ input: { - items: [ - { - variant_id: product.variants[0].id, - quantity: 1, - }, - ], cart_id: cart.id, + item_id: cart.items?.[0]!.id!, + update: { + quantity: 2, + title: "Some other title", + }, }, - throwOnError: false, }) - expect(errors).toEqual([ - { - action: "validate-variant-prices", - handlerType: "invoke", - error: expect.objectContaining({ - message: expect.stringContaining( - `Variants with IDs ${product.variants[0].id} do not have a price` - ), - }), - }, - ]) - }) - - it("should throw if variant does not exist", async () => { - const cart = await cartModuleService.createCarts({ - currency_code: "usd", + cart = await cartModuleService.retrieveCart(cart.id, { + relations: ["items"], }) - const { errors } = await addToCartWorkflow(appContainer).run({ - input: { + expect(cart).toEqual( + expect.objectContaining({ + id: cart.id, + currency_code: "usd", items: [ { - variant_id: "prva_foo", - quantity: 1, + cart_id: expect.any(String), + compare_at_unit_price: null, + created_at: expect.any(Date), + deleted_at: null, + id: expect.any(String), + is_discountable: false, + is_tax_inclusive: false, + is_custom_price: true, + metadata: { + foo: "bar", + }, + product_collection: null, + product_description: null, + product_handle: null, + product_id: null, + product_subtitle: null, + product_title: null, + product_type: null, + product_type_id: null, + quantity: 2, + raw_compare_at_unit_price: null, + raw_unit_price: { + precision: 20, + value: "3000", + }, + requires_shipping: true, + subtitle: "Test subtitle", + thumbnail: "some-url", + title: "Some other title", + unit_price: 3000, + updated_at: expect.any(Date), + variant_barcode: null, + variant_id: "some_random_id", + variant_option_values: null, + variant_sku: null, + variant_title: null, }, ], + }) + ) + + await updateLineItemInCartWorkflow(appContainer).run({ + input: { cart_id: cart.id, + item_id: cart.items?.[0]!.id!, + update: { + quantity: 4, + }, }, - throwOnError: false, }) - expect(errors).toEqual([ - { - action: "use-remote-query", - handlerType: "invoke", - error: expect.objectContaining({ - message: `ProductVariant id not found: prva_foo`, - }), - }, - ]) + cart = await cartModuleService.retrieveCart(cart.id, { + relations: ["items"], + }) + + expect(cart).toEqual( + expect.objectContaining({ + id: cart.id, + currency_code: "usd", + items: [ + { + cart_id: expect.any(String), + compare_at_unit_price: null, + created_at: expect.any(Date), + deleted_at: null, + id: expect.any(String), + is_discountable: false, + is_tax_inclusive: false, + is_custom_price: true, + metadata: { + foo: "bar", + }, + product_collection: null, + product_description: null, + product_handle: null, + product_id: null, + product_subtitle: null, + product_title: null, + product_type: null, + product_type_id: null, + quantity: 4, + raw_compare_at_unit_price: null, + raw_unit_price: { + precision: 20, + value: "3000", + }, + requires_shipping: true, + subtitle: "Test subtitle", + thumbnail: "some-url", + title: "Some other title", + unit_price: 3000, + updated_at: expect.any(Date), + variant_barcode: null, + variant_id: "some_random_id", + variant_option_values: null, + variant_sku: null, + variant_title: null, + }, + ], + }) + ) }) - it("should add item to cart with price list", async () => { + it("should update unit price of regular item in cart", async () => { const salesChannel = await scModuleService.createSalesChannels({ name: "Webshop", }) - const customer = await customerModule.createCustomers({ - first_name: "Test", - last_name: "Test", - }) - - const customer_group = await customerModule.createCustomerGroups({ - name: "Test Group", - }) - - await customerModule.addCustomerToGroup({ - customer_id: customer.id, - customer_group_id: customer_group.id, - }) - const location = await stockLocationModule.createStockLocations({ name: "Warehouse", }) - let cart = await cartModuleService.createCarts({ - currency_code: "usd", - sales_channel_id: salesChannel.id, - customer_id: customer.id, - }) - const [product] = await productModule.createProducts([ { title: "Test product", @@ -973,6 +1800,7 @@ medusaIntegrationTestRunner({ inventory_item_id: inventoryItem.id, location_id: location.id, stocked_quantity: 2, + reserved_quantity: 0, }, ]) @@ -985,31 +1813,6 @@ medusaIntegrationTestRunner({ ], }) - await pricingModule.createPricePreferences({ - attribute: "currency_code", - value: "usd", - is_tax_inclusive: true, - }) - - await pricingModule.createPriceLists([ - { - title: "test price list", - description: "test", - status: PriceListStatus.ACTIVE, - type: PriceListType.OVERRIDE, - prices: [ - { - amount: 1500, - currency_code: "usd", - price_set_id: priceSet.id, - }, - ], - rules: { - "customer.groups.id": [customer_group.id], - }, - }, - ]) - await remoteLink.create([ { [Modules.PRODUCT]: { @@ -1037,38 +1840,49 @@ medusaIntegrationTestRunner({ }, ]) + let cart = await cartModuleService.createCarts({ + currency_code: "usd", + sales_channel_id: salesChannel.id, + items: [ + { + variant_id: product.variants[0].id, + quantity: 1, + unit_price: 5000, + title: "Test item", + }, + ], + }) + cart = await cartModuleService.retrieveCart(cart.id, { - select: ["id", "region_id", "currency_code", "sales_channel_id"], + select: ["id", "region_id", "currency_code"], + relations: ["items", "items.variant_id", "items.metadata"], }) - await addToCartWorkflow(appContainer).run({ + const item = cart.items?.[0]! + + await updateLineItemInCartWorkflow(appContainer).run({ input: { - items: [ - { - variant_id: product.variants[0].id, - quantity: 1, - }, - ], cart_id: cart.id, + item_id: item.id, + update: { + metadata: { + foo: "bar", + }, + unit_price: 4000, + quantity: 2, + }, }, }) - cart = await cartModuleService.retrieveCart(cart.id, { - relations: ["items"], - }) + const updatedItem = await cartModuleService.retrieveLineItem(item.id) - expect(cart).toEqual( + expect(updatedItem).toEqual( expect.objectContaining({ - id: cart.id, - currency_code: "usd", - items: expect.arrayContaining([ - expect.objectContaining({ - unit_price: 1500, - is_tax_inclusive: true, - quantity: 1, - title: "Test variant", - }), - ]), + id: item.id, + unit_price: 4000, + is_custom_price: true, + quantity: 2, + title: "Test variant", }) ) }) @@ -1184,8 +1998,8 @@ medusaIntegrationTestRunner({ { variant_id: product.variants[0].id, quantity: 1, - unit_price: 5000, title: "Test item", + unit_price: 5000, }, ], }) @@ -1197,9 +2011,7 @@ medusaIntegrationTestRunner({ const item = cart.items?.[0]! - const { errors } = await updateLineItemInCartWorkflow( - appContainer - ).run({ + await updateLineItemInCartWorkflow(appContainer).run({ input: { cart_id: cart.id, item_id: item.id, diff --git a/integration-tests/modules/__tests__/order/draft-order.spec.ts b/integration-tests/modules/__tests__/order/draft-order.spec.ts index b097ed54c0cc2..e11bf06c43ed3 100644 --- a/integration-tests/modules/__tests__/order/draft-order.spec.ts +++ b/integration-tests/modules/__tests__/order/draft-order.spec.ts @@ -207,8 +207,8 @@ medusaIntegrationTestRunner({ }, { title: "Custom Item", - sku: "sku123", - barcode: "barcode123", + variant_sku: "sku123", + variant_barcode: "barcode123", unit_price: 2200, quantity: 1, }, @@ -254,6 +254,7 @@ medusaIntegrationTestRunner({ requires_shipping: true, is_discountable: true, is_tax_inclusive: true, + is_custom_price: false, raw_compare_at_unit_price: null, raw_unit_price: expect.objectContaining({ value: "3000", @@ -323,7 +324,8 @@ medusaIntegrationTestRunner({ title: "Custom Item", variant_sku: "sku123", variant_barcode: "barcode123", - variant_title: "Custom Item", + variant_title: null, + is_custom_price: true, raw_unit_price: expect.objectContaining({ value: "2200", }), diff --git a/packages/core/core-flows/src/cart/steps/validate-line-item-prices.ts b/packages/core/core-flows/src/cart/steps/validate-line-item-prices.ts new file mode 100644 index 0000000000000..9241e1d1cfac2 --- /dev/null +++ b/packages/core/core-flows/src/cart/steps/validate-line-item-prices.ts @@ -0,0 +1,36 @@ +import { MedusaError, isPresent } from "@medusajs/framework/utils" +import { createStep } from "@medusajs/framework/workflows-sdk" + +export interface ValidateLineItemPricesStepInput { + items: { + unit_price?: number | null + title: string + }[] +} + +export const validateLineItemPricesStepId = "validate-line-item-prices" +/** + * This step validates the specified line item objects to ensure they have prices. + */ +export const validateLineItemPricesStep = createStep( + validateLineItemPricesStepId, + async (data: ValidateLineItemPricesStepInput, { container }) => { + if (!data.items?.length) { + return + } + + const priceNotFound: string[] = [] + for (const item of data.items) { + if (!isPresent(item?.unit_price)) { + priceNotFound.push(item.title) + } + } + + if (priceNotFound.length > 0) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Items ${priceNotFound.join(", ")} do not have a price` + ) + } + } +) diff --git a/packages/core/core-flows/src/cart/steps/validate-variant-prices.ts b/packages/core/core-flows/src/cart/steps/validate-variant-prices.ts index b5731a765e1aa..fe34300bf5098 100644 --- a/packages/core/core-flows/src/cart/steps/validate-variant-prices.ts +++ b/packages/core/core-flows/src/cart/steps/validate-variant-prices.ts @@ -18,6 +18,10 @@ export const validateVariantPricesStepId = "validate-variant-prices" export const validateVariantPricesStep = createStep( validateVariantPricesStepId, async (data: ValidateVariantPricesStepInput, { container }) => { + if (!data.variants?.length) { + return + } + const priceNotFound: string[] = [] for (const variant of data.variants) { if (!isPresent(variant?.calculated_price?.calculated_amount)) { diff --git a/packages/core/core-flows/src/cart/utils/prepare-line-item-data.ts b/packages/core/core-flows/src/cart/utils/prepare-line-item-data.ts index 433e36a8db790..74d216f576948 100644 --- a/packages/core/core-flows/src/cart/utils/prepare-line-item-data.ts +++ b/packages/core/core-flows/src/cart/utils/prepare-line-item-data.ts @@ -1,61 +1,106 @@ import { BigNumberInput, - CartLineItemDTO, CreateOrderAdjustmentDTO, CreateOrderLineItemTaxLineDTO, InventoryItemDTO, + LineItemAdjustmentDTO, + LineItemTaxLineDTO, ProductVariantDTO, } from "@medusajs/framework/types" -import { isDefined, MathBN, PriceListType } from "@medusajs/framework/utils" - -interface Input { - item?: CartLineItemDTO +import { + isDefined, + isPresent, + MathBN, + PriceListType, +} from "@medusajs/framework/utils" + +interface PrepareItemLineItemInput { + title?: string + subtitle?: string + thumbnail?: string quantity: BigNumberInput - metadata?: Record - unitPrice: BigNumberInput - compareAtUnitPrice?: BigNumberInput | null - isTaxInclusive?: boolean - variant: ProductVariantDTO & { - inventory_items: { inventory: InventoryItemDTO }[] + + product_id?: string + product_title?: string + product_description?: string + product_subtitle?: string + product_type?: string + product_type_id?: string + product_collection?: string + product_handle?: string + + variant_id?: string + variant_sku?: string + variant_barcode?: string + variant_title?: string + variant_option_values?: Record + + requires_shipping?: boolean + + is_discountable?: boolean + is_tax_inclusive?: boolean + + raw_compare_at_unit_price?: BigNumberInput + compare_at_unit_price?: BigNumberInput + unit_price?: BigNumberInput + + tax_lines?: LineItemTaxLineDTO[] + adjustments?: LineItemAdjustmentDTO[] + cart_id?: string + metadata?: Record | null +} + +export interface PrepareVariantLineItemInput extends ProductVariantDTO { + inventory_items: { inventory: InventoryItemDTO }[] + calculated_price: { calculated_price: { - calculated_price: { - price_list_type: string - } - original_amount: BigNumberInput - calculated_amount: BigNumberInput + price_list_type: string } + is_calculated_price_tax_inclusive: boolean + original_amount: BigNumberInput + calculated_amount: BigNumberInput } +} + +export interface PrepareLineItemDataInput { + item?: PrepareItemLineItemInput + isCustomPrice?: boolean + variant?: PrepareVariantLineItemInput taxLines?: CreateOrderLineItemTaxLineDTO[] adjustments?: CreateOrderAdjustmentDTO[] cartId?: string + unitPrice?: BigNumberInput + isTaxInclusive: boolean } -export function prepareLineItemData(data: Input) { +export function prepareLineItemData(data: PrepareLineItemDataInput) { const { item, variant, - unitPrice, - isTaxInclusive, - quantity, - metadata, cartId, taxLines, adjustments, + isCustomPrice, + unitPrice, + isTaxInclusive, } = data - if (!variant.product) { + if (variant && !variant.product) { throw new Error("Variant does not have a product") } - let compareAtUnitPrice = data.compareAtUnitPrice + let compareAtUnitPrice = item?.compare_at_unit_price + + const isSalePrice = + variant?.calculated_price?.calculated_price?.price_list_type === + PriceListType.SALE if ( - !isDefined(compareAtUnitPrice) && - variant.calculated_price.calculated_price.price_list_type === - PriceListType.SALE && + !isPresent(compareAtUnitPrice) && + isSalePrice && !MathBN.eq( - variant.calculated_price.original_amount, - variant.calculated_price.calculated_amount + variant.calculated_price?.original_amount, + variant.calculated_price?.calculated_amount ) ) { compareAtUnitPrice = variant.calculated_price.original_amount @@ -63,9 +108,8 @@ export function prepareLineItemData(data: Input) { // Note: If any of the items require shipping, we enable fulfillment // unless explicitly set to not require shipping by the item in the request - const { inventory_items: inventoryItems } = variant - const someInventoryRequiresShipping = inventoryItems.length - ? inventoryItems.some( + const someInventoryRequiresShipping = variant?.inventory_items?.length + ? variant.inventory_items.some( (inventoryItem) => !!inventoryItem.inventory.requires_shipping ) : true @@ -74,37 +118,42 @@ export function prepareLineItemData(data: Input) { ? item.requires_shipping : someInventoryRequiresShipping - const lineItem: any = { - quantity, - title: variant.title ?? item?.title, - subtitle: variant.product.title ?? item?.subtitle, - thumbnail: variant.product.thumbnail ?? item?.thumbnail, + let lineItem: any = { + quantity: item?.quantity, + title: variant?.title ?? item?.title, + subtitle: variant?.product?.title ?? item?.subtitle, + thumbnail: variant?.product?.thumbnail ?? item?.thumbnail, - product_id: variant.product.id ?? item?.product_id, - product_title: variant.product.title ?? item?.product_title, + product_id: variant?.product?.id ?? item?.product_id, + product_title: variant?.product?.title ?? item?.product_title, product_description: - variant.product.description ?? item?.product_description, - product_subtitle: variant.product.subtitle ?? item?.product_subtitle, - product_type: variant.product.type?.value ?? item?.product_type ?? null, - product_type_id: variant.product.type?.id ?? item?.product_type_id ?? null, + variant?.product?.description ?? item?.product_description, + product_subtitle: variant?.product?.subtitle ?? item?.product_subtitle, + product_type: variant?.product?.type?.value ?? item?.product_type ?? null, + product_type_id: + variant?.product?.type?.id ?? item?.product_type_id ?? null, product_collection: - variant.product.collection?.title ?? item?.product_collection ?? null, - product_handle: variant.product.handle ?? item?.product_handle, + variant?.product?.collection?.title ?? item?.product_collection ?? null, + product_handle: variant?.product?.handle ?? item?.product_handle, - variant_id: variant.id, - variant_sku: variant.sku ?? item?.variant_sku, - variant_barcode: variant.barcode ?? item?.variant_barcode, - variant_title: variant.title ?? item?.variant_title, + variant_id: variant?.id, + variant_sku: variant?.sku ?? item?.variant_sku, + variant_barcode: variant?.barcode ?? item?.variant_barcode, + variant_title: variant?.title ?? item?.variant_title, variant_option_values: item?.variant_option_values, - is_discountable: variant.product.discountable ?? item?.is_discountable, + is_discountable: variant?.product?.discountable ?? item?.is_discountable, requires_shipping: requiresShipping, unit_price: unitPrice, compare_at_unit_price: compareAtUnitPrice, is_tax_inclusive: !!isTaxInclusive, - metadata, + metadata: item?.metadata ?? {}, + } + + if (isCustomPrice) { + lineItem.is_custom_price = !!isCustomPrice } if (taxLines) { diff --git a/packages/core/core-flows/src/cart/workflows/add-to-cart.ts b/packages/core/core-flows/src/cart/workflows/add-to-cart.ts index 22beb5f262865..737305b5a99e9 100644 --- a/packages/core/core-flows/src/cart/workflows/add-to-cart.ts +++ b/packages/core/core-flows/src/cart/workflows/add-to-cart.ts @@ -1,13 +1,11 @@ +import { AddToCartWorkflowInputDTO } from "@medusajs/framework/types" +import { CartWorkflowEvents, isDefined } from "@medusajs/framework/utils" import { - AddToCartWorkflowInputDTO, - CreateLineItemForCartDTO, -} from "@medusajs/framework/types" -import { CartWorkflowEvents } from "@medusajs/framework/utils" -import { - WorkflowData, createWorkflow, parallelize, transform, + when, + WorkflowData, } from "@medusajs/framework/workflows-sdk" import { useQueryGraphStep } from "../../common" import { emitEventStep } from "../../common/steps/emit-event" @@ -18,12 +16,16 @@ import { updateLineItemsStep, } from "../steps" import { validateCartStep } from "../steps/validate-cart" +import { validateLineItemPricesStep } from "../steps/validate-line-item-prices" import { validateVariantPricesStep } from "../steps/validate-variant-prices" import { cartFieldsForPricingContext, productVariantsFields, } from "../utils/fields" -import { prepareLineItemData } from "../utils/prepare-line-item-data" +import { + prepareLineItemData, + PrepareLineItemDataInput, +} from "../utils/prepare-line-item-data" import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory" import { refreshCartItemsWorkflow } from "./refresh-cart-items" @@ -50,41 +52,55 @@ export const addToCartWorkflow = createWorkflow( validateCartStep({ cart }) const variantIds = transform({ input }, (data) => { - return (data.input.items ?? []).map((i) => i.variant_id) + return (data.input.items ?? []).map((i) => i.variant_id).filter(Boolean) }) - const variants = useRemoteQueryStep({ - entry_point: "variants", - fields: productVariantsFields, - variables: { - id: variantIds, - calculated_price: { context: cart }, - }, - throw_if_key_not_found: true, + const variants = when({ variantIds }, ({ variantIds }) => { + return !!variantIds.length + }).then(() => { + return useRemoteQueryStep({ + entry_point: "variants", + fields: productVariantsFields, + variables: { + id: variantIds, + calculated_price: { + context: cart, + }, + }, + }) }) validateVariantPricesStep({ variants }) const lineItems = transform({ input, variants }, (data) => { const items = (data.input.items ?? []).map((item) => { - const variant = data.variants.find((v) => v.id === item.variant_id)! + const variant = (data.variants ?? []).find( + (v) => v.id === item.variant_id + )! - return prepareLineItemData({ + const input: PrepareLineItemDataInput = { + item, variant: variant, - unitPrice: - item.unit_price || variant.calculated_price.calculated_amount, + cartId: data.input.cart_id, + unitPrice: item.unit_price, isTaxInclusive: - item.is_tax_inclusive || - variant.calculated_price.is_calculated_price_tax_inclusive, - quantity: item.quantity, - metadata: item?.metadata ?? {}, - cartId: input.cart_id, - }) as CreateLineItemForCartDTO + item.is_tax_inclusive ?? + variant?.calculated_price?.is_calculated_price_tax_inclusive, + isCustomPrice: isDefined(item?.unit_price), + } + + if (variant && !input.unitPrice) { + input.unitPrice = variant.calculated_price?.calculated_amount + } + + return prepareLineItemData(input) }) return items }) + validateLineItemPricesStep({ items: lineItems }) + const { itemsToCreate = [], itemsToUpdate = [] } = getLineItemActionsStep({ id: cart.id, items: lineItems, diff --git a/packages/core/core-flows/src/cart/workflows/complete-cart.ts b/packages/core/core-flows/src/cart/workflows/complete-cart.ts index 42c28e1b5bdf7..b0d199c860eac 100644 --- a/packages/core/core-flows/src/cart/workflows/complete-cart.ts +++ b/packages/core/core-flows/src/cart/workflows/complete-cart.ts @@ -5,7 +5,7 @@ import { import { Modules, OrderStatus, - OrderWorkflowEvents, + OrderWorkflowEvents } from "@medusajs/framework/utils" import { createWorkflow, @@ -31,6 +31,7 @@ import { prepareConfirmInventoryInput } from "../utils/prepare-confirm-inventory import { prepareAdjustmentsData, prepareLineItemData, + PrepareLineItemDataInput, prepareTaxLinesData, } from "../utils/prepare-line-item-data" @@ -115,18 +116,17 @@ export const completeCartWorkflow = createWorkflow( }) ?? [] const allItems = (cart.items ?? []).map((item) => { - return prepareLineItemData({ + const input: PrepareLineItemDataInput = { item, variant: item.variant, - unitPrice: item.raw_unit_price ?? item.unit_price, - compareAtUnitPrice: - item.raw_compare_at_unit_price ?? item.compare_at_unit_price, + cartId: cart.id, + unitPrice: item.unit_price, isTaxInclusive: item.is_tax_inclusive, - quantity: item.raw_quantity ?? item.quantity, - metadata: item?.metadata, taxLines: item.tax_lines ?? [], adjustments: item.adjustments ?? [], - }) + } + + return prepareLineItemData(input) }) const shippingMethods = (cart.shipping_methods ?? []).map((sm) => { diff --git a/packages/core/core-flows/src/cart/workflows/create-carts.ts b/packages/core/core-flows/src/cart/workflows/create-carts.ts index ddec635c2bcce..b1e107549942c 100644 --- a/packages/core/core-flows/src/cart/workflows/create-carts.ts +++ b/packages/core/core-flows/src/cart/workflows/create-carts.ts @@ -2,14 +2,19 @@ import { AdditionalData, CreateCartWorkflowInputDTO, } from "@medusajs/framework/types" -import { CartWorkflowEvents, MedusaError } from "@medusajs/framework/utils" import { - WorkflowData, - WorkflowResponse, + CartWorkflowEvents, + isDefined, + MedusaError, +} from "@medusajs/framework/utils" +import { createHook, createWorkflow, parallelize, transform, + when, + WorkflowData, + WorkflowResponse, } from "@medusajs/framework/workflows-sdk" import { emitEventStep } from "../../common/steps/emit-event" import { useRemoteQueryStep } from "../../common/steps/use-remote-query" @@ -18,11 +23,14 @@ import { findOneOrAnyRegionStep, findOrCreateCustomerStep, findSalesChannelStep, - getVariantPriceSetsStep, } from "../steps" +import { validateLineItemPricesStep } from "../steps/validate-line-item-prices" import { validateVariantPricesStep } from "../steps/validate-variant-prices" import { productVariantsFields } from "../utils/fields" -import { prepareLineItemData } from "../utils/prepare-line-item-data" +import { + prepareLineItemData, + PrepareLineItemDataInput, +} from "../utils/prepare-line-item-data" import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory" import { refreshPaymentCollectionForCartWorkflow } from "./refresh-payment-collection" import { updateCartPromotionsWorkflow } from "./update-cart-promotions" @@ -36,7 +44,7 @@ export const createCartWorkflow = createWorkflow( createCartWorkflowId, (input: WorkflowData) => { const variantIds = transform({ input }, (data) => { - return (data.input.items ?? []).map((i) => i.variant_id) + return (data.input.items ?? []).map((i) => i.variant_id).filter(Boolean) }) const [salesChannel, region, customerData] = parallelize( @@ -68,16 +76,19 @@ export const createCartWorkflow = createWorkflow( } ) - const variants = useRemoteQueryStep({ - entry_point: "variants", - fields: productVariantsFields, - variables: { - id: variantIds, - calculated_price: { - context: pricingContext, + const variants = when({ variantIds }, ({ variantIds }) => { + return !!variantIds.length + }).then(() => { + return useRemoteQueryStep({ + entry_point: "variants", + fields: productVariantsFields, + variables: { + id: variantIds, + calculated_price: { + context: pricingContext, + }, }, - }, - throw_if_key_not_found: true, + }) }) validateVariantPricesStep({ variants }) @@ -90,11 +101,6 @@ export const createCartWorkflow = createWorkflow( }, }) - const priceSets = getVariantPriceSetsStep({ - variantIds, - context: pricingContext, - }) - const cartInput = transform( { input, region, customerData, salesChannel }, (data) => { @@ -131,26 +137,34 @@ export const createCartWorkflow = createWorkflow( } ) - const lineItems = transform({ priceSets, input, variants }, (data) => { + const lineItems = transform({ input, variants }, (data) => { const items = (data.input.items ?? []).map((item) => { - const variant = data.variants.find((v) => v.id === item.variant_id)! + const variant = (data.variants ?? []).find( + (v) => v.id === item.variant_id + )! - return prepareLineItemData({ + const input: PrepareLineItemDataInput = { + item, variant: variant, - unitPrice: - item.unit_price || - data.priceSets[item.variant_id].calculated_amount, + unitPrice: item.unit_price, isTaxInclusive: - item.is_tax_inclusive || - data.priceSets[item.variant_id].is_calculated_price_tax_inclusive, - quantity: item.quantity, - metadata: item?.metadata ?? {}, - }) + item.is_tax_inclusive ?? + variant?.calculated_price?.is_calculated_price_tax_inclusive, + isCustomPrice: isDefined(item?.unit_price), + } + + if (variant && !input.unitPrice) { + input.unitPrice = variant.calculated_price?.calculated_amount + } + + return prepareLineItemData(input) }) return items }) + validateLineItemPricesStep({ items: lineItems }) + const cartToCreate = transform({ lineItems, cartInput }, (data) => { return { ...data.cartInput, diff --git a/packages/core/core-flows/src/cart/workflows/refresh-cart-items.ts b/packages/core/core-flows/src/cart/workflows/refresh-cart-items.ts index ac5724c6c2bb7..722cd2a6252d1 100644 --- a/packages/core/core-flows/src/cart/workflows/refresh-cart-items.ts +++ b/packages/core/core-flows/src/cart/workflows/refresh-cart-items.ts @@ -6,6 +6,7 @@ import { import { createWorkflow, transform, + when, WorkflowData, WorkflowResponse, } from "@medusajs/framework/workflows-sdk" @@ -17,7 +18,10 @@ import { cartFieldsForRefreshSteps, productVariantsFields, } from "../utils/fields" -import { prepareLineItemData } from "../utils/prepare-line-item-data" +import { + prepareLineItemData, + PrepareLineItemDataInput, +} from "../utils/prepare-line-item-data" import { refreshCartShippingMethodsWorkflow } from "./refresh-cart-shipping-methods" import { refreshPaymentCollectionForCartWorkflow } from "./refresh-payment-collection" import { updateCartPromotionsWorkflow } from "./update-cart-promotions" @@ -43,40 +47,49 @@ export const refreshCartItemsWorkflow = createWorkflow( }) const variantIds = transform({ cart }, (data) => { - return (data.cart.items ?? []).map((i) => i.variant_id) + return (data.cart.items ?? []).map((i) => i.variant_id).filter(Boolean) }) const cartPricingContext = transform({ cart }, ({ cart }) => { return filterObjectByKeys(cart, cartFieldsForPricingContext) }) - const variants = useRemoteQueryStep({ - entry_point: "variants", - fields: productVariantsFields, - variables: { - id: variantIds, - calculated_price: { - context: cartPricingContext, + const variants = when({ variantIds }, ({ variantIds }) => { + return !!variantIds.length + }).then(() => { + return useRemoteQueryStep({ + entry_point: "variants", + fields: productVariantsFields, + variables: { + id: variantIds, + calculated_price: { + context: cartPricingContext, + }, }, - }, - throw_if_key_not_found: true, - }).config({ name: "fetch-variants" }) + }).config({ name: "fetch-variants" }) + }) validateVariantPricesStep({ variants }) const lineItems = transform({ cart, variants }, ({ cart, variants }) => { const items = cart.items.map((item) => { - const variant = variants.find((v) => v.id === item.variant_id)! + const variant = (variants ?? []).find((v) => v.id === item.variant_id)! - const preparedItem = prepareLineItemData({ + const input: PrepareLineItemDataInput = { + item, variant: variant, - unitPrice: variant.calculated_price.calculated_amount, - isTaxInclusive: - variant.calculated_price.is_calculated_price_tax_inclusive, - quantity: item.quantity, - metadata: item.metadata, cartId: cart.id, - }) + unitPrice: item.unit_price, + isTaxInclusive: item.is_tax_inclusive, + } + + if (variant && !item.is_custom_price) { + input.unitPrice = variant.calculated_price?.calculated_amount + input.isTaxInclusive = + variant.calculated_price?.is_calculated_price_tax_inclusive + } + + const preparedItem = prepareLineItemData(input) return { selector: { id: item.id }, diff --git a/packages/core/core-flows/src/cart/workflows/update-cart.ts b/packages/core/core-flows/src/cart/workflows/update-cart.ts index 9114ed6f574cd..2733ea15958f9 100644 --- a/packages/core/core-flows/src/cart/workflows/update-cart.ts +++ b/packages/core/core-flows/src/cart/workflows/update-cart.ts @@ -16,7 +16,12 @@ import { WorkflowData, WorkflowResponse, } from "@medusajs/framework/workflows-sdk" -import { emitEventStep, useRemoteQueryStep } from "../../common" +import { + emitEventStep, + useQueryGraphStep, + useRemoteQueryStep, +} from "../../common" +import { deleteLineItemsStep } from "../../line-item" import { findOrCreateCustomerStep, findSalesChannelStep, @@ -167,11 +172,18 @@ export const updateCartWorkflow = createWorkflow( }) */ - when({ input, cartToUpdate }, ({ input, cartToUpdate }) => { - return ( - isDefined(input.region_id) && - input.region_id !== cartToUpdate?.region?.id - ) + const regionUpdated = transform( + { input, cartToUpdate }, + ({ input, cartToUpdate }) => { + return ( + isDefined(input.region_id) && + input.region_id !== cartToUpdate?.region?.id + ) + } + ) + + when({ regionUpdated }, ({ regionUpdated }) => { + return !!regionUpdated }).then(() => { emitEventStep({ eventName: CartWorkflowEvents.REGION_UPDATED, @@ -187,6 +199,27 @@ export const updateCartWorkflow = createWorkflow( }) ) + // In case the region is updated, we might have a new currency OR tax inclusivity setting + // Therefore, we need to delete line items with a custom price for good measure + when({ regionUpdated }, ({ regionUpdated }) => { + return !!regionUpdated + }).then(() => { + const lineItems = useQueryGraphStep({ + entity: "line_items", + filters: { + cart_id: input.id, + is_custom_price: true, + }, + fields: ["id"], + }) + + const lineItemIds = transform({ lineItems }, ({ lineItems }) => { + return lineItems.data.map((i) => i.id) + }) + + deleteLineItemsStep(lineItemIds) + }) + const cart = refreshCartItemsWorkflow.runAsStep({ input: { cart_id: cartInput.id, promo_codes: input.promo_codes }, }) diff --git a/packages/core/core-flows/src/cart/workflows/update-line-item-in-cart.ts b/packages/core/core-flows/src/cart/workflows/update-line-item-in-cart.ts index 95f6e359af115..630f085d5d5f6 100644 --- a/packages/core/core-flows/src/cart/workflows/update-line-item-in-cart.ts +++ b/packages/core/core-flows/src/cart/workflows/update-line-item-in-cart.ts @@ -1,8 +1,10 @@ import { UpdateLineItemInCartWorkflowInputDTO } from "@medusajs/framework/types" +import { isDefined, MedusaError } from "@medusajs/framework/utils" import { - WorkflowData, createWorkflow, transform, + when, + WorkflowData, } from "@medusajs/framework/workflows-sdk" import { useQueryGraphStep } from "../../common" import { useRemoteQueryStep } from "../../common/steps/use-remote-query" @@ -40,19 +42,22 @@ export const updateLineItemInCartWorkflow = createWorkflow( validateCartStep({ cart }) const variantIds = transform({ item }, ({ item }) => { - return [item.variant_id] + return [item.variant_id].filter(Boolean) }) - const variants = useRemoteQueryStep({ - entry_point: "variants", - fields: productVariantsFields, - variables: { - id: variantIds, - calculated_price: { - context: cart, + const variants = when({ variantIds }, ({ variantIds }) => { + return !!variantIds.length + }).then(() => { + return useRemoteQueryStep({ + entry_point: "variants", + fields: productVariantsFields, + variables: { + id: variantIds, + calculated_price: { + context: cart, + }, }, - }, - throw_if_key_not_found: true, + }).config({ name: "fetch-variants" }) }) validateVariantPricesStep({ variants }) @@ -69,16 +74,36 @@ export const updateLineItemInCartWorkflow = createWorkflow( }, }) - const lineItemUpdate = transform({ input, variants }, (data) => { - const variant = data.variants[0] + const lineItemUpdate = transform({ input, variants, item }, (data) => { + const variant = data.variants?.[0] ?? undefined + const item = data.item + + const updateData = { + ...data.input.update, + unit_price: isDefined(data.input.update.unit_price) + ? data.input.update.unit_price + : item.unit_price, + is_custom_price: isDefined(data.input.update.unit_price) + ? true + : item.is_custom_price, + is_tax_inclusive: + item.is_tax_inclusive || + variant?.calculated_price?.is_calculated_price_tax_inclusive, + } + + if (variant && !updateData.is_custom_price) { + updateData.unit_price = variant.calculated_price.calculated_amount + } + + if (!isDefined(updateData.unit_price)) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Line item ${item.title} has no unit price` + ) + } return { - data: { - ...data.input.update, - unit_price: variant.calculated_price.calculated_amount, - is_tax_inclusive: - !!variant.calculated_price.is_calculated_price_tax_inclusive, - }, + data: updateData, selector: { id: data.input.item_id, }, diff --git a/packages/core/core-flows/src/common/steps/use-remote-query.ts b/packages/core/core-flows/src/common/steps/use-remote-query.ts index 27045d394c952..6392745c31fc7 100644 --- a/packages/core/core-flows/src/common/steps/use-remote-query.ts +++ b/packages/core/core-flows/src/common/steps/use-remote-query.ts @@ -53,9 +53,9 @@ export const useRemoteQueryStepId = "use-remote-query" * Learn more in the [Remote Query documentation](https://docs.medusajs.com/learn/fundamentals/module-links/query). * * :::note - * + * * This step is deprecated. Use {@link useQueryGraphStep} instead. - * + * * ::: * * @example diff --git a/packages/core/core-flows/src/order/utils/prepare-custom-line-item-data.ts b/packages/core/core-flows/src/order/utils/prepare-custom-line-item-data.ts deleted file mode 100644 index 16410cd31bc95..0000000000000 --- a/packages/core/core-flows/src/order/utils/prepare-custom-line-item-data.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { - BigNumberInput, - CreateOrderAdjustmentDTO, - CreateOrderLineItemTaxLineDTO, -} from "@medusajs/framework/types" -import { - prepareAdjustmentsData, - prepareTaxLinesData, -} from "../../cart/utils/prepare-line-item-data" - -interface Input { - quantity: BigNumberInput - metadata?: Record - unitPrice: BigNumberInput - isTaxInclusive?: boolean - taxLines?: CreateOrderLineItemTaxLineDTO[] - adjustments?: CreateOrderAdjustmentDTO[] - variant: { - title: string - sku?: string - barcode?: string - } -} - -interface Output { - quantity: BigNumberInput - title: string - variant_sku?: string - variant_barcode?: string - variant_title?: string - unit_price: BigNumberInput - is_tax_inclusive: boolean - metadata?: Record -} - -export function prepareCustomLineItemData(data: Input): Output { - const { - variant, - unitPrice, - isTaxInclusive, - quantity, - metadata, - taxLines, - adjustments, - } = data - - const lineItem: any = { - quantity, - title: variant.title, - variant_sku: variant.sku, - variant_barcode: variant.barcode, - variant_title: variant.title, - - unit_price: unitPrice, - is_tax_inclusive: !!isTaxInclusive, - metadata, - } - - if (taxLines) { - lineItem.tax_lines = prepareTaxLinesData(taxLines) - } - - if (adjustments) { - lineItem.adjustments = prepareAdjustmentsData(adjustments) - } - - return lineItem -} diff --git a/packages/core/core-flows/src/order/workflows/add-line-items.ts b/packages/core/core-flows/src/order/workflows/add-line-items.ts index ce7f2dfb2d2b7..3ac6d2f0df3de 100644 --- a/packages/core/core-flows/src/order/workflows/add-line-items.ts +++ b/packages/core/core-flows/src/order/workflows/add-line-items.ts @@ -1,59 +1,48 @@ import { OrderLineItemDTO, OrderWorkflow } from "@medusajs/framework/types" -import { MathBN, MedusaError } from "@medusajs/framework/utils" +import { isDefined, MedusaError } from "@medusajs/framework/utils" import { - WorkflowData, - WorkflowResponse, createWorkflow, parallelize, transform, + when, + WorkflowData, + WorkflowResponse, } from "@medusajs/framework/workflows-sdk" import { findOneOrAnyRegionStep } from "../../cart/steps/find-one-or-any-region" import { findOrCreateCustomerStep } from "../../cart/steps/find-or-create-customer" import { findSalesChannelStep } from "../../cart/steps/find-sales-channel" -import { getVariantPriceSetsStep } from "../../cart/steps/get-variant-price-sets" +import { validateLineItemPricesStep } from "../../cart/steps/validate-line-item-prices" import { validateVariantPricesStep } from "../../cart/steps/validate-variant-prices" -import { prepareLineItemData } from "../../cart/utils/prepare-line-item-data" +import { + prepareLineItemData, + PrepareLineItemDataInput, +} from "../../cart/utils/prepare-line-item-data" import { confirmVariantInventoryWorkflow } from "../../cart/workflows/confirm-variant-inventory" import { useRemoteQueryStep } from "../../common" import { createOrderLineItemsStep } from "../steps" import { productVariantsFields } from "../utils/fields" -import { prepareCustomLineItemData } from "../utils/prepare-custom-line-item-data" function prepareLineItems(data) { const items = (data.input.items ?? []).map((item) => { const variant = data.variants.find((v) => v.id === item.variant_id)! - if (!variant) { - return prepareCustomLineItemData({ - variant: { - ...item, - }, - unitPrice: MathBN.max(0, item.unit_price), - isTaxInclusive: - item.is_tax_inclusive ?? - data.priceSets[item.variant_id!]?.is_calculated_price_tax_inclusive, - quantity: item.quantity as number, - metadata: item?.metadata, - taxLines: item.tax_lines || [], - adjustments: item.adjustments || [], - }) - } - - return prepareLineItemData({ + const input: PrepareLineItemDataInput = { + item, variant: variant, - unitPrice: MathBN.max( - 0, - item.unit_price ?? - data.priceSets[item.variant_id!]?.raw_calculated_amount - ), + unitPrice: item.unit_price, isTaxInclusive: item.is_tax_inclusive ?? - data.priceSets[item.variant_id!]?.is_calculated_price_tax_inclusive, - quantity: item.quantity as number, - metadata: item?.metadata, + variant?.calculated_price?.is_calculated_price_tax_inclusive, + isCustomPrice: isDefined(item?.unit_price), taxLines: item.tax_lines || [], adjustments: item.adjustments || [], - }) + } + + if (variant && !input.unitPrice) { + input.unitPrice = variant.calculated_price?.calculated_amount + } + + return prepareLineItemData(input) }) return items @@ -117,17 +106,20 @@ export const addOrderLineItemsWorkflow = createWorkflow( } ) - const variants = useRemoteQueryStep({ - entry_point: "variants", - fields: productVariantsFields, - variables: { - id: variantIds, - calculated_price: { - context: pricingContext, + const variants = when({ variantIds }, ({ variantIds }) => { + return !!variantIds.length + }).then(() => { + return useRemoteQueryStep({ + entry_point: "variants", + fields: productVariantsFields, + variables: { + id: variantIds, + calculated_price: { + context: pricingContext, + }, }, - }, - throw_if_key_not_found: true, - }).config({ name: "variants-query" }) + }) + }) validateVariantPricesStep({ variants }) @@ -139,15 +131,9 @@ export const addOrderLineItemsWorkflow = createWorkflow( }, }) - const priceSets = getVariantPriceSetsStep({ - variantIds, - context: pricingContext, - }) + const lineItems = transform({ input, variants }, prepareLineItems) - const lineItems = transform( - { priceSets, input, variants }, - prepareLineItems - ) + validateLineItemPricesStep({ items: lineItems }) return new WorkflowResponse( createOrderLineItemsStep({ diff --git a/packages/core/core-flows/src/order/workflows/create-order.ts b/packages/core/core-flows/src/order/workflows/create-order.ts index 80a015051143c..4ccd369b275b0 100644 --- a/packages/core/core-flows/src/order/workflows/create-order.ts +++ b/packages/core/core-flows/src/order/workflows/create-order.ts @@ -1,5 +1,5 @@ import { AdditionalData, CreateOrderDTO } from "@medusajs/framework/types" -import { MathBN, MedusaError, isPresent } from "@medusajs/framework/utils" +import { MedusaError, isDefined, isPresent } from "@medusajs/framework/utils" import { WorkflowData, WorkflowResponse, @@ -7,51 +7,44 @@ import { createWorkflow, parallelize, transform, + when, } from "@medusajs/framework/workflows-sdk" import { findOneOrAnyRegionStep } from "../../cart/steps/find-one-or-any-region" import { findOrCreateCustomerStep } from "../../cart/steps/find-or-create-customer" import { findSalesChannelStep } from "../../cart/steps/find-sales-channel" -import { getVariantPriceSetsStep } from "../../cart/steps/get-variant-price-sets" +import { validateLineItemPricesStep } from "../../cart/steps/validate-line-item-prices" import { validateVariantPricesStep } from "../../cart/steps/validate-variant-prices" -import { prepareLineItemData } from "../../cart/utils/prepare-line-item-data" +import { + PrepareLineItemDataInput, + prepareLineItemData, +} from "../../cart/utils/prepare-line-item-data" import { confirmVariantInventoryWorkflow } from "../../cart/workflows/confirm-variant-inventory" import { useRemoteQueryStep } from "../../common" import { createOrdersStep } from "../steps" import { productVariantsFields } from "../utils/fields" -import { prepareCustomLineItemData } from "../utils/prepare-custom-line-item-data" import { updateOrderTaxLinesWorkflow } from "./update-tax-lines" function prepareLineItems(data) { const items = (data.input.items ?? []).map((item) => { const variant = data.variants.find((v) => v.id === item.variant_id)! - if (!variant) { - return prepareCustomLineItemData({ - variant: { - ...item, - }, - unitPrice: MathBN.max(0, item.unit_price), - isTaxInclusive: item.is_tax_inclusive, - quantity: item.quantity as number, - metadata: item?.metadata ?? {}, - }) - } - - return prepareLineItemData({ + const input: PrepareLineItemDataInput = { + item, variant: variant, - unitPrice: MathBN.max( - 0, - item.unit_price ?? - data.priceSets[item.variant_id!]?.raw_calculated_amount - ), + unitPrice: item.unit_price ?? undefined, isTaxInclusive: item.is_tax_inclusive ?? - data.priceSets[item.variant_id!]?.is_calculated_price_tax_inclusive, - quantity: item.quantity as number, - metadata: item?.metadata ?? {}, + variant?.calculated_price?.is_calculated_price_tax_inclusive, + isCustomPrice: isDefined(item?.unit_price), taxLines: item.tax_lines || [], adjustments: item.adjustments || [], - }) + } + + if (variant && !input.unitPrice) { + input.unitPrice = variant.calculated_price?.calculated_amount + } + + return prepareLineItemData(input) }) return items @@ -126,16 +119,19 @@ export const createOrderWorkflow = createWorkflow( } ) - const variants = useRemoteQueryStep({ - entry_point: "variants", - fields: productVariantsFields, - variables: { - id: variantIds, - calculated_price: { - context: pricingContext, + const variants = when({ variantIds }, ({ variantIds }) => { + return !!variantIds.length + }).then(() => { + return useRemoteQueryStep({ + entry_point: "variants", + fields: productVariantsFields, + variables: { + id: variantIds, + calculated_price: { + context: pricingContext, + }, }, - }, - throw_if_key_not_found: true, + }) }) validateVariantPricesStep({ variants }) @@ -148,20 +144,14 @@ export const createOrderWorkflow = createWorkflow( }, }) - const priceSets = getVariantPriceSetsStep({ - variantIds, - context: pricingContext, - }) - const orderInput = transform( { input, region, customerData, salesChannel }, getOrderInput ) - const lineItems = transform( - { priceSets, input, variants }, - prepareLineItems - ) + const lineItems = transform({ input, variants }, prepareLineItems) + + validateLineItemPricesStep({ items: lineItems }) const orderToCreate = transform({ lineItems, orderInput }, (data) => { return { diff --git a/packages/core/types/src/cart/common.ts b/packages/core/types/src/cart/common.ts index d066c491468b4..87f063beec0fe 100644 --- a/packages/core/types/src/cart/common.ts +++ b/packages/core/types/src/cart/common.ts @@ -663,6 +663,11 @@ export interface CartLineItemDTO extends CartLineItemTotalsDTO { */ is_tax_inclusive: boolean + /** + * Whether the line item price is a custom price. + */ + is_custom_price: boolean + /** * The calculated price of the line item. */ diff --git a/packages/core/types/src/cart/mutations.ts b/packages/core/types/src/cart/mutations.ts index 3d51cd8cb6051..d961b57b53e6b 100644 --- a/packages/core/types/src/cart/mutations.ts +++ b/packages/core/types/src/cart/mutations.ts @@ -555,6 +555,11 @@ export interface CreateLineItemDTO { */ is_tax_inclusive?: boolean + /** + * Whether the line item's amount is a custom price. + */ + is_custom_price?: boolean + /** * The calculated price of the line item after applying promotions. */ diff --git a/packages/core/types/src/cart/workflows.ts b/packages/core/types/src/cart/workflows.ts index c5f94c36fa111..194e575fef297 100644 --- a/packages/core/types/src/cart/workflows.ts +++ b/packages/core/types/src/cart/workflows.ts @@ -13,7 +13,7 @@ import { export interface CreateCartCreateLineItemDTO { quantity: BigNumberInput - variant_id: string + variant_id?: string title?: string subtitle?: string diff --git a/packages/core/utils/src/common/deep-flat-map.ts b/packages/core/utils/src/common/deep-flat-map.ts index 3be83e3a174db..e1336c810ed73 100644 --- a/packages/core/utils/src/common/deep-flat-map.ts +++ b/packages/core/utils/src/common/deep-flat-map.ts @@ -68,7 +68,7 @@ export function deepFlatMap( const currentKey = path[0] const remainingPath = path.slice(1) - if (!isDefined(element[currentKey])) { + if (!isDefined(element?.[currentKey])) { callback({ ...context }) continue } diff --git a/packages/medusa/src/api/admin/draft-orders/validators.ts b/packages/medusa/src/api/admin/draft-orders/validators.ts index 4581538f97f3a..800f1579122fe 100644 --- a/packages/medusa/src/api/admin/draft-orders/validators.ts +++ b/packages/medusa/src/api/admin/draft-orders/validators.ts @@ -37,23 +37,25 @@ const ShippingMethod = z.object({ amount: BigNumberInput, }) -const Item = z - .object({ - title: z.string().nullish(), - sku: z.string().nullish(), - barcode: z.string().nullish(), - variant_id: z.string().nullish(), - unit_price: BigNumberInput.nullish(), - quantity: z.number(), - metadata: z.record(z.unknown()).nullish(), - }) - .refine((data) => { - if (!data.variant_id) { - return data.title && (data.sku || data.barcode) - } - - return true - }) +const Item = z.object({ + title: z.string().nullish(), + variant_sku: z.string().nullish(), + variant_barcode: z.string().nullish(), + /** + * Use variant_sku instead + * @deprecated + */ + sku: z.string().nullish(), + /** + * Use variant_barcode instead + * @deprecated + */ + barcode: z.string().nullish(), + variant_id: z.string().nullish(), + unit_price: BigNumberInput.nullish(), + quantity: z.number(), + metadata: z.record(z.unknown()).nullish(), +}) export type AdminCreateDraftOrderType = z.infer const CreateDraftOrder = z diff --git a/packages/modules/cart/integration-tests/__tests__/services/cart-module/index.spec.ts b/packages/modules/cart/integration-tests/__tests__/services/cart-module/index.spec.ts index 5d276268af8d6..4977a3e0dd396 100644 --- a/packages/modules/cart/integration-tests/__tests__/services/cart-module/index.spec.ts +++ b/packages/modules/cart/integration-tests/__tests__/services/cart-module/index.spec.ts @@ -2512,6 +2512,7 @@ moduleIntegrationTestRunner({ requires_shipping: true, is_discountable: true, is_tax_inclusive: false, + is_custom_price: false, raw_compare_at_unit_price: null, raw_unit_price: { value: "100", @@ -2617,6 +2618,7 @@ moduleIntegrationTestRunner({ requires_shipping: true, is_discountable: true, is_tax_inclusive: false, + is_custom_price: false, raw_compare_at_unit_price: null, raw_unit_price: { value: "200", diff --git a/packages/modules/cart/src/migrations/.snapshot-medusa-cart.json b/packages/modules/cart/src/migrations/.snapshot-medusa-cart.json index 1b02c64962622..bcdc0b173dc73 100644 --- a/packages/modules/cart/src/migrations/.snapshot-medusa-cart.json +++ b/packages/modules/cart/src/migrations/.snapshot-medusa-cart.json @@ -617,6 +617,16 @@ "default": "false", "mappedType": "boolean" }, + "is_custom_price": { + "name": "is_custom_price", + "type": "boolean", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "false", + "mappedType": "boolean" + }, "compare_at_unit_price": { "name": "compare_at_unit_price", "type": "numeric", diff --git a/packages/modules/cart/src/migrations/Migration20241218091938.ts b/packages/modules/cart/src/migrations/Migration20241218091938.ts new file mode 100644 index 0000000000000..d4314e8c5cd3c --- /dev/null +++ b/packages/modules/cart/src/migrations/Migration20241218091938.ts @@ -0,0 +1,13 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20241218091938 extends Migration { + + async up(): Promise { + this.addSql('alter table if exists "cart_line_item" add column if not exists "is_custom_price" boolean not null default false;'); + } + + async down(): Promise { + this.addSql('alter table if exists "cart_line_item" drop column if exists "is_custom_price";'); + } + +} diff --git a/packages/modules/cart/src/models/line-item.ts b/packages/modules/cart/src/models/line-item.ts index b37a13211d148..65d842b5402d6 100644 --- a/packages/modules/cart/src/models/line-item.ts +++ b/packages/modules/cart/src/models/line-item.ts @@ -1,7 +1,7 @@ import { model } from "@medusajs/framework/utils" import Cart from "./cart" -import LineItemTaxLine from "./line-item-tax-line" import LineItemAdjustment from "./line-item-adjustment" +import LineItemTaxLine from "./line-item-tax-line" const LineItem = model .define( @@ -28,6 +28,7 @@ const LineItem = model requires_shipping: model.boolean().default(true), is_discountable: model.boolean().default(true), is_tax_inclusive: model.boolean().default(false), + is_custom_price: model.boolean().default(false), compare_at_unit_price: model.bigNumber().nullable(), unit_price: model.bigNumber(), metadata: model.json().nullable(), From c7008bb569292f0e56aecb10b86e4b2d15b96878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frane=20Poli=C4=87?= <16856471+fPolic@users.noreply.github.com> Date: Wed, 18 Dec 2024 12:56:18 +0100 Subject: [PATCH 4/5] fix: order details status (#10650) --- .../order-general-section/order-general-section.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/admin/dashboard/src/routes/orders/order-detail/components/order-general-section/order-general-section.tsx b/packages/admin/dashboard/src/routes/orders/order-detail/components/order-general-section/order-general-section.tsx index ac8f9e00970a3..e32ae907e3635 100644 --- a/packages/admin/dashboard/src/routes/orders/order-detail/components/order-general-section/order-general-section.tsx +++ b/packages/admin/dashboard/src/routes/orders/order-detail/components/order-general-section/order-general-section.tsx @@ -61,7 +61,6 @@ export const OrderGeneralSection = ({ order }: OrderGeneralSectionProps) => {
- {/*TODO: SHOW ORDER STATUS INSTEAD OF FULFILLMENT STATUS HERE - if the last fulfillment is canceled it looks like the order is canceled*/}
{ { label: t("actions.cancel"), onClick: handleCancel, + disabled: !!order.canceled_at, icon: , }, ], @@ -100,11 +100,6 @@ const FulfillmentBadge = ({ order }: { order: HttpTypes.AdminOrder }) => { const PaymentBadge = ({ order }: { order: HttpTypes.AdminOrder }) => { const { t } = useTranslation() - /** - * TODO: revisit when Order<>Payment are linked - */ - return null - const { label, color } = getOrderPaymentStatus(t, order.payment_status) return ( From 9133957a947b80d9a466727eb5c33ab5105c1db2 Mon Sep 17 00:00:00 2001 From: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> Date: Wed, 18 Dec 2024 13:59:12 +0100 Subject: [PATCH 5/5] fix(notification): Only use enabled providers for notis (#10659) --- .../notification/src/services/notification-provider.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/modules/notification/src/services/notification-provider.ts b/packages/modules/notification/src/services/notification-provider.ts index adb07b7904a5d..26fdb3f81b15c 100644 --- a/packages/modules/notification/src/services/notification-provider.ts +++ b/packages/modules/notification/src/services/notification-provider.ts @@ -68,7 +68,9 @@ export default class NotificationProviderService extends ModulesSdkUtils.MedusaI TOutput = TChannel extends string[] ? Provider[] : Provider | undefined >(channels: TChannel): Promise { if (!this.providersCache) { - const providers = await this.notificationProviderRepository_.find() + const providers = await this.notificationProviderRepository_.find({ + where: { is_enabled: true }, + }) this.providersCache = new Map( providers.flatMap((provider) =>