feat(dashboard, core-flows): associate shipping option to type (#13226)
* feat(dashboard, core-flows): allow associating shipping option type to a shipping option * edit as well * fix translation schema * fix some tests * changeset * add new test to update shipping option type * add new test to create shipping option with shipping option type * pr comments * pr comments * rename variable * make zod great again
This commit is contained in:
1 parent
b1ee204369
commit
67d3660abf
18 files changed
+525
-42
No files matched your search
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/fulfillment": patch
|
||||||
|
"@medusajs/dashboard": patch
|
||||||
|
"@medusajs/core-flows": patch
|
||||||
|
"@medusajs/types": patch
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(dashboard, core-flows): associate shipping option to type
|
||||||
@@ -17,6 +17,7 @@ medusaIntegrationTestRunner({
|
|||||||
let appContainer
|
let appContainer
|
||||||
let location
|
let location
|
||||||
let location2
|
let location2
|
||||||
|
let type
|
||||||
|
|
||||||
const shippingOptionRule = {
|
const shippingOptionRule = {
|
||||||
operator: RuleOperator.EQ,
|
operator: RuleOperator.EQ,
|
||||||
@@ -92,6 +93,17 @@ medusaIntegrationTestRunner({
|
|||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
).data.region
|
).data.region
|
||||||
|
|
||||||
|
type = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-option-types`,
|
||||||
|
{
|
||||||
|
label: "Test",
|
||||||
|
code: 'test',
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_option_type
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("GET /admin/shipping-options", () => {
|
describe("GET /admin/shipping-options", () => {
|
||||||
@@ -404,6 +416,103 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should create a shipping option successfully with the provided shipping option type", async () => {
|
||||||
|
const shippingOptionPayload = {
|
||||||
|
name: "Test shipping option",
|
||||||
|
service_zone_id: fulfillmentSet.service_zones[0].id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
provider_id: "manual_test-provider",
|
||||||
|
price_type: "flat",
|
||||||
|
type_id: type.id,
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
region_id: region.id,
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
region_id: region.id,
|
||||||
|
amount: 500,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "item_total",
|
||||||
|
operator: "gt",
|
||||||
|
value: 200,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rules: [shippingOptionRule],
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/shipping-options`,
|
||||||
|
shippingOptionPayload,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.shipping_option).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: shippingOptionPayload.name,
|
||||||
|
provider: expect.objectContaining({
|
||||||
|
id: shippingOptionPayload.provider_id,
|
||||||
|
}),
|
||||||
|
price_type: shippingOptionPayload.price_type,
|
||||||
|
type: expect.objectContaining({
|
||||||
|
id: type.id,
|
||||||
|
label: type.label,
|
||||||
|
description: type.description,
|
||||||
|
code: type.code,
|
||||||
|
}),
|
||||||
|
service_zone_id: fulfillmentSet.service_zones[0].id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
prices: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 1000,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
currency_code: "eur",
|
||||||
|
amount: 1000,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
currency_code: "eur",
|
||||||
|
amount: 500,
|
||||||
|
rules_count: 2,
|
||||||
|
price_rules: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
attribute: "item_total",
|
||||||
|
operator: "gt",
|
||||||
|
value: "200",
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
attribute: "region_id",
|
||||||
|
operator: "eq",
|
||||||
|
value: region.id,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
rules: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
operator: "eq",
|
||||||
|
attribute: "old_attr",
|
||||||
|
value: "old value",
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
it("should throw error when creating a price rule with a non white listed attribute", async () => {
|
it("should throw error when creating a price rule with a non white listed attribute", async () => {
|
||||||
const shippingOptionPayload = {
|
const shippingOptionPayload = {
|
||||||
name: "Test shipping option",
|
name: "Test shipping option",
|
||||||
@@ -552,6 +661,72 @@ medusaIntegrationTestRunner({
|
|||||||
"Providers (does-not-exist) are not enabled for the service location"
|
"Providers (does-not-exist) are not enabled for the service location"
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should throw error if both type and type_id are missing", async () => {
|
||||||
|
const shippingOptionPayload = {
|
||||||
|
name: "Test shipping option",
|
||||||
|
service_zone_id: fulfillmentSet.service_zones[0].id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
provider_id: "manual_test-provider",
|
||||||
|
price_type: "flat",
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rules: [shippingOptionRule],
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = await api
|
||||||
|
.post(
|
||||||
|
`/admin/shipping-options`,
|
||||||
|
shippingOptionPayload,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(error.response.status).toEqual(400)
|
||||||
|
expect(error.response.data.message).toEqual(
|
||||||
|
"Invalid request: Exactly one of 'type' or 'type_id' must be provided, but not both"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw error if both type and type_id are defined", async () => {
|
||||||
|
const shippingOptionPayload = {
|
||||||
|
name: "Test shipping option",
|
||||||
|
service_zone_id: fulfillmentSet.service_zones[0].id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
provider_id: "manual_test-provider",
|
||||||
|
type: {
|
||||||
|
label: "Test type",
|
||||||
|
description: "Test description",
|
||||||
|
code: "test-code",
|
||||||
|
},
|
||||||
|
type_id: "test_type_id",
|
||||||
|
price_type: "flat",
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rules: [shippingOptionRule],
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = await api
|
||||||
|
.post(
|
||||||
|
`/admin/shipping-options`,
|
||||||
|
shippingOptionPayload,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(error.response.status).toEqual(400)
|
||||||
|
expect(error.response.data.message).toEqual(
|
||||||
|
"Invalid request: Exactly one of 'type' or 'type_id' must be provided, but not both"
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("POST /admin/shipping-options/:id", () => {
|
describe("POST /admin/shipping-options/:id", () => {
|
||||||
@@ -637,7 +812,7 @@ medusaIntegrationTestRunner({
|
|||||||
],
|
],
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
// Un touched
|
// Untouched
|
||||||
id: oldAttrRule.id,
|
id: oldAttrRule.id,
|
||||||
operator: RuleOperator.EQ,
|
operator: RuleOperator.EQ,
|
||||||
attribute: "old_attr",
|
attribute: "old_attr",
|
||||||
@@ -735,6 +910,135 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should update a shipping option with a provided shipping option type successfully", async () => {
|
||||||
|
const shippingOptionPayload = {
|
||||||
|
name: "Test shipping option",
|
||||||
|
service_zone_id: fulfillmentSet.service_zones[0].id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
provider_id: "manual_test-provider",
|
||||||
|
price_type: "flat",
|
||||||
|
type: {
|
||||||
|
label: "Test type",
|
||||||
|
description: "Test description",
|
||||||
|
code: "test-code",
|
||||||
|
},
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
region_id: region.id,
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
operator: RuleOperator.EQ,
|
||||||
|
attribute: "old_attr",
|
||||||
|
value: "old value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operator: RuleOperator.EQ,
|
||||||
|
attribute: "old_attr_2",
|
||||||
|
value: "true",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/shipping-options`,
|
||||||
|
shippingOptionPayload,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const shippingOptionId = response.data.shipping_option.id
|
||||||
|
|
||||||
|
const updateResponse = await api.post(
|
||||||
|
`/admin/shipping-options/${shippingOptionId}`,
|
||||||
|
{
|
||||||
|
name: "Updated shipping option",
|
||||||
|
type_id: type.id,
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(updateResponse.status).toEqual(200)
|
||||||
|
expect(updateResponse.data.shipping_option).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: "Updated shipping option",
|
||||||
|
type: expect.objectContaining({
|
||||||
|
id: type.id,
|
||||||
|
label: type.label,
|
||||||
|
description: type.description,
|
||||||
|
code: type.code,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update a shipping option without providing shipping option type successfully", async () => {
|
||||||
|
const shippingOptionPayload = {
|
||||||
|
name: "Test shipping option",
|
||||||
|
service_zone_id: fulfillmentSet.service_zones[0].id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
provider_id: "manual_test-provider",
|
||||||
|
price_type: "flat",
|
||||||
|
type: {
|
||||||
|
label: "Test type",
|
||||||
|
description: "Test description",
|
||||||
|
code: "test-code",
|
||||||
|
},
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
region_id: region.id,
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
operator: RuleOperator.EQ,
|
||||||
|
attribute: "old_attr",
|
||||||
|
value: "old value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operator: RuleOperator.EQ,
|
||||||
|
attribute: "old_attr_2",
|
||||||
|
value: "true",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/shipping-options`,
|
||||||
|
shippingOptionPayload,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const shippingOptionId = response.data.shipping_option.id
|
||||||
|
|
||||||
|
const updateResponse = await api.post(
|
||||||
|
`/admin/shipping-options/${shippingOptionId}`,
|
||||||
|
{
|
||||||
|
name: "Updated shipping option"
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(updateResponse.status).toEqual(200)
|
||||||
|
expect(updateResponse.data.shipping_option).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: "Updated shipping option"
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
it("should throw an error when provider does not belong to service location", async () => {
|
it("should throw an error when provider does not belong to service location", async () => {
|
||||||
const shippingOptionPayload = {
|
const shippingOptionPayload = {
|
||||||
name: "Test shipping option",
|
name: "Test shipping option",
|
||||||
@@ -785,6 +1089,60 @@ medusaIntegrationTestRunner({
|
|||||||
"Providers (another_test-provider) are not enabled for the service location"
|
"Providers (another_test-provider) are not enabled for the service location"
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should throw an error when type and type_id are both defined", async () => {
|
||||||
|
const shippingOptionPayload = {
|
||||||
|
name: "Test shipping option",
|
||||||
|
service_zone_id: fulfillmentSet.service_zones[0].id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
provider_id: "manual_test-provider",
|
||||||
|
price_type: "flat",
|
||||||
|
type: {
|
||||||
|
label: "Test type",
|
||||||
|
description: "Test description",
|
||||||
|
code: "test-code",
|
||||||
|
},
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
region_id: region.id,
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rules: [shippingOptionRule],
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/shipping-options`,
|
||||||
|
shippingOptionPayload,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const shippingOptionId = response.data.shipping_option.id
|
||||||
|
|
||||||
|
const updateShippingOptionPayload = {
|
||||||
|
type: {
|
||||||
|
label: "Test type",
|
||||||
|
description: "Test description",
|
||||||
|
code: "test-code",
|
||||||
|
},
|
||||||
|
type_id: "test_type_id"
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = await api
|
||||||
|
.post(
|
||||||
|
`/admin/shipping-options/${shippingOptionId}`,
|
||||||
|
updateShippingOptionPayload,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(error.response.status).toEqual(400)
|
||||||
|
expect(error.response.data.message).toEqual("Invalid request: Only one of 'type' or 'type_id' can be provided")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("DELETE /admin/shipping-options/:id", () => {
|
describe("DELETE /admin/shipping-options/:id", () => {
|
||||||
|
|||||||
@@ -6308,6 +6308,9 @@
|
|||||||
"profile": {
|
"profile": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"fulfillmentOption": {
|
"fulfillmentOption": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@@ -6318,6 +6321,7 @@
|
|||||||
"enableInStore",
|
"enableInStore",
|
||||||
"provider",
|
"provider",
|
||||||
"profile",
|
"profile",
|
||||||
|
"type",
|
||||||
"fulfillmentOption"
|
"fulfillmentOption"
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
|
|||||||
@@ -1683,6 +1683,7 @@
|
|||||||
},
|
},
|
||||||
"provider": "Fulfillment provider",
|
"provider": "Fulfillment provider",
|
||||||
"profile": "Shipping profile",
|
"profile": "Shipping profile",
|
||||||
|
"type": "Shipping option type",
|
||||||
"fulfillmentOption": "Fulfillment option"
|
"fulfillmentOption": "Fulfillment option"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+36
-4
@@ -10,10 +10,7 @@ import { Combobox } from "../../../../../components/inputs/combobox"
|
|||||||
import { useComboboxData } from "../../../../../hooks/use-combobox-data"
|
import { useComboboxData } from "../../../../../hooks/use-combobox-data"
|
||||||
import { sdk } from "../../../../../lib/client"
|
import { sdk } from "../../../../../lib/client"
|
||||||
import { formatProvider } from "../../../../../lib/format-provider"
|
import { formatProvider } from "../../../../../lib/format-provider"
|
||||||
import {
|
import { FulfillmentSetType, ShippingOptionPriceType, } from "../../../common/constants"
|
||||||
FulfillmentSetType,
|
|
||||||
ShippingOptionPriceType,
|
|
||||||
} from "../../../common/constants"
|
|
||||||
import { CreateShippingOptionSchema } from "./schema"
|
import { CreateShippingOptionSchema } from "./schema"
|
||||||
|
|
||||||
type CreateShippingOptionDetailsFormProps = {
|
type CreateShippingOptionDetailsFormProps = {
|
||||||
@@ -49,6 +46,16 @@ export const CreateShippingOptionDetailsForm = ({
|
|||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const shippingOptionTypes = useComboboxData({
|
||||||
|
queryFn: (params) => sdk.admin.shippingOptionType.list(params),
|
||||||
|
queryKey: ["shipping_option_types"],
|
||||||
|
getOptions: (data) =>
|
||||||
|
data.shipping_option_types.map((type) => ({
|
||||||
|
label: type.label,
|
||||||
|
value: type.id,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
|
||||||
const fulfillmentProviders = useComboboxData({
|
const fulfillmentProviders = useComboboxData({
|
||||||
queryFn: (params) =>
|
queryFn: (params) =>
|
||||||
sdk.admin.fulfillmentProvider.list({
|
sdk.admin.fulfillmentProvider.list({
|
||||||
@@ -170,6 +177,31 @@ export const CreateShippingOptionDetailsForm = ({
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="shipping_option_type_id"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>
|
||||||
|
{t("stockLocations.shippingOptions.fields.type")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Combobox
|
||||||
|
{...field}
|
||||||
|
options={shippingOptionTypes.options}
|
||||||
|
searchValue={shippingOptionTypes.searchValue}
|
||||||
|
onSearchValueChange={
|
||||||
|
shippingOptionTypes.onSearchValueChange
|
||||||
|
}
|
||||||
|
disabled={shippingOptionTypes.disabled}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
|
|||||||
+1
-6
@@ -167,12 +167,7 @@ export function CreateShippingOptionsForm({
|
|||||||
operator: "eq",
|
operator: "eq",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
type: {
|
type_id: data.shipping_option_type_id,
|
||||||
// TODO: FETCH TYPES
|
|
||||||
label: "Type label",
|
|
||||||
description: "Type description",
|
|
||||||
code: "type-code",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: ({ shipping_option }) => {
|
onSuccess: ({ shipping_option }) => {
|
||||||
|
|||||||
+1
@@ -13,6 +13,7 @@ export const CreateShippingOptionDetailsSchema = z.object({
|
|||||||
shipping_profile_id: z.string().min(1),
|
shipping_profile_id: z.string().min(1),
|
||||||
provider_id: z.string().min(1),
|
provider_id: z.string().min(1),
|
||||||
fulfillment_option_id: z.string().min(1),
|
fulfillment_option_id: z.string().min(1),
|
||||||
|
shipping_option_type_id: z.string().min(1),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const ShippingOptionConditionalPriceSchema = z.object({
|
export const ShippingOptionConditionalPriceSchema = z.object({
|
||||||
|
|||||||
+43
-1
@@ -31,6 +31,7 @@ const EditShippingOptionSchema = zod.object({
|
|||||||
price_type: zod.nativeEnum(ShippingOptionPriceType),
|
price_type: zod.nativeEnum(ShippingOptionPriceType),
|
||||||
enabled_in_store: zod.boolean().optional(),
|
enabled_in_store: zod.boolean().optional(),
|
||||||
shipping_profile_id: zod.string(),
|
shipping_profile_id: zod.string(),
|
||||||
|
shipping_option_type_id: zod.string(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const EditShippingOptionForm = ({
|
export const EditShippingOptionForm = ({
|
||||||
@@ -54,12 +55,23 @@ export const EditShippingOptionForm = ({
|
|||||||
defaultValue: shippingOption.shipping_profile_id,
|
defaultValue: shippingOption.shipping_profile_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const shippingOptionTypes = useComboboxData({
|
||||||
|
queryFn: (params) => sdk.admin.shippingOptionType.list(params),
|
||||||
|
queryKey: ["shipping_option_types"],
|
||||||
|
getOptions: (data) =>
|
||||||
|
data.shipping_option_types.map((type) => ({
|
||||||
|
label: type.label,
|
||||||
|
value: type.id,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
|
||||||
const form = useForm<zod.infer<typeof EditShippingOptionSchema>>({
|
const form = useForm<zod.infer<typeof EditShippingOptionSchema>>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: shippingOption.name,
|
name: shippingOption.name,
|
||||||
price_type: shippingOption.price_type as ShippingOptionPriceType,
|
price_type: shippingOption.price_type as ShippingOptionPriceType,
|
||||||
enabled_in_store: isOptionEnabledInStore(shippingOption),
|
enabled_in_store: isOptionEnabledInStore(shippingOption),
|
||||||
shipping_profile_id: shippingOption.shipping_profile_id,
|
shipping_profile_id: shippingOption.shipping_profile_id,
|
||||||
|
shipping_option_type_id: shippingOption.type.id,
|
||||||
},
|
},
|
||||||
resolver: zodResolver(EditShippingOptionSchema),
|
resolver: zodResolver(EditShippingOptionSchema),
|
||||||
})
|
})
|
||||||
@@ -92,6 +104,7 @@ export const EditShippingOptionForm = ({
|
|||||||
price_type: values.price_type,
|
price_type: values.price_type,
|
||||||
shipping_profile_id: values.shipping_profile_id,
|
shipping_profile_id: values.shipping_profile_id,
|
||||||
rules,
|
rules,
|
||||||
|
type_id: values.shipping_option_type_id,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: ({ shipping_option }) => {
|
onSuccess: ({ shipping_option }) => {
|
||||||
@@ -111,7 +124,10 @@ export const EditShippingOptionForm = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<RouteDrawer.Form form={form}>
|
<RouteDrawer.Form form={form}>
|
||||||
<KeyboundForm onSubmit={handleSubmit} className="flex flex-1 flex-col overflow-hidden">
|
<KeyboundForm
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
className="flex flex-1 flex-col overflow-hidden"
|
||||||
|
>
|
||||||
<RouteDrawer.Body className="overflow-y-auto">
|
<RouteDrawer.Body className="overflow-y-auto">
|
||||||
<div className="flex flex-col gap-y-8">
|
<div className="flex flex-col gap-y-8">
|
||||||
<div className="flex flex-col gap-y-8">
|
<div className="flex flex-col gap-y-8">
|
||||||
@@ -200,6 +216,32 @@ export const EditShippingOptionForm = ({
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="shipping_option_type_id"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>
|
||||||
|
{t("stockLocations.shippingOptions.fields.type")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Combobox
|
||||||
|
{...field}
|
||||||
|
options={shippingOptionTypes.options}
|
||||||
|
searchValue={shippingOptionTypes.searchValue}
|
||||||
|
onSearchValueChange={
|
||||||
|
shippingOptionTypes.onSearchValueChange
|
||||||
|
}
|
||||||
|
disabled={shippingOptionTypes.disabled}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|||||||
@@ -4,12 +4,8 @@ import {
|
|||||||
ShippingOptionDTO,
|
ShippingOptionDTO,
|
||||||
UpsertShippingOptionDTO,
|
UpsertShippingOptionDTO,
|
||||||
} from "@medusajs/framework/types"
|
} from "@medusajs/framework/types"
|
||||||
import {
|
import { arrayDifference, getSelectsAndRelationsFromObjectArray, Modules, } from "@medusajs/framework/utils"
|
||||||
Modules,
|
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||||
arrayDifference,
|
|
||||||
getSelectsAndRelationsFromObjectArray,
|
|
||||||
} from "@medusajs/framework/utils"
|
|
||||||
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The data to create or update shipping options.
|
* The data to create or update shipping options.
|
||||||
@@ -65,7 +61,13 @@ export const upsertShippingOptionsStep = createStep(
|
|||||||
|
|
||||||
const upsertedShippingOptions: ShippingOptionDTO[] =
|
const upsertedShippingOptions: ShippingOptionDTO[] =
|
||||||
await fulfillmentService.upsertShippingOptions(
|
await fulfillmentService.upsertShippingOptions(
|
||||||
input as UpsertShippingOptionDTO[]
|
input.map(inputItem => {
|
||||||
|
const upsertShippingOption = inputItem as UpsertShippingOptionDTO
|
||||||
|
if (inputItem.type_id) {
|
||||||
|
upsertShippingOption.type = inputItem.type_id
|
||||||
|
}
|
||||||
|
return upsertShippingOption;
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const upsertedShippingOptionIds = upsertedShippingOptions.map((s) => s.id)
|
const upsertedShippingOptionIds = upsertedShippingOptions.map((s) => s.id)
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { FulfillmentWorkflow } from "@medusajs/framework/types"
|
import { FulfillmentWorkflow } from "@medusajs/framework/types"
|
||||||
import {
|
import { MedusaError, Modules, ShippingOptionPriceType, } from "@medusajs/framework/utils"
|
||||||
MedusaError,
|
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||||
Modules,
|
import { CreateShippingOptionDTO } from "@medusajs/types"
|
||||||
ShippingOptionPriceType,
|
|
||||||
} from "@medusajs/framework/utils"
|
|
||||||
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The data to validate shipping option prices.
|
* The data to validate shipping option prices.
|
||||||
@@ -96,7 +93,7 @@ export const validateShippingOptionPricesStep = createStep(
|
|||||||
|
|
||||||
const validation =
|
const validation =
|
||||||
await fulfillmentModuleService.validateShippingOptionsForPriceCalculation(
|
await fulfillmentModuleService.validateShippingOptionsForPriceCalculation(
|
||||||
calculatedOptions as FulfillmentWorkflow.CreateShippingOptionsWorkflowInput[]
|
calculatedOptions as CreateShippingOptionDTO[]
|
||||||
)
|
)
|
||||||
|
|
||||||
if (validation.some((v) => !v)) {
|
if (validation.some((v) => !v)) {
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ import {
|
|||||||
WorkflowData,
|
WorkflowData,
|
||||||
WorkflowResponse,
|
WorkflowResponse,
|
||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
import {
|
import { createShippingOptionsPriceSetsStep, upsertShippingOptionsStep, } from "../steps"
|
||||||
createShippingOptionsPriceSetsStep,
|
|
||||||
upsertShippingOptionsStep,
|
|
||||||
} from "../steps"
|
|
||||||
import { setShippingOptionsPriceSetsStep } from "../steps/set-shipping-options-price-sets"
|
import { setShippingOptionsPriceSetsStep } from "../steps/set-shipping-options-price-sets"
|
||||||
import { validateFulfillmentProvidersStep } from "../steps/validate-fulfillment-providers"
|
import { validateFulfillmentProvidersStep } from "../steps/validate-fulfillment-providers"
|
||||||
import { validateShippingOptionPricesStep } from "../steps/validate-shipping-option-prices"
|
import { validateShippingOptionPricesStep } from "../steps/validate-shipping-option-prices"
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { NextFunction } from "express"
|
import { NextFunction } from "express"
|
||||||
import { MedusaRequest, MedusaResponse } from "../types"
|
import { MedusaRequest, MedusaResponse } from "../types"
|
||||||
import { zodValidator } from "../../zod/zod-helpers"
|
import { zodValidator } from "../../zod"
|
||||||
|
|
||||||
export function validateAndTransformBody(
|
export function validateAndTransformBody(
|
||||||
zodSchema:
|
zodSchema:
|
||||||
| z.ZodObject<any, any>
|
| z.ZodObject<any, any>
|
||||||
|
| z.ZodEffects<any, any>
|
||||||
| ((
|
| ((
|
||||||
customSchema?: z.ZodOptional<z.ZodNullable<z.ZodObject<any, any>>>
|
customSchema?: z.ZodOptional<z.ZodNullable<z.ZodObject<any, any>>>
|
||||||
) => z.ZodObject<any, any> | z.ZodEffects<any, any>)
|
) => z.ZodObject<any, any> | z.ZodEffects<any, any>)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export interface CreateShippingOptionDTO {
|
|||||||
/**
|
/**
|
||||||
* The shipping option type associated with the shipping option.
|
* The shipping option type associated with the shipping option.
|
||||||
*/
|
*/
|
||||||
type: CreateShippingOptionTypeDTO
|
type: CreateShippingOptionTypeDTO | string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The data necessary for the associated fulfillment provider to process the shipping option
|
* The data necessary for the associated fulfillment provider to process the shipping option
|
||||||
@@ -87,7 +87,7 @@ export interface UpdateShippingOptionDTO {
|
|||||||
* The shipping option type associated with the shipping option.
|
* The shipping option type associated with the shipping option.
|
||||||
*/
|
*/
|
||||||
type?:
|
type?:
|
||||||
| Omit<CreateShippingOptionTypeDTO, "shipping_option_id">
|
| CreateShippingOptionTypeDTO | string
|
||||||
| {
|
| {
|
||||||
/**
|
/**
|
||||||
* The ID of the shipping option type.
|
* The ID of the shipping option type.
|
||||||
|
|||||||
@@ -156,7 +156,14 @@ export interface AdminCreateShippingOption {
|
|||||||
* Learn more in the [Shipping Option](https://docs.medusajs.com/resources/commerce-modules/fulfillment/shipping-option#shipping-profile-and-types)
|
* Learn more in the [Shipping Option](https://docs.medusajs.com/resources/commerce-modules/fulfillment/shipping-option#shipping-profile-and-types)
|
||||||
* documentation.
|
* documentation.
|
||||||
*/
|
*/
|
||||||
type: AdminCreateShippingOptionType
|
type?: AdminCreateShippingOptionType
|
||||||
|
/**
|
||||||
|
* The ID of the type of shipping option.
|
||||||
|
*
|
||||||
|
* Learn more in the [Shipping Option](https://docs.medusajs.com/resources/commerce-modules/fulfillment/shipping-option#shipping-profile-and-types)
|
||||||
|
* documentation.
|
||||||
|
*/
|
||||||
|
type_id?: string
|
||||||
/**
|
/**
|
||||||
* The prices of the shipping option.
|
* The prices of the shipping option.
|
||||||
*/
|
*/
|
||||||
@@ -254,6 +261,13 @@ export interface AdminUpdateShippingOption {
|
|||||||
* documentation.
|
* documentation.
|
||||||
*/
|
*/
|
||||||
type?: AdminCreateShippingOptionType
|
type?: AdminCreateShippingOptionType
|
||||||
|
/**
|
||||||
|
* The ID of the type of shipping option.
|
||||||
|
*
|
||||||
|
* Learn more in the [Shipping Option](https://docs.medusajs.com/resources/commerce-modules/fulfillment/shipping-option#shipping-profile-and-types)
|
||||||
|
* documentation.
|
||||||
|
*/
|
||||||
|
type_id?: string
|
||||||
/**
|
/**
|
||||||
* The prices of the shipping option.
|
* The prices of the shipping option.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ type CreateFlatShippingOptionInputBase = {
|
|||||||
/**
|
/**
|
||||||
* The type of the shipping option.
|
* The type of the shipping option.
|
||||||
*/
|
*/
|
||||||
type: {
|
type?: {
|
||||||
/**
|
/**
|
||||||
* The label of the shipping option type.
|
* The label of the shipping option type.
|
||||||
*/
|
*/
|
||||||
@@ -53,6 +53,10 @@ type CreateFlatShippingOptionInputBase = {
|
|||||||
*/
|
*/
|
||||||
code: string
|
code: string
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* The ID of the type of shipping option.
|
||||||
|
*/
|
||||||
|
type_id?: string
|
||||||
/**
|
/**
|
||||||
* The rules that determine when the shipping option is available.
|
* The rules that determine when the shipping option is available.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ type UpdateFlatShippingOptionInputBase = {
|
|||||||
*/
|
*/
|
||||||
code: string
|
code: string
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* The ID of the type of shipping option.
|
||||||
|
*/
|
||||||
|
type_id?: string
|
||||||
/**
|
/**
|
||||||
* The rules that determine when the shipping option is available.
|
* The rules that determine when the shipping option is available.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
createOperatorMap,
|
createOperatorMap,
|
||||||
createSelectParams,
|
createSelectParams,
|
||||||
} from "../../utils/validators"
|
} from "../../utils/validators"
|
||||||
|
import { isDefined } from "@medusajs/utils"
|
||||||
|
|
||||||
export type AdminGetShippingOptionParamsType = z.infer<
|
export type AdminGetShippingOptionParamsType = z.infer<
|
||||||
typeof AdminGetShippingOptionParams
|
typeof AdminGetShippingOptionParams
|
||||||
@@ -127,9 +128,6 @@ export const AdminUpdateShippingOptionPriceWithRegion = z
|
|||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
|
|
||||||
export type AdminCreateShippingOptionType = z.infer<
|
|
||||||
typeof AdminCreateShippingOption
|
|
||||||
>
|
|
||||||
export const AdminCreateShippingOption = z
|
export const AdminCreateShippingOption = z
|
||||||
.object({
|
.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
@@ -138,13 +136,19 @@ export const AdminCreateShippingOption = z
|
|||||||
data: z.record(z.unknown()).optional(),
|
data: z.record(z.unknown()).optional(),
|
||||||
price_type: z.nativeEnum(ShippingOptionPriceTypeEnum),
|
price_type: z.nativeEnum(ShippingOptionPriceTypeEnum),
|
||||||
provider_id: z.string(),
|
provider_id: z.string(),
|
||||||
type: AdminCreateShippingOptionTypeObject,
|
type: AdminCreateShippingOptionTypeObject.optional(),
|
||||||
|
type_id: z.string().optional(),
|
||||||
prices: AdminCreateShippingOptionPriceWithCurrency.or(
|
prices: AdminCreateShippingOptionPriceWithCurrency.or(
|
||||||
AdminCreateShippingOptionPriceWithRegion
|
AdminCreateShippingOptionPriceWithRegion
|
||||||
).array(),
|
).array(),
|
||||||
rules: AdminCreateShippingOptionRule.array().optional(),
|
rules: AdminCreateShippingOptionRule.array().optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
|
.refine((data) => isDefined(data.type_id) !== isDefined(data.type), {
|
||||||
|
message:
|
||||||
|
"Exactly one of 'type' or 'type_id' must be provided, but not both",
|
||||||
|
path: ["type_id", "type"],
|
||||||
|
})
|
||||||
|
|
||||||
export type AdminUpdateShippingOptionType = z.infer<
|
export type AdminUpdateShippingOptionType = z.infer<
|
||||||
typeof AdminUpdateShippingOption
|
typeof AdminUpdateShippingOption
|
||||||
@@ -157,6 +161,7 @@ export const AdminUpdateShippingOption = z
|
|||||||
provider_id: z.string().optional(),
|
provider_id: z.string().optional(),
|
||||||
shipping_profile_id: z.string().optional(),
|
shipping_profile_id: z.string().optional(),
|
||||||
type: AdminCreateShippingOptionTypeObject.optional(),
|
type: AdminCreateShippingOptionTypeObject.optional(),
|
||||||
|
type_id: z.string().optional(),
|
||||||
prices: AdminUpdateShippingOptionPriceWithCurrency.or(
|
prices: AdminUpdateShippingOptionPriceWithCurrency.or(
|
||||||
AdminUpdateShippingOptionPriceWithRegion
|
AdminUpdateShippingOptionPriceWithRegion
|
||||||
)
|
)
|
||||||
@@ -167,3 +172,19 @@ export const AdminUpdateShippingOption = z
|
|||||||
.optional(),
|
.optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
|
.refine(
|
||||||
|
(data) => {
|
||||||
|
const hasType = isDefined(data.type)
|
||||||
|
const hasTypeId = isDefined(data.type_id)
|
||||||
|
|
||||||
|
if (!hasType && !hasTypeId) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasType !== hasTypeId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "Only one of 'type' or 'type_id' can be provided",
|
||||||
|
path: ["type_id", "type"],
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -59,6 +59,7 @@ import { joinerConfig } from "../joiner-config"
|
|||||||
import { UpdateShippingOptionsInput } from "../types/service"
|
import { UpdateShippingOptionsInput } from "../types/service"
|
||||||
import { buildCreatedShippingOptionEvents } from "../utils/events"
|
import { buildCreatedShippingOptionEvents } from "../utils/events"
|
||||||
import FulfillmentProviderService from "./fulfillment-provider"
|
import FulfillmentProviderService from "./fulfillment-provider"
|
||||||
|
import { isObject } from "@medusajs/utils"
|
||||||
|
|
||||||
const generateMethodForModels = {
|
const generateMethodForModels = {
|
||||||
FulfillmentSet,
|
FulfillmentSet,
|
||||||
@@ -1408,9 +1409,9 @@ export default class FulfillmentModuleService
|
|||||||
dataArray.forEach((shippingOption) => {
|
dataArray.forEach((shippingOption) => {
|
||||||
const existingShippingOption = existingShippingOptions.get(
|
const existingShippingOption = existingShippingOptions.get(
|
||||||
shippingOption.id
|
shippingOption.id
|
||||||
)! // Garuantueed to exist since the validation above have been performed
|
)! // Guaranteed to exist since the validation above have been performed
|
||||||
|
|
||||||
if (shippingOption.type && !("id" in shippingOption.type)) {
|
if (isObject(shippingOption.type) && !("id" in shippingOption.type)) {
|
||||||
optionTypeDeletedIds.push(existingShippingOption.type.id)
|
optionTypeDeletedIds.push(existingShippingOption.type.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1534,7 +1535,7 @@ export default class FulfillmentModuleService
|
|||||||
const createdOptionTypeIds = updatedShippingOptions
|
const createdOptionTypeIds = updatedShippingOptions
|
||||||
.filter((so) => {
|
.filter((so) => {
|
||||||
const updateData = shippingOptionsData.find((sod) => sod.id === so.id)
|
const updateData = shippingOptionsData.find((sod) => sod.id === so.id)
|
||||||
return updateData?.type && !("id" in updateData.type)
|
return isObject(updateData?.type) && !("id" in updateData.type)
|
||||||
})
|
})
|
||||||
.map((so) => so.type.id)
|
.map((so) => so.type.id)
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user