feat(core-flows, dashboard, medusa, types): optional shipping profile (#11434)
* feat: create product flow changes * feat: allow unsetting SP on product update * feat: update prepare line item helper * test: add testcase * wip: fix tests * fix: update module tests * fix: cart module test
This commit is contained in:
@@ -1123,6 +1123,102 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
})
|
||||
|
||||
it("should successfully complete cart without shipping for digital products", async () => {
|
||||
/**
|
||||
* Product has a shipping profile so cart item should not require shipping
|
||||
*/
|
||||
const product = (
|
||||
await api.post(
|
||||
`/admin/products`,
|
||||
{
|
||||
title: "Product without inventory management",
|
||||
description: "test",
|
||||
options: [
|
||||
{
|
||||
title: "Size",
|
||||
values: ["S", "M", "L", "XL"],
|
||||
},
|
||||
],
|
||||
variants: [
|
||||
{
|
||||
title: "S / Black",
|
||||
sku: "special-shirt",
|
||||
options: {
|
||||
Size: "S",
|
||||
},
|
||||
manage_inventory: false,
|
||||
prices: [
|
||||
{
|
||||
amount: 1500,
|
||||
currency_code: "usd",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.product
|
||||
|
||||
let cart = (
|
||||
await api.post(
|
||||
`/store/carts`,
|
||||
{
|
||||
currency_code: "usd",
|
||||
sales_channel_id: salesChannel.id,
|
||||
region_id: region.id,
|
||||
shipping_address: shippingAddressData,
|
||||
},
|
||||
storeHeadersWithCustomer
|
||||
)
|
||||
).data.cart
|
||||
|
||||
cart = (
|
||||
await api.post(
|
||||
`/store/carts/${cart.id}/line-items`,
|
||||
{
|
||||
variant_id: product.variants[0].id,
|
||||
quantity: 1,
|
||||
},
|
||||
storeHeaders
|
||||
)
|
||||
).data.cart
|
||||
|
||||
const paymentCollection = (
|
||||
await api.post(
|
||||
`/store/payment-collections`,
|
||||
{ cart_id: cart.id },
|
||||
storeHeaders
|
||||
)
|
||||
).data.payment_collection
|
||||
|
||||
await api.post(
|
||||
`/store/payment-collections/${paymentCollection.id}/payment-sessions`,
|
||||
{ provider_id: "pp_system_default" },
|
||||
storeHeaders
|
||||
)
|
||||
|
||||
expect(cart.items[0].requires_shipping).toEqual(false)
|
||||
|
||||
const response = await api.post(
|
||||
`/store/carts/${cart.id}/complete`,
|
||||
{},
|
||||
storeHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.order).toEqual(
|
||||
expect.objectContaining({
|
||||
shipping_methods: [],
|
||||
items: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
requires_shipping: false,
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
describe("with sale price lists", () => {
|
||||
let priceList
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ export async function createOrderSeeder({
|
||||
"/admin/products",
|
||||
{
|
||||
title: `Test fixture ${shippingProfile.id}`,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
shipping_profile_id: withoutShipping ? undefined : shippingProfile.id,
|
||||
options: [
|
||||
{ title: "size", values: ["large", "small"] },
|
||||
{ title: "color", values: ["green"] },
|
||||
|
||||
@@ -668,7 +668,6 @@ medusaIntegrationTestRunner({
|
||||
"/admin/products",
|
||||
{
|
||||
title: `Test fixture 2`,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [
|
||||
{ title: "size", values: ["large", "small"] },
|
||||
{ title: "color", values: ["green"] },
|
||||
|
||||
@@ -990,7 +990,7 @@ medusaIntegrationTestRunner({
|
||||
is_tax_inclusive: true,
|
||||
is_custom_price: false,
|
||||
quantity: 1,
|
||||
requires_shipping: true,
|
||||
requires_shipping: false, // product doesn't have a shipping profile nor inventory items that require shipping
|
||||
subtitle: "Test product",
|
||||
title: "Test variant",
|
||||
unit_price: 3000,
|
||||
@@ -1006,7 +1006,7 @@ medusaIntegrationTestRunner({
|
||||
metadata: {
|
||||
foo: "bar",
|
||||
},
|
||||
requires_shipping: true,
|
||||
requires_shipping: true, // overriden when adding to cart
|
||||
subtitle: "Test subtitle",
|
||||
thumbnail: "some-url",
|
||||
title: "Test item",
|
||||
@@ -1040,7 +1040,7 @@ medusaIntegrationTestRunner({
|
||||
is_tax_inclusive: false,
|
||||
is_custom_price: false,
|
||||
quantity: 1,
|
||||
requires_shipping: true,
|
||||
requires_shipping: false,
|
||||
subtitle: "Test product",
|
||||
title: "Test variant",
|
||||
unit_price: 2000,
|
||||
|
||||
@@ -1218,7 +1218,6 @@ medusaIntegrationTestRunner({
|
||||
"/admin/products",
|
||||
{
|
||||
title: "Test fixture",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [
|
||||
{ title: "size", values: ["large", "small"] },
|
||||
{ title: "color", values: ["green"] },
|
||||
|
||||
@@ -57,6 +57,7 @@ interface ComboboxProps<T extends Value = Value>
|
||||
isFetchingNextPage?: boolean
|
||||
onCreateOption?: (value: string) => void
|
||||
noResultsPlaceholder?: ReactNode
|
||||
allowClear?: boolean
|
||||
}
|
||||
|
||||
const ComboboxImpl = <T extends Value = string>(
|
||||
@@ -72,6 +73,7 @@ const ComboboxImpl = <T extends Value = string>(
|
||||
isFetchingNextPage,
|
||||
onCreateOption,
|
||||
noResultsPlaceholder,
|
||||
allowClear,
|
||||
...inputProps
|
||||
}: ComboboxProps<T>,
|
||||
ref: ForwardedRef<HTMLInputElement>
|
||||
@@ -303,6 +305,18 @@ const ComboboxImpl = <T extends Value = string>(
|
||||
{...inputProps}
|
||||
/>
|
||||
</div>
|
||||
{allowClear && controlledValue && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
handleValueChange(undefined)
|
||||
}}
|
||||
className="bg-ui-bg-base hover:bg-ui-bg-base-hover txt-compact-small-plus text-ui-fg-subtle focus-within:border-ui-fg-interactive transition-fg absolute right-[28px] top-0.5 z-[1] flex h-[28px] items-center rounded-[4px] border px-1.5 py-[2px] outline-none"
|
||||
>
|
||||
<XMarkMini className="text-ui-fg-muted" />
|
||||
</button>
|
||||
)}
|
||||
<PrimitiveComboboxDisclosure
|
||||
render={(props) => {
|
||||
return (
|
||||
|
||||
-9
@@ -181,15 +181,6 @@ export const ProductCreateForm = ({
|
||||
}
|
||||
|
||||
if (currentTab === Tab.ORGANIZE) {
|
||||
// TODO: this is temp until we add partial validation per tab
|
||||
if (!form.getValues("shipping_profile_id")) {
|
||||
form.setError("shipping_profile_id", {
|
||||
type: "required",
|
||||
message: t("products.shippingProfile.create.errors.required"),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
setTab(Tab.VARIANTS)
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -173,7 +173,9 @@ export const ProductCreateOrganizationSection = ({
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<Form.Label>{t("products.fields.shipping_profile.label")}</Form.Label>
|
||||
<Form.Label optional>
|
||||
{t("products.fields.shipping_profile.label")}
|
||||
</Form.Label>
|
||||
<Form.Hint>
|
||||
<Trans i18nKey={"products.fields.shipping_profile.hint"} />
|
||||
</Form.Hint>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from "zod"
|
||||
import { i18n } from "../../../components/utilities/i18n/i18n.tsx"
|
||||
import { optionalFloat, optionalInt } from "../../../lib/validation.ts"
|
||||
import { decorateVariantsWithDefaultValues } from "./utils.ts"
|
||||
import { i18n } from "../../../components/utilities/i18n/i18n"
|
||||
import { optionalFloat, optionalInt } from "../../../lib/validation"
|
||||
import { decorateVariantsWithDefaultValues } from "./utils"
|
||||
|
||||
export const MediaSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
@@ -64,7 +64,7 @@ export const ProductCreateSchema = z
|
||||
discountable: z.boolean(),
|
||||
type_id: z.string().optional(),
|
||||
collection_id: z.string().optional(),
|
||||
shipping_profile_id: z.string(), // TODO: require min(1) when partial validation per tab is added
|
||||
shipping_profile_id: z.string().optional(),
|
||||
categories: z.array(z.string()),
|
||||
tags: z.array(z.string()).optional(),
|
||||
sales_channels: z
|
||||
|
||||
@@ -24,7 +24,7 @@ export const normalizeProductFormValues = (
|
||||
: undefined,
|
||||
images,
|
||||
collection_id: values.collection_id || undefined,
|
||||
shipping_profile_id: values.shipping_profile_id,
|
||||
shipping_profile_id: values.shipping_profile_id || undefined,
|
||||
categories: values.categories.map((id) => ({ id })),
|
||||
type_id: values.type_id || undefined,
|
||||
handle: values.handle || undefined,
|
||||
|
||||
+13
-2
@@ -7,12 +7,13 @@ import { Form } from "../../../../../components/common/form"
|
||||
import { Combobox } from "../../../../../components/inputs/combobox"
|
||||
import { RouteDrawer, useRouteModal } from "../../../../../components/modals"
|
||||
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
||||
import { useExtendableForm } from "../../../../../extensions"
|
||||
import { useUpdateProduct } from "../../../../../hooks/api/products"
|
||||
import { useComboboxData } from "../../../../../hooks/use-combobox-data"
|
||||
import { sdk } from "../../../../../lib/client"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { se } from "date-fns/locale"
|
||||
import { useEffect } from "react"
|
||||
|
||||
type ProductShippingProfileFormProps = {
|
||||
product: HttpTypes.AdminProduct & {
|
||||
@@ -47,12 +48,15 @@ export const ProductShippingProfileForm = ({
|
||||
resolver: zodResolver(ProductShippingProfileSchema),
|
||||
})
|
||||
|
||||
const selectedShippingProfile = form.watch("shipping_profile_id")
|
||||
|
||||
const { mutateAsync, isPending } = useUpdateProduct(product.id)
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
await mutateAsync(
|
||||
{
|
||||
shipping_profile_id: data.shipping_profile_id,
|
||||
shipping_profile_id:
|
||||
data.shipping_profile_id === "" ? null : data.shipping_profile_id,
|
||||
},
|
||||
{
|
||||
onSuccess: ({ product }) => {
|
||||
@@ -70,6 +74,12 @@ export const ProductShippingProfileForm = ({
|
||||
)
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof selectedShippingProfile === "undefined") {
|
||||
form.setValue("shipping_profile_id", "")
|
||||
}
|
||||
}, [selectedShippingProfile])
|
||||
|
||||
return (
|
||||
<RouteDrawer.Form form={form}>
|
||||
<KeyboundForm onSubmit={handleSubmit} className="flex h-full flex-col">
|
||||
@@ -87,6 +97,7 @@ export const ProductShippingProfileForm = ({
|
||||
<Form.Control>
|
||||
<Combobox
|
||||
{...field}
|
||||
allowClear
|
||||
options={shippingProfiles.options}
|
||||
searchValue={shippingProfiles.searchValue}
|
||||
onSearchValueChange={
|
||||
|
||||
@@ -147,6 +147,7 @@ export const productVariantsFields = [
|
||||
"product.collection.title",
|
||||
"product.handle",
|
||||
"product.discountable",
|
||||
"product.shipping_profile.id",
|
||||
"calculated_price.*",
|
||||
"inventory_items.inventory_item_id",
|
||||
"inventory_items.required_quantity",
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
InventoryItemDTO,
|
||||
LineItemAdjustmentDTO,
|
||||
LineItemTaxLineDTO,
|
||||
ProductDTO,
|
||||
ProductVariantDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
@@ -50,6 +51,10 @@ interface PrepareItemLineItemInput {
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
type AddItemProductDTO = ProductDTO & {
|
||||
shipping_profile: { id: string }
|
||||
}
|
||||
|
||||
export interface PrepareVariantLineItemInput extends ProductVariantDTO {
|
||||
inventory_items: { inventory: InventoryItemDTO }[]
|
||||
calculated_price: {
|
||||
@@ -106,17 +111,19 @@ export function prepareLineItemData(data: PrepareLineItemDataInput) {
|
||||
compareAtUnitPrice = variant.calculated_price.original_amount
|
||||
}
|
||||
|
||||
// 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 someInventoryRequiresShipping = variant?.inventory_items?.length
|
||||
? variant.inventory_items.some(
|
||||
(inventoryItem) => !!inventoryItem.inventory.requires_shipping
|
||||
)
|
||||
: true
|
||||
const hasShippingProfile = isDefined(
|
||||
(variant?.product as AddItemProductDTO)?.shipping_profile?.id
|
||||
)
|
||||
|
||||
const someInventoryRequiresShipping = !!variant?.inventory_items?.some(
|
||||
(inventoryItem) => !!inventoryItem.inventory.requires_shipping
|
||||
)
|
||||
|
||||
// Note: If any of the items require shipping or product has a shipping profile set,
|
||||
// we enable fulfillment unless explicitly set to not require shipping by the item in the request
|
||||
const requiresShipping = isDefined(item?.requires_shipping)
|
||||
? item.requires_shipping
|
||||
: someInventoryRequiresShipping
|
||||
: hasShippingProfile || someInventoryRequiresShipping
|
||||
|
||||
let lineItem: any = {
|
||||
quantity: item?.quantity,
|
||||
|
||||
@@ -35,12 +35,12 @@ const cartFields = ["completed_at"].concat(cartFieldsForPricingContext)
|
||||
|
||||
export const addToCartWorkflowId = "add-to-cart"
|
||||
/**
|
||||
* This workflow adds a product variant to a cart as a line item. It's executed by the
|
||||
* This workflow adds a product variant to a cart as a line item. It's executed by the
|
||||
* [Add Line Item Store API Route](https://docs.medusajs.com/api/store#carts_postcartsidlineitems).
|
||||
*
|
||||
*
|
||||
* You can use this workflow within your own customizations or custom workflows, allowing you to wrap custom logic around adding an item to the cart.
|
||||
* For example, you can use this workflow to add a line item to the cart with a custom price.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const { result } = await addToCartWorkflow(container)
|
||||
* .run({
|
||||
@@ -59,11 +59,11 @@ export const addToCartWorkflowId = "add-to-cart"
|
||||
* ]
|
||||
* }
|
||||
* })
|
||||
*
|
||||
*
|
||||
* @summary
|
||||
*
|
||||
*
|
||||
* Add a line item to a cart.
|
||||
*
|
||||
*
|
||||
* @property hooks.validate - This hook is executed before all operations. You can consume this hook to perform any custom validation. If validation fails, you can throw an error to stop the workflow execution.
|
||||
*/
|
||||
export const addToCartWorkflow = createWorkflow(
|
||||
|
||||
@@ -19,11 +19,7 @@ import {
|
||||
transform,
|
||||
createStep,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import {
|
||||
createRemoteLinkStep,
|
||||
emitEventStep,
|
||||
useQueryGraphStep,
|
||||
} from "../../common"
|
||||
import { createRemoteLinkStep, emitEventStep } from "../../common"
|
||||
import { associateProductsWithSalesChannelsStep } from "../../sales-channel"
|
||||
import { createProductsStep } from "../steps/create-products"
|
||||
import { createProductVariantsWorkflow } from "./create-product-variants"
|
||||
@@ -36,16 +32,6 @@ export interface ValidateProductInputStepInput {
|
||||
* The products to validate.
|
||||
*/
|
||||
products: Omit<CreateProductWorkflowInputDTO, "sales_channels">[]
|
||||
|
||||
/**
|
||||
* The shipping profiles to validate.
|
||||
*/
|
||||
shippingProfiles: {
|
||||
/**
|
||||
* The shipping profile's ID.
|
||||
*/
|
||||
id: string
|
||||
}[]
|
||||
}
|
||||
|
||||
const validateProductInputStepId = "validate-product-input"
|
||||
@@ -87,7 +73,7 @@ const validateProductInputStepId = "validate-product-input"
|
||||
export const validateProductInputStep = createStep(
|
||||
validateProductInputStepId,
|
||||
async (data: ValidateProductInputStepInput) => {
|
||||
const { products, shippingProfiles } = data
|
||||
const { products } = data
|
||||
|
||||
const missingOptionsProductTitles = products
|
||||
.filter((product) => !product.options?.length)
|
||||
@@ -101,25 +87,6 @@ export const validateProductInputStep = createStep(
|
||||
)}].`
|
||||
)
|
||||
}
|
||||
|
||||
const existingProfileIds = new Set(shippingProfiles.map((p) => p.id))
|
||||
|
||||
const missingShippingProfileProductTitles = products
|
||||
.filter(
|
||||
(product) =>
|
||||
!product.shipping_profile_id ||
|
||||
!existingProfileIds.has(product.shipping_profile_id)
|
||||
)
|
||||
.map((product) => product.title)
|
||||
|
||||
if (missingShippingProfileProductTitles.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Shipping profile is not provided for: [${missingShippingProfileProductTitles.join(
|
||||
", "
|
||||
)}].`
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -190,14 +157,10 @@ export const createProductsWorkflow = createWorkflow(
|
||||
createProductsWorkflowId,
|
||||
(input: WorkflowData<CreateProductsWorkflowInput>) => {
|
||||
// Passing prices to the product module will fail, we want to keep them for after the product is created.
|
||||
const { products: productWithoutExternalRelations, shippingPorfileIds } =
|
||||
transform({ input }, (data) => {
|
||||
const shippingPorfileIds: string[] = []
|
||||
const { products: productWithoutExternalRelations } = transform(
|
||||
{ input },
|
||||
(data) => {
|
||||
const productsData = data.input.products.map((p) => {
|
||||
if (p.shipping_profile_id) {
|
||||
shippingPorfileIds.push(p.shipping_profile_id)
|
||||
}
|
||||
|
||||
return {
|
||||
...p,
|
||||
sales_channels: undefined,
|
||||
@@ -206,18 +169,11 @@ export const createProductsWorkflow = createWorkflow(
|
||||
}
|
||||
})
|
||||
|
||||
return { products: productsData, shippingPorfileIds }
|
||||
})
|
||||
return { products: productsData }
|
||||
}
|
||||
)
|
||||
|
||||
const { data: shippingProfiles } = useQueryGraphStep({
|
||||
entity: "shipping_profile",
|
||||
fields: ["id"],
|
||||
filters: {
|
||||
id: shippingPorfileIds,
|
||||
},
|
||||
})
|
||||
|
||||
validateProductInputStep({ products: input.products, shippingProfiles })
|
||||
validateProductInputStep({ products: input.products })
|
||||
|
||||
const createdProducts = createProductsStep(productWithoutExternalRelations)
|
||||
|
||||
@@ -240,16 +196,18 @@ export const createProductsWorkflow = createWorkflow(
|
||||
const shippingProfileLinks = transform(
|
||||
{ input, createdProducts },
|
||||
(data) => {
|
||||
return data.createdProducts.map((createdProduct, i) => {
|
||||
return {
|
||||
[Modules.PRODUCT]: {
|
||||
product_id: createdProduct.id,
|
||||
},
|
||||
[Modules.FULFILLMENT]: {
|
||||
shipping_profile_id: data.input.products[i].shipping_profile_id,
|
||||
},
|
||||
}
|
||||
})
|
||||
return data.createdProducts
|
||||
.map((createdProduct, i) => {
|
||||
return {
|
||||
[Modules.PRODUCT]: {
|
||||
product_id: createdProduct.id,
|
||||
},
|
||||
[Modules.FULFILLMENT]: {
|
||||
shipping_profile_id: data.input.products[i].shipping_profile_id,
|
||||
},
|
||||
}
|
||||
})
|
||||
.filter((link) => !!link[Modules.FULFILLMENT].shipping_profile_id)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Modules,
|
||||
ProductWorkflowEvents,
|
||||
arrayDifference,
|
||||
isDefined,
|
||||
} from "@medusajs/framework/utils"
|
||||
import {
|
||||
WorkflowData,
|
||||
@@ -50,7 +51,7 @@ export type UpdateProductsWorkflowInputSelector = {
|
||||
/**
|
||||
* The shipping profile to set.
|
||||
*/
|
||||
shipping_profile_id?: string
|
||||
shipping_profile_id?: string | null
|
||||
}
|
||||
} & AdditionalData
|
||||
|
||||
@@ -73,7 +74,7 @@ export type UpdateProductsWorkflowInputProducts = {
|
||||
/**
|
||||
* The shipping profile to set.
|
||||
*/
|
||||
shipping_profile_id?: string
|
||||
shipping_profile_id?: string | null
|
||||
})[]
|
||||
} & AdditionalData
|
||||
|
||||
@@ -152,12 +153,12 @@ function findProductsWithShippingProfiles({
|
||||
|
||||
if ("products" in input) {
|
||||
const discardedProductIds: string[] = input.products
|
||||
.filter((p) => !p.shipping_profile_id)
|
||||
.filter((p) => !isDefined(p.shipping_profile_id))
|
||||
.map((p) => p.id as string)
|
||||
return arrayDifference(productIds, discardedProductIds)
|
||||
}
|
||||
|
||||
return !input.update?.shipping_profile_id ? [] : productIds
|
||||
return !isDefined(input.update?.shipping_profile_id) ? [] : productIds
|
||||
}
|
||||
|
||||
function prepareSalesChannelLinks({
|
||||
@@ -215,7 +216,7 @@ function prepareShippingProfileLinks({
|
||||
}
|
||||
|
||||
return input.products
|
||||
.filter((p) => p.shipping_profile_id)
|
||||
.filter((p) => typeof p.shipping_profile_id === "string")
|
||||
.map((p) => ({
|
||||
[Modules.PRODUCT]: {
|
||||
product_id: p.id,
|
||||
@@ -226,7 +227,7 @@ function prepareShippingProfileLinks({
|
||||
}))
|
||||
}
|
||||
|
||||
if (input.selector && input.update?.shipping_profile_id) {
|
||||
if (input.selector && typeof input.update?.shipping_profile_id === "string") {
|
||||
return updatedProducts.map((p) => ({
|
||||
[Modules.PRODUCT]: {
|
||||
product_id: p.id,
|
||||
|
||||
@@ -213,7 +213,7 @@ export interface AdminCreateProduct {
|
||||
/**
|
||||
* The ID of the product's shipping profile.
|
||||
*/
|
||||
shipping_profile_id: string
|
||||
shipping_profile_id?: string
|
||||
/**
|
||||
* The product's categories.
|
||||
*/
|
||||
@@ -467,7 +467,7 @@ export interface AdminUpdateProduct {
|
||||
/**
|
||||
* The ID of the product's shipping profile.
|
||||
*/
|
||||
shipping_profile_id?: string
|
||||
shipping_profile_id?: string | null
|
||||
/**
|
||||
* The product's weight.
|
||||
*/
|
||||
|
||||
@@ -36,7 +36,7 @@ export type CreateProductWorkflowInputDTO = Omit<
|
||||
/**
|
||||
* The product's shipping profile.
|
||||
*/
|
||||
shipping_profile_id: string
|
||||
shipping_profile_id?: string
|
||||
/**
|
||||
* The product's variants.
|
||||
*/
|
||||
@@ -45,5 +45,5 @@ export type CreateProductWorkflowInputDTO = Omit<
|
||||
|
||||
export type UpdateProductWorkflowInputDTO = ProductTypes.UpsertProductDTO & {
|
||||
sales_channels?: { id: string }[]
|
||||
shipping_profile_id?: string
|
||||
shipping_profile_id?: string | null
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ export const CreateProduct = z
|
||||
options: z.array(CreateProductOption).optional(),
|
||||
variants: z.array(CreateProductVariant).optional(),
|
||||
sales_channels: z.array(z.object({ id: z.string() })).optional(),
|
||||
shipping_profile_id: z.string(),
|
||||
shipping_profile_id: z.string().optional(),
|
||||
weight: z.number().nullish(),
|
||||
length: z.number().nullish(),
|
||||
height: z.number().nullish(),
|
||||
@@ -267,7 +267,7 @@ export const UpdateProduct = z
|
||||
categories: z.array(IdAssociation).optional(),
|
||||
tags: z.array(IdAssociation).optional(),
|
||||
sales_channels: z.array(z.object({ id: z.string() })).optional(),
|
||||
shipping_profile_id: z.string().optional(),
|
||||
shipping_profile_id: z.string().nullish(),
|
||||
weight: z.number().nullish(),
|
||||
length: z.number().nullish(),
|
||||
height: z.number().nullish(),
|
||||
|
||||
Reference in New Issue
Block a user