feat(core-flows, dashboard, link-modules,medusa, types, utils): fulfillment shipping changes (#10902)
**What** - product <> shipping profile link - create and update product workflows/endpoints accepts shipping profile - pass shipping option id when creating fulfillment to allow overriding customer selected SO - validate shipping profile delete - dashboard - set shipping profile on product create - manage shipping profile for a product - **update the create fulfillment form** - other - fix create product form infinite rerenders --- CLOSES CMRC-831 CMRC-834 CMRC-836 CMRC-837 CMRC-838 CMRC-857 TRI-761
This commit is contained in:
1 parent
3e81962503
commit
864d772e34
78 files changed
+3380
-645
No files matched your search
@@ -43,7 +43,8 @@ medusaIntegrationTestRunner({
|
|||||||
salesChannel,
|
salesChannel,
|
||||||
cart,
|
cart,
|
||||||
customer,
|
customer,
|
||||||
promotion
|
promotion,
|
||||||
|
shippingProfile
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
appContainer = getContainer()
|
appContainer = getContainer()
|
||||||
@@ -68,6 +69,14 @@ medusaIntegrationTestRunner({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "default", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
await setupTaxStructure(appContainer.resolve(Modules.TAX))
|
await setupTaxStructure(appContainer.resolve(Modules.TAX))
|
||||||
|
|
||||||
region = (
|
region = (
|
||||||
@@ -87,7 +96,7 @@ medusaIntegrationTestRunner({
|
|||||||
).data.region
|
).data.region
|
||||||
|
|
||||||
product = (
|
product = (
|
||||||
await api.post("/admin/products", medusaTshirtProduct, adminHeaders)
|
await api.post("/admin/products", { ...medusaTshirtProduct, shipping_profile_id: shippingProfile.id }, adminHeaders)
|
||||||
).data.product
|
).data.product
|
||||||
|
|
||||||
salesChannel = (
|
salesChannel = (
|
||||||
|
|||||||
@@ -34,6 +34,17 @@ medusaIntegrationTestRunner({
|
|||||||
const container = getContainer()
|
const container = getContainer()
|
||||||
await createAdminUser(dbConnection, adminHeaders, container)
|
await createAdminUser(dbConnection, adminHeaders, container)
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{
|
||||||
|
name: "Test",
|
||||||
|
type: "default",
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
const region = (
|
const region = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/regions",
|
"/admin/regions",
|
||||||
@@ -72,6 +83,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
title: "Test product",
|
title: "Test product",
|
||||||
options: [{ title: "size", values: ["large", "small"] }],
|
options: [{ title: "size", values: ["large", "small"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Test variant",
|
title: "Test variant",
|
||||||
@@ -96,6 +108,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
title: "Extra product",
|
title: "Extra product",
|
||||||
options: [{ title: "size", values: ["large", "small"] }],
|
options: [{ title: "size", values: ["large", "small"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "my variant",
|
title: "my variant",
|
||||||
@@ -213,17 +226,6 @@ medusaIntegrationTestRunner({
|
|||||||
customer_id: customer.id,
|
customer_id: customer.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
shippingProfile = (
|
|
||||||
await api.post(
|
|
||||||
`/admin/shipping-profiles`,
|
|
||||||
{
|
|
||||||
name: "Test",
|
|
||||||
type: "default",
|
|
||||||
},
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
).data.shipping_profile
|
|
||||||
|
|
||||||
location = (
|
location = (
|
||||||
await api.post(
|
await api.post(
|
||||||
`/admin/stock-locations`,
|
`/admin/stock-locations`,
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ medusaIntegrationTestRunner({
|
|||||||
let baseProduct
|
let baseProduct
|
||||||
let baseProduct1
|
let baseProduct1
|
||||||
|
|
||||||
|
let shippingProfile
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const container = getContainer()
|
const container = getContainer()
|
||||||
await createAdminUser(dbConnection, adminHeaders, container)
|
await createAdminUser(dbConnection, adminHeaders, container)
|
||||||
@@ -44,12 +46,21 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
).data.collection
|
).data.collection
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "Test", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
baseProduct = (
|
baseProduct = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/products",
|
"/admin/products",
|
||||||
{
|
{
|
||||||
title: "test-product",
|
title: "test-product",
|
||||||
options: [{ title: "size", values: ["x", "l"] }],
|
options: [{ title: "size", values: ["x", "l"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
},
|
},
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
@@ -61,6 +72,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
title: "test-product1",
|
title: "test-product1",
|
||||||
options: [{ title: "size", values: ["x", "l"] }],
|
options: [{ title: "size", values: ["x", "l"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
},
|
},
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -41,6 +41,17 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
).data.region
|
).data.region
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{
|
||||||
|
name: "Test",
|
||||||
|
type: "default",
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
const customer = (
|
const customer = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/customers",
|
"/admin/customers",
|
||||||
@@ -67,6 +78,7 @@ medusaIntegrationTestRunner({
|
|||||||
"/admin/products",
|
"/admin/products",
|
||||||
{
|
{
|
||||||
title: "Test product",
|
title: "Test product",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
options: [{ title: "size", values: ["large", "small"] }],
|
options: [{ title: "size", values: ["large", "small"] }],
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
@@ -91,6 +103,7 @@ medusaIntegrationTestRunner({
|
|||||||
"/admin/products",
|
"/admin/products",
|
||||||
{
|
{
|
||||||
title: "Extra product",
|
title: "Extra product",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
options: [{ title: "size", values: ["large", "small"] }],
|
options: [{ title: "size", values: ["large", "small"] }],
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
@@ -203,17 +216,6 @@ medusaIntegrationTestRunner({
|
|||||||
customer_id: customer.id,
|
customer_id: customer.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
shippingProfile = (
|
|
||||||
await api.post(
|
|
||||||
`/admin/shipping-profiles`,
|
|
||||||
{
|
|
||||||
name: "Test",
|
|
||||||
type: "default",
|
|
||||||
},
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
).data.shipping_profile
|
|
||||||
|
|
||||||
location = (
|
location = (
|
||||||
await api.post(
|
await api.post(
|
||||||
`/admin/stock-locations`,
|
`/admin/stock-locations`,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
AdminInventoryItem,
|
AdminInventoryItem,
|
||||||
AdminProduct,
|
AdminProduct,
|
||||||
|
AdminShippingProfile,
|
||||||
AdminStockLocation,
|
AdminStockLocation,
|
||||||
MedusaContainer,
|
MedusaContainer,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
@@ -18,6 +19,8 @@ export async function createOrderSeeder({
|
|||||||
additionalProducts,
|
additionalProducts,
|
||||||
stockChannelOverride,
|
stockChannelOverride,
|
||||||
inventoryItemOverride,
|
inventoryItemOverride,
|
||||||
|
shippingProfileOverride,
|
||||||
|
withoutShipping,
|
||||||
}: {
|
}: {
|
||||||
api: any
|
api: any
|
||||||
container: MedusaContainer
|
container: MedusaContainer
|
||||||
@@ -26,6 +29,8 @@ export async function createOrderSeeder({
|
|||||||
stockChannelOverride?: AdminStockLocation
|
stockChannelOverride?: AdminStockLocation
|
||||||
additionalProducts?: { variant_id: string; quantity: number }[]
|
additionalProducts?: { variant_id: string; quantity: number }[]
|
||||||
inventoryItemOverride?: AdminInventoryItem
|
inventoryItemOverride?: AdminInventoryItem
|
||||||
|
shippingProfileOverride?: AdminShippingProfile
|
||||||
|
withoutShipping?: boolean
|
||||||
}) {
|
}) {
|
||||||
const publishableKey = await generatePublishableKey(container)
|
const publishableKey = await generatePublishableKey(container)
|
||||||
|
|
||||||
@@ -86,7 +91,9 @@ export async function createOrderSeeder({
|
|||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
|
|
||||||
const shippingProfile = (
|
const shippingProfile =
|
||||||
|
shippingProfileOverride ??
|
||||||
|
(
|
||||||
await api.post(
|
await api.post(
|
||||||
`/admin/shipping-profiles`,
|
`/admin/shipping-profiles`,
|
||||||
{ name: `test-${stockLocation.id}`, type: "default" },
|
{ name: `test-${stockLocation.id}`, type: "default" },
|
||||||
@@ -101,6 +108,7 @@ export async function createOrderSeeder({
|
|||||||
"/admin/products",
|
"/admin/products",
|
||||||
{
|
{
|
||||||
title: `Test fixture ${shippingProfile.id}`,
|
title: `Test fixture ${shippingProfile.id}`,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
options: [
|
options: [
|
||||||
{ title: "size", values: ["large", "small"] },
|
{ title: "size", values: ["large", "small"] },
|
||||||
{ title: "color", values: ["green"] },
|
{ title: "color", values: ["green"] },
|
||||||
@@ -217,6 +225,14 @@ export async function createOrderSeeder({
|
|||||||
)
|
)
|
||||||
).data.cart
|
).data.cart
|
||||||
|
|
||||||
|
if (!withoutShipping) {
|
||||||
|
await api.post(
|
||||||
|
`/store/carts/${cart.id}/shipping-methods`,
|
||||||
|
{ option_id: shippingOption.id },
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const paymentCollection = (
|
const paymentCollection = (
|
||||||
await api.post(
|
await api.post(
|
||||||
`/store/payment-collections`,
|
`/store/payment-collections`,
|
||||||
|
|||||||
@@ -13,9 +13,19 @@ medusaIntegrationTestRunner({
|
|||||||
let stockLocation1
|
let stockLocation1
|
||||||
let stockLocation2
|
let stockLocation2
|
||||||
let stockLocation3
|
let stockLocation3
|
||||||
|
|
||||||
|
let shippingProfile
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "Test", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
stockLocation1 = (
|
stockLocation1 = (
|
||||||
await api.post(`/admin/stock-locations`, { name: "loc1" }, adminHeaders)
|
await api.post(`/admin/stock-locations`, { name: "loc1" }, adminHeaders)
|
||||||
).data.stock_location
|
).data.stock_location
|
||||||
@@ -990,6 +1000,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
title: "product 1",
|
title: "product 1",
|
||||||
options: [{ title: "size", values: ["large"] }],
|
options: [{ title: "size", values: ["large"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "variant 1",
|
title: "variant 1",
|
||||||
|
|||||||
@@ -85,12 +85,24 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
).data.sales_channel
|
).data.sales_channel
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{
|
||||||
|
name: "Test",
|
||||||
|
type: "default",
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
const product = (
|
const product = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/products",
|
"/admin/products",
|
||||||
{
|
{
|
||||||
title: "Test product",
|
title: "Test product",
|
||||||
options: [{ title: "size", values: ["large", "small"] }],
|
options: [{ title: "size", values: ["large", "small"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Test variant",
|
title: "Test variant",
|
||||||
@@ -115,6 +127,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
title: "Extra product",
|
title: "Extra product",
|
||||||
options: [{ title: "size", values: ["large", "small"] }],
|
options: [{ title: "size", values: ["large", "small"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "my variant",
|
title: "my variant",
|
||||||
@@ -174,17 +187,6 @@ medusaIntegrationTestRunner({
|
|||||||
customer_id: customer.id,
|
customer_id: customer.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
shippingProfile = (
|
|
||||||
await api.post(
|
|
||||||
`/admin/shipping-profiles`,
|
|
||||||
{
|
|
||||||
name: "Test",
|
|
||||||
type: "default",
|
|
||||||
},
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
).data.shipping_profile
|
|
||||||
|
|
||||||
location = (
|
location = (
|
||||||
await api.post(
|
await api.post(
|
||||||
`/admin/stock-locations`,
|
`/admin/stock-locations`,
|
||||||
|
|||||||
@@ -11,13 +11,21 @@ jest.setTimeout(300000)
|
|||||||
|
|
||||||
medusaIntegrationTestRunner({
|
medusaIntegrationTestRunner({
|
||||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||||
let order, seeder, inventoryItemOverride3, productOverride3
|
let order, seeder, inventoryItemOverride3, productOverride3, shippingProfile
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const container = getContainer()
|
const container = getContainer()
|
||||||
|
|
||||||
await setupTaxStructure(container.resolve(ModuleRegistrationName.TAX))
|
await setupTaxStructure(container.resolve(ModuleRegistrationName.TAX))
|
||||||
await createAdminUser(dbConnection, adminHeaders, container)
|
await createAdminUser(dbConnection, adminHeaders, container)
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "Test", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("POST /orders/:id", () => {
|
describe("POST /orders/:id", () => {
|
||||||
@@ -345,9 +353,19 @@ medusaIntegrationTestRunner({
|
|||||||
|
|
||||||
describe("POST /orders/:id/cancel", () => {
|
describe("POST /orders/:id/cancel", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
const inventoryItemOverride = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/inventory-items`,
|
||||||
|
{ sku: "test-variant", requires_shipping: false },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.inventory_item
|
||||||
|
|
||||||
seeder = await createOrderSeeder({
|
seeder = await createOrderSeeder({
|
||||||
api,
|
api,
|
||||||
container: getContainer(),
|
container: getContainer(),
|
||||||
|
inventoryItemOverride,
|
||||||
|
withoutShipping: true,
|
||||||
})
|
})
|
||||||
order = seeder.order
|
order = seeder.order
|
||||||
|
|
||||||
@@ -536,8 +554,12 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("POST /orders/:id/fulfillments", () => {
|
describe("POST /orders/:id/fulfillments", () => {
|
||||||
|
let productOverride4WithOverrideShippingProfile,
|
||||||
|
shippingProfileOverride,
|
||||||
|
stockChannelOverride
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const stockChannelOverride = (
|
stockChannelOverride = (
|
||||||
await api.post(
|
await api.post(
|
||||||
`/admin/stock-locations`,
|
`/admin/stock-locations`,
|
||||||
{ name: "test location" },
|
{ name: "test location" },
|
||||||
@@ -558,6 +580,7 @@ medusaIntegrationTestRunner({
|
|||||||
"/admin/products",
|
"/admin/products",
|
||||||
{
|
{
|
||||||
title: `Test fixture`,
|
title: `Test fixture`,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
options: [
|
options: [
|
||||||
{ title: "size", values: ["large", "small"] },
|
{ title: "size", values: ["large", "small"] },
|
||||||
{ title: "color", values: ["green"] },
|
{ title: "color", values: ["green"] },
|
||||||
@@ -605,6 +628,14 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
).data.inventory_item
|
).data.inventory_item
|
||||||
|
|
||||||
|
const inventoryItemOverride4RequiresShipping = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/inventory-items`,
|
||||||
|
{ sku: "test-variant-4", requires_shipping: true },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.inventory_item
|
||||||
|
|
||||||
await api.post(
|
await api.post(
|
||||||
`/admin/inventory-items/${inventoryItemOverride2.id}/location-levels`,
|
`/admin/inventory-items/${inventoryItemOverride2.id}/location-levels`,
|
||||||
{
|
{
|
||||||
@@ -623,11 +654,21 @@ medusaIntegrationTestRunner({
|
|||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/admin/inventory-items/${inventoryItemOverride4RequiresShipping.id}/location-levels`,
|
||||||
|
{
|
||||||
|
location_id: stockChannelOverride.id,
|
||||||
|
stocked_quantity: 10,
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
const productOverride2 = (
|
const productOverride2 = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/products",
|
"/admin/products",
|
||||||
{
|
{
|
||||||
title: `Test fixture 2`,
|
title: `Test fixture 2`,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
options: [
|
options: [
|
||||||
{ title: "size", values: ["large", "small"] },
|
{ title: "size", values: ["large", "small"] },
|
||||||
{ title: "color", values: ["green"] },
|
{ title: "color", values: ["green"] },
|
||||||
@@ -664,6 +705,7 @@ medusaIntegrationTestRunner({
|
|||||||
"/admin/products",
|
"/admin/products",
|
||||||
{
|
{
|
||||||
title: `Test fixture 3`,
|
title: `Test fixture 3`,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
options: [
|
options: [
|
||||||
{ title: "size", values: ["large", "small"] },
|
{ title: "size", values: ["large", "small"] },
|
||||||
{ title: "color", values: ["green"] },
|
{ title: "color", values: ["green"] },
|
||||||
@@ -695,6 +737,52 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
).data.product
|
).data.product
|
||||||
|
|
||||||
|
shippingProfileOverride = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: `test-${stockChannelOverride.id}`, type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
|
productOverride4WithOverrideShippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
"/admin/products",
|
||||||
|
{
|
||||||
|
title: `Test fixture 4`,
|
||||||
|
shipping_profile_id: shippingProfileOverride.id,
|
||||||
|
options: [
|
||||||
|
{ title: "size", values: ["large", "small"] },
|
||||||
|
{ title: "color", values: ["green"] },
|
||||||
|
],
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
title: "Test variant 4",
|
||||||
|
sku: "test-variant-4",
|
||||||
|
inventory_items: [
|
||||||
|
{
|
||||||
|
inventory_item_id:
|
||||||
|
inventoryItemOverride4RequiresShipping.id,
|
||||||
|
required_quantity: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
options: {
|
||||||
|
size: "small",
|
||||||
|
color: "green",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.product
|
||||||
|
|
||||||
seeder = await createOrderSeeder({
|
seeder = await createOrderSeeder({
|
||||||
api,
|
api,
|
||||||
container: getContainer(),
|
container: getContainer(),
|
||||||
@@ -702,9 +790,15 @@ medusaIntegrationTestRunner({
|
|||||||
additionalProducts: [
|
additionalProducts: [
|
||||||
{ variant_id: productOverride2.variants[0].id, quantity: 1 },
|
{ variant_id: productOverride2.variants[0].id, quantity: 1 },
|
||||||
{ variant_id: productOverride3.variants[0].id, quantity: 3 },
|
{ variant_id: productOverride3.variants[0].id, quantity: 3 },
|
||||||
|
{
|
||||||
|
variant_id:
|
||||||
|
productOverride4WithOverrideShippingProfile.variants[0].id,
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
stockChannelOverride,
|
stockChannelOverride,
|
||||||
inventoryItemOverride,
|
inventoryItemOverride,
|
||||||
|
shippingProfileOverride: shippingProfile,
|
||||||
})
|
})
|
||||||
order = seeder.order
|
order = seeder.order
|
||||||
order = (await api.get(`/admin/orders/${order.id}`, adminHeaders)).data
|
order = (await api.get(`/admin/orders/${order.id}`, adminHeaders)).data
|
||||||
@@ -827,6 +921,30 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should throw if shipping profile of the product doesn't match the shipping profile of the shipping option", async () => {
|
||||||
|
const orderItemId = order.items.find(
|
||||||
|
(i) =>
|
||||||
|
i.variant_id ===
|
||||||
|
productOverride4WithOverrideShippingProfile.variants[0].id
|
||||||
|
).id
|
||||||
|
|
||||||
|
const res = await api
|
||||||
|
.post(
|
||||||
|
`/admin/orders/${order.id}/fulfillments`,
|
||||||
|
{
|
||||||
|
location_id: stockChannelOverride.id,
|
||||||
|
items: [{ id: orderItemId, quantity: 1 }],
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(res.response.status).toBe(400)
|
||||||
|
expect(res.response.data.message).toBe(
|
||||||
|
`Shipping profile ${seeder.shippingProfile.id} does not match the shipping profile of the order item ${orderItemId}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
it("should only create fulfillments grouped by shipping requirement", async () => {
|
it("should only create fulfillments grouped by shipping requirement", async () => {
|
||||||
const {
|
const {
|
||||||
response: { data },
|
response: { data },
|
||||||
|
|||||||
@@ -24,7 +24,21 @@ medusaIntegrationTestRunner({
|
|||||||
|
|
||||||
await setupTaxStructure(container.resolve(Modules.TAX))
|
await setupTaxStructure(container.resolve(Modules.TAX))
|
||||||
await createAdminUser(dbConnection, adminHeaders, container)
|
await createAdminUser(dbConnection, adminHeaders, container)
|
||||||
const seeders = await createOrderSeeder({ api, container })
|
|
||||||
|
const inventoryItemOverride = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/inventory-items`,
|
||||||
|
{ sku: "test-variant", requires_shipping: false },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.inventory_item
|
||||||
|
|
||||||
|
const seeders = await createOrderSeeder({
|
||||||
|
api,
|
||||||
|
container,
|
||||||
|
inventoryItemOverride,
|
||||||
|
withoutShipping: true,
|
||||||
|
})
|
||||||
order = seeders.order
|
order = seeders.order
|
||||||
|
|
||||||
shippingProfile = (
|
shippingProfile = (
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ medusaIntegrationTestRunner({
|
|||||||
let region
|
let region
|
||||||
let product
|
let product
|
||||||
let cart
|
let cart
|
||||||
|
let shippingProfile
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
region = (
|
region = (
|
||||||
@@ -34,12 +35,21 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
).data.region
|
).data.region
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "Test", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
product = (
|
product = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/products",
|
"/admin/products",
|
||||||
getProductFixture({
|
getProductFixture({
|
||||||
title: "test",
|
title: "test",
|
||||||
status: "published",
|
status: "published",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Test variant",
|
title: "Test variant",
|
||||||
|
|||||||
@@ -38,7 +38,22 @@ medusaIntegrationTestRunner({
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
container = getContainer()
|
container = getContainer()
|
||||||
await createAdminUser(dbConnection, adminHeaders, container)
|
await createAdminUser(dbConnection, adminHeaders, container)
|
||||||
const seeders = await createOrderSeeder({ api, container })
|
|
||||||
|
const inventoryItemOverride = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/inventory-items`,
|
||||||
|
{ sku: "test-variant", requires_shipping: false },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.inventory_item
|
||||||
|
|
||||||
|
const seeders = await createOrderSeeder({
|
||||||
|
api,
|
||||||
|
container,
|
||||||
|
inventoryItemOverride,
|
||||||
|
withoutShipping: true,
|
||||||
|
})
|
||||||
|
|
||||||
order = seeders.order
|
order = seeders.order
|
||||||
|
|
||||||
await api.post(
|
await api.post(
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ medusaIntegrationTestRunner({
|
|||||||
let region1
|
let region1
|
||||||
let product1
|
let product1
|
||||||
let customerGroup1
|
let customerGroup1
|
||||||
|
let shippingProfile
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const container = getContainer()
|
const container = getContainer()
|
||||||
@@ -34,10 +35,21 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
).data.region
|
).data.region
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "Test", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
product1 = (
|
product1 = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/products",
|
"/admin/products",
|
||||||
getProductFixture({ title: "Test product" }),
|
getProductFixture({
|
||||||
|
title: "Test product",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
}),
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
).data.product
|
).data.product
|
||||||
|
|||||||
@@ -18,9 +18,19 @@ medusaIntegrationTestRunner({
|
|||||||
let productCategoryChild2
|
let productCategoryChild2
|
||||||
let productCategoryChild3
|
let productCategoryChild3
|
||||||
|
|
||||||
|
let shippingProfile
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const appContainer = getContainer()
|
const appContainer = getContainer()
|
||||||
await createAdminUser(dbConnection, adminHeaders, appContainer)
|
await createAdminUser(dbConnection, adminHeaders, appContainer)
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "default", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("GET /admin/product-categories/:id", () => {
|
describe("GET /admin/product-categories/:id", () => {
|
||||||
@@ -1349,6 +1359,7 @@ medusaIntegrationTestRunner({
|
|||||||
title: "product 1",
|
title: "product 1",
|
||||||
options: [{ title: "size", values: ["x", "l"] }],
|
options: [{ title: "size", values: ["x", "l"] }],
|
||||||
categories: [{ id: productCategory.id }],
|
categories: [{ id: productCategory.id }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
},
|
},
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
@@ -1358,6 +1369,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
title: "product 2",
|
title: "product 2",
|
||||||
options: [{ title: "color", values: ["r", "g"] }],
|
options: [{ title: "color", values: ["r", "g"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
},
|
},
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
|
|||||||
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
Product Id,Product Handle,Product Title,Product Status,Product Description,Product Subtitle,Product External Id,Product Thumbnail,Product Collection Id,Product Type Id,Product Created At,Product Deleted At,Product Discountable,Product Height,Product Hs Code,Product Image 1,Product Image 2,Product Is Giftcard,Product Length,Product Material,Product Mid Code,Product Origin Country,Product Tag 1,Product Tag 2,Product Updated At,Product Weight,Product Width,Variant Id,Variant Title,Variant Sku,Variant Upc,Variant Ean,Variant Hs Code,Variant Mid Code,Variant Manage Inventory,Variant Allow Backorder,Variant Barcode,Variant Created At,Variant Deleted At,Variant Height,Variant Length,Variant Material,Variant Metadata,Variant Option 1 Name,Variant Option 1 Value,Variant Option 2 Name,Variant Option 2 Value,Variant Origin Country,Variant Price DKK,Variant Price EUR,Variant Price USD,Variant Product Id,Variant Updated At,Variant Variant Rank,Variant Weight,Variant Width
|
Product Id,Product Handle,Product Title,Product Status,Product Description,Product Subtitle,Product External Id,Product Thumbnail,Product Collection Id,Product Type Id,Product Created At,Product Deleted At,Product Discountable,Product Height,Product Hs Code,Product Image 1,Product Image 2,Product Is Giftcard,Product Length,Product Material,Product Mid Code,Product Origin Country,Product Tag 1,Product Tag 2,Product Updated At,Product Weight,Product Width,Variant Id,Variant Title,Variant Sku,Variant Upc,Variant Ean,Variant Hs Code,Variant Mid Code,Variant Manage Inventory,Variant Allow Backorder,Variant Barcode,Variant Created At,Variant Deleted At,Variant Height,Variant Length,Variant Material,Variant Metadata,Variant Option 1 Name,Variant Option 1 Value,Variant Option 2 Name,Variant Option 2 Value,Variant Origin Country,Variant Price DKK,Variant Price EUR,Variant Price USD,Variant Product Id,Variant Updated At,Variant Variant Rank,Variant Weight,Variant Width,Shipping Profile Id
|
||||||
prod_01J44RRJZ3M5F63NY82434RNM5,base-product,Base product,draft,"test-product-description
|
prod_01J44RRJZ3M5F63NY82434RNM5,base-product,Base product,draft,"test-product-description
|
||||||
test line 2",,,test-image.png,pcol_01J44RRJXM6AM3YS5PMJDMH3YF,ptyp_01J44RRJYAFEBZ2EY1KE1JM3XD,2024-07-31T16:07:55.102Z,,true,,,test-image.png,test-image-2.png,false,,,,,123,456,2024-07-31T16:07:55.102Z,,,variant_01J44RRJZW1T9KQB6XG7Q6K61F,Test variant,,,,,,true,false,,2024-07-31T16:07:55.133Z,,,,,,size,large,color,green,,30,45,100,prod_01J44RRJZ3M5F63NY82434RNM5,2024-07-31T16:07:55.133Z,0,,
|
test line 2",,,test-image.png,pcol_01J44RRJXM6AM3YS5PMJDMH3YF,ptyp_01J44RRJYAFEBZ2EY1KE1JM3XD,2024-07-31T16:07:55.102Z,,true,,,test-image.png,test-image-2.png,false,,,,,123,456,2024-07-31T16:07:55.102Z,,,variant_01J44RRJZW1T9KQB6XG7Q6K61F,Test variant,,,,,,true,false,,2024-07-31T16:07:55.133Z,,,,,,size,large,color,green,,30,45,100,prod_01J44RRJZ3M5F63NY82434RNM5,2024-07-31T16:07:55.133Z,0,,,import-shipping-profile
|
||||||
prod_01J44RRJZ3M5F63NY82434RNM5,base-product,Base product,draft,"test-product-description
|
prod_01J44RRJZ3M5F63NY82434RNM5,base-product,Base product,draft,"test-product-description
|
||||||
test line 2",,,test-image.png,pcol_01J44RRJXM6AM3YS5PMJDMH3YF,ptyp_01J44RRJYAFEBZ2EY1KE1JM3XD,2024-07-31T16:07:55.102Z,,true,,,test-image.png,test-image-2.png,false,,,,,123,456,2024-07-31T16:07:55.102Z,,,variant_01J44RRJZW5GNQKT1FEDACEESW,Test variant 2,,,,,,true,false,,2024-07-31T16:07:55.133Z,,,,,,size,small,color,green,,50,65,200,prod_01J44RRJZ3M5F63NY82434RNM5,2024-07-31T16:07:55.133Z,0,,
|
test line 2",,,test-image.png,pcol_01J44RRJXM6AM3YS5PMJDMH3YF,ptyp_01J44RRJYAFEBZ2EY1KE1JM3XD,2024-07-31T16:07:55.102Z,,true,,,test-image.png,test-image-2.png,false,,,,,123,456,2024-07-31T16:07:55.102Z,,,variant_01J44RRJZW5GNQKT1FEDACEESW,Test variant 2,,,,,,true,false,,2024-07-31T16:07:55.133Z,,,,,,size,small,color,green,,50,65,200,prod_01J44RRJZ3M5F63NY82434RNM5,2024-07-31T16:07:55.133Z,0,,,import-shipping-profile
|
||||||
prod_01J44RRK2GJJVMQQXT67TJCV08,proposed-product,Proposed product,proposed,test-product-description,,,test-image.png,,ptyp_01J44RRJYAFEBZ2EY1KE1JM3XD,2024-07-31T16:07:55.213Z,,true,,,test-image.png,test-image-2.png,false,,,,,new-tag,,2024-07-31T16:07:55.213Z,,,variant_01J44RRK2WYHH0RDEK8BBGP7CY,Test variant,,,,,,true,false,,2024-07-31T16:07:55.228Z,,,,,,size,large,color,green,,30,45,100,prod_01J44RRK2GJJVMQQXT67TJCV08,2024-07-31T16:07:55.228Z,0,,
|
prod_01J44RRK2GJJVMQQXT67TJCV08,proposed-product,Proposed product,proposed,test-product-description,,,test-image.png,,ptyp_01J44RRJYAFEBZ2EY1KE1JM3XD,2024-07-31T16:07:55.213Z,,true,,,test-image.png,test-image-2.png,false,,,,,new-tag,,2024-07-31T16:07:55.213Z,,,variant_01J44RRK2WYHH0RDEK8BBGP7CY,Test variant,,,,,,true,false,,2024-07-31T16:07:55.228Z,,,,,,size,large,color,green,,30,45,100,prod_01J44RRK2GJJVMQQXT67TJCV08,2024-07-31T16:07:55.228Z,0,,,import-shipping-profile
|
||||||
|
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
Product Id;Product Handle;Product Title;Product Status;Product Description;Product Subtitle;Product External Id;Product Thumbnail;Product Collection Id;Product Type Id;Product Created At;Product Deleted At;Product Discountable;Product Height;Product Hs Code;Product Image 1;Product Image 2;Product Is Giftcard;Product Length;Product Material;Product Mid Code;Product Origin Country;Product Tag 1;Product Tag 2;Product Updated At;Product Weight;Product Width;Variant Id;Variant Title;Variant Sku;Variant Upc;Variant Ean;Variant Hs Code;Variant Mid Code;Variant Manage Inventory;Variant Allow Backorder;Variant Barcode;Variant Created At;Variant Deleted At;Variant Height;Variant Length;Variant Material;Variant Metadata;Variant Option 1 Name;Variant Option 1 Value;Variant Option 2 Name;Variant Option 2 Value;Variant Origin Country;Variant Price DKK;Variant Price EUR;Variant Price USD;Variant Product Id;Variant Updated At;Variant Variant Rank;Variant Weight;Variant Width
|
Product Id;Product Handle;Product Title;Product Status;Product Description;Product Subtitle;Product External Id;Product Thumbnail;Product Collection Id;Product Type Id;Product Created At;Product Deleted At;Product Discountable;Product Height;Product Hs Code;Product Image 1;Product Image 2;Product Is Giftcard;Product Length;Product Material;Product Mid Code;Product Origin Country;Product Tag 1;Product Tag 2;Product Updated At;Product Weight;Product Width;Variant Id;Variant Title;Variant Sku;Variant Upc;Variant Ean;Variant Hs Code;Variant Mid Code;Variant Manage Inventory;Variant Allow Backorder;Variant Barcode;Variant Created At;Variant Deleted At;Variant Height;Variant Length;Variant Material;Variant Metadata;Variant Option 1 Name;Variant Option 1 Value;Variant Option 2 Name;Variant Option 2 Value;Variant Origin Country;Variant Price DKK;Variant Price EUR;Variant Price USD;Variant Product Id;Variant Updated At;Variant Variant Rank;Variant Weight;Variant Width;Shipping Profile Id
|
||||||
prod_01J44RRJZ3M5F63NY82434RNM5;base-product;Base product;draft;"test-product-description
|
prod_01J44RRJZ3M5F63NY82434RNM5;base-product;Base product;draft;"test-product-description
|
||||||
test line 2";;;test-image.png;pcol_01J44RRJXM6AM3YS5PMJDMH3YF;ptyp_01J44RRJYAFEBZ2EY1KE1JM3XD;2024-07-31T16:07:55.102Z;;true;;;test-image.png;test-image-2.png;false;;;;;123;456;2024-07-31T16:07:55.102Z;;;variant_01J44RRJZW1T9KQB6XG7Q6K61F;Test variant;;;;;;true;false;;2024-07-31T16:07:55.133Z;;;;;;size;large;color;green;;30;45;100;prod_01J44RRJZ3M5F63NY82434RNM5;2024-07-31T16:07:55.133Z;0;;
|
test line 2";;;test-image.png;pcol_01J44RRJXM6AM3YS5PMJDMH3YF;ptyp_01J44RRJYAFEBZ2EY1KE1JM3XD;2024-07-31T16:07:55.102Z;;true;;;test-image.png;test-image-2.png;false;;;;;123;456;2024-07-31T16:07:55.102Z;;;variant_01J44RRJZW1T9KQB6XG7Q6K61F;Test variant;;;;;;true;false;;2024-07-31T16:07:55.133Z;;;;;;size;large;color;green;;30;45;100;prod_01J44RRJZ3M5F63NY82434RNM5;2024-07-31T16:07:55.133Z;0;;;import-shipping-profile
|
||||||
prod_01J44RRJZ3M5F63NY82434RNM5;base-product;Base product;draft;"test-product-description
|
prod_01J44RRJZ3M5F63NY82434RNM5;base-product;Base product;draft;"test-product-description
|
||||||
test line 2";;;test-image.png;pcol_01J44RRJXM6AM3YS5PMJDMH3YF;ptyp_01J44RRJYAFEBZ2EY1KE1JM3XD;2024-07-31T16:07:55.102Z;;true;;;test-image.png;test-image-2.png;false;;;;;123;456;2024-07-31T16:07:55.102Z;;;variant_01J44RRJZW5GNQKT1FEDACEESW;Test variant 2;;;;;;true;false;;2024-07-31T16:07:55.133Z;;;;;;size;small;color;green;;50;65;200;prod_01J44RRJZ3M5F63NY82434RNM5;2024-07-31T16:07:55.133Z;0;;
|
test line 2";;;test-image.png;pcol_01J44RRJXM6AM3YS5PMJDMH3YF;ptyp_01J44RRJYAFEBZ2EY1KE1JM3XD;2024-07-31T16:07:55.102Z;;true;;;test-image.png;test-image-2.png;false;;;;;123;456;2024-07-31T16:07:55.102Z;;;variant_01J44RRJZW5GNQKT1FEDACEESW;Test variant 2;;;;;;true;false;;2024-07-31T16:07:55.133Z;;;;;;size;small;color;green;;50;65;200;prod_01J44RRJZ3M5F63NY82434RNM5;2024-07-31T16:07:55.133Z;0;;;import-shipping-profile
|
||||||
prod_01J44RRK2GJJVMQQXT67TJCV08;proposed-product;Proposed product;proposed;test-product-description;;;test-image.png;;ptyp_01J44RRJYAFEBZ2EY1KE1JM3XD;2024-07-31T16:07:55.213Z;;true;;;test-image.png;test-image-2.png;false;;;;;new-tag;;2024-07-31T16:07:55.213Z;;;variant_01J44RRK2WYHH0RDEK8BBGP7CY;Test variant;;;;;;true;false;;2024-07-31T16:07:55.228Z;;;;;;size;large;color;green;;30;45;100;prod_01J44RRK2GJJVMQQXT67TJCV08;2024-07-31T16:07:55.228Z;0;;
|
prod_01J44RRK2GJJVMQQXT67TJCV08;proposed-product;Proposed product;proposed;test-product-description;;;test-image.png;;ptyp_01J44RRJYAFEBZ2EY1KE1JM3XD;2024-07-31T16:07:55.213Z;;true;;;test-image.png;test-image-2.png;false;;;;;new-tag;;2024-07-31T16:07:55.213Z;;;variant_01J44RRK2WYHH0RDEK8BBGP7CY;Test variant;;;;;;true;false;;2024-07-31T16:07:55.228Z;;;;;;size;large;color;green;;30;45;100;prod_01J44RRK2GJJVMQQXT67TJCV08;2024-07-31T16:07:55.228Z;0;;;import-shipping-profile
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
Product Id,Product Handle,Product Title,Product Status,Product Description,Product Subtitle,Product External Id,Product Thumbnail,Product Collection Id,Product Type Id,Product Created At,Product Deleted At,Product Discountable,Product Height,Product Hs Code,Product Image 1,Product Image 2,Product Is Giftcard,Product Length,Product Material,Product Mid Code,Product Origin Country,Product Tag 1,Product Updated At,Product Weight,Product Width,Variant Id,Variant Title,Variant Sku,Variant Upc,Variant Ean,Variant Hs Code,Variant Mid Code,Variant Manage Inventory,Variant Allow Backorder,Variant Barcode,Variant Created At,Variant Deleted At,Variant Height,Variant Length,Variant Material,Variant Metadata,Variant Option 1 Name,Variant Option 1 Value,Variant Option 2 Name,Variant Option 2 Value,Variant Origin Country,Variant Price DKK,Variant Price EUR,Variant Price USD,Variant Product Id,Variant Updated At,Variant Variant Rank,Variant Weight,Variant Width
|
Product Id,Product Handle,Product Title,Product Status,Product Description,Product Subtitle,Product External Id,Product Thumbnail,Product Collection Id,Product Type Id,Product Created At,Product Deleted At,Product Discountable,Product Height,Product Hs Code,Product Image 1,Product Image 2,Product Is Giftcard,Product Length,Product Material,Product Mid Code,Product Origin Country,Product Tag 1,Product Updated At,Product Weight,Product Width,Variant Id,Variant Title,Variant Sku,Variant Upc,Variant Ean,Variant Hs Code,Variant Mid Code,Variant Manage Inventory,Variant Allow Backorder,Variant Barcode,Variant Created At,Variant Deleted At,Variant Height,Variant Length,Variant Material,Variant Metadata,Variant Option 1 Name,Variant Option 1 Value,Variant Option 2 Name,Variant Option 2 Value,Variant Origin Country,Variant Price DKK,Variant Price EUR,Variant Price USD,Variant Product Id,Variant Updated At,Variant Variant Rank,Variant Weight,Variant Width,Shipping Profile Id
|
||||||
prod_01J44RRMJ7H2K9JAD9AQHA724B,proposed-product,Proposed product,proposed,test-product-description,,,test-image.png,,ptyp_01J44RRMF4J9SG6F7FG3B16A7F,2024-07-31T16:07:56.741Z,,true,,,test-image.png,test-image-2.png,false,,,,,new-tag,2024-07-31T16:07:56.741Z,,,variant_01J44RRMJJT9HSEGFJHTY2SX2P,Test variant,,,,,,true,false,,2024-07-31T16:07:56.754Z,,,,,,size,large,color,green,,30,45,100,prod_01J44RRMJ7H2K9JAD9AQHA724B,2024-07-31T16:07:56.754Z,0,,
|
prod_01J44RRMJ7H2K9JAD9AQHA724B,proposed-product,Proposed product,proposed,test-product-description,,,test-image.png,,ptyp_01J44RRMF4J9SG6F7FG3B16A7F,2024-07-31T16:07:56.741Z,,true,,,test-image.png,test-image-2.png,false,,,,,new-tag,2024-07-31T16:07:56.741Z,,,variant_01J44RRMJJT9HSEGFJHTY2SX2P,Test variant,,,,,,true,false,,2024-07-31T16:07:56.754Z,,,,,,size,large,color,green,,30,45,100,prod_01J44RRMJ7H2K9JAD9AQHA724B,2024-07-31T16:07:56.754Z,0,,,import-shipping-profile
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
Product Id,Product Title,Product Subtitle,Product Status,Product External Id,Product Description,Product Handle,Product Is Giftcard,Product Discountable,Product Thumbnail,Product Collection Id,Product Type Id,Product Weight,Product Length,Product Height,Product Width,Product Hs Code,Product Origin Country,Product Mid Code,Product Material,Product Created At,Product Updated At,Product Deleted At,Product Image 1,Product Image 2,Product Tag 1,Variant Id,Variant Title,Variant Sku,Variant Barcode,Variant Ean,Variant Upc,Variant Allow Backorder,Variant Manage Inventory,Variant Hs Code,Variant Origin Country,Variant Mid Code,Variant Material,Variant Weight,Variant Length,Variant Height,Variant Width,Variant Metadata,Variant Variant Rank,Variant Product Id,Variant Created At,Variant Updated At,Variant Deleted At,Variant Price USD,Variant Price EUR,Variant Price DKK,Variant Option 1 Name,Variant Option 1 Value,Variant Option 2 Name,Variant Option 2 Value,Product field
|
Product Id,Product Title,Product Subtitle,Product Status,Product External Id,Product Description,Product Handle,Product Is Giftcard,Product Discountable,Product Thumbnail,Product Collection Id,Product Type Id,Product Weight,Product Length,Product Height,Product Width,Product Hs Code,Product Origin Country,Product Mid Code,Product Material,Product Created At,Product Updated At,Product Deleted At,Product Image 1,Product Image 2,Product Tag 1,Variant Id,Variant Title,Variant Sku,Variant Barcode,Variant Ean,Variant Upc,Variant Allow Backorder,Variant Manage Inventory,Variant Hs Code,Variant Origin Country,Variant Mid Code,Variant Material,Variant Weight,Variant Length,Variant Height,Variant Width,Variant Metadata,Variant Variant Rank,Variant Product Id,Variant Created At,Variant Updated At,Variant Deleted At,Variant Price USD,Variant Price EUR,Variant Price DKK,Variant Option 1 Name,Variant Option 1 Value,Variant Option 2 Name,Variant Option 2 Value,Product field,Shipping Profile Id
|
||||||
prod_01J3CSN791SN1RN7X155Z8S9CN,Proposed product,,proposed,,test-product-description,proposed-product,false,true,test-image.png,,ptyp_01J3CSN76GCRSCDV9V489B5FWQ,,,,,,,,,2024-07-22T08:41:47.040Z,2024-07-22T08:41:47.040Z,,test-image.png,test-image-2.png,new-tag,variant_01J3CSN79CQ2ND94SRJSXMEMNH,Test variant,,,,,false,true,,,,,,,,,,0,prod_01J3CSN791SN1RN7X155Z8S9CN,2024-07-22T08:41:47.053Z,2024-07-22T08:41:47.053Z,,100,45,30,size,large,color,green,someval
|
prod_01J3CSN791SN1RN7X155Z8S9CN,Proposed product,,proposed,,test-product-description,proposed-product,false,true,test-image.png,,ptyp_01J3CSN76GCRSCDV9V489B5FWQ,,,,,,,,,2024-07-22T08:41:47.040Z,2024-07-22T08:41:47.040Z,,test-image.png,test-image-2.png,new-tag,variant_01J3CSN79CQ2ND94SRJSXMEMNH,Test variant,,,,,false,true,,,,,,,,,,0,prod_01J3CSN791SN1RN7X155Z8S9CN,2024-07-22T08:41:47.053Z,2024-07-22T08:41:47.053Z,,100,45,30,size,large,color,green,someval,import-shipping-profile
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
Product Id,Product Title,Product Subtitle,Product Status,Product External Id,Product Description,Product Handle,Product Is Giftcard,Product Discountable,Product Thumbnail,Product Collection Id,Product Type Id,Product Weight,Product Length,Product Height,Product Width,Product Hs Code,Product Origin Country,Product Mid Code,Product Material,Product Created At,Product Updated At,Product Deleted At,Product Image 1,Product Image 2,Product Tag 1,Variant Id,Variant Title,Variant Sku,Variant Barcode,Variant Ean,Variant Upc,Variant Allow Backorder,Variant Manage Inventory,Variant Hs Code,Variant Origin Country,Variant Mid Code,Variant Material,Variant Weight,Variant Length,Variant Height,Variant Width,Variant Metadata,Variant Variant Rank,Variant Product Id,Variant Created At,Variant Updated At,Variant Deleted At,Variant Price USD,Variant Price EUR,Variant Price nonexistent [EUR],Variant Option 1 Name,Variant Option 1 Value,Variant Option 2 Name,Variant Option 2 Value
|
Product Id,Product Title,Product Subtitle,Product Status,Product External Id,Product Description,Product Handle,Product Is Giftcard,Product Discountable,Product Thumbnail,Product Collection Id,Product Type Id,Product Weight,Product Length,Product Height,Product Width,Product Hs Code,Product Origin Country,Product Mid Code,Product Material,Product Created At,Product Updated At,Product Deleted At,Product Image 1,Product Image 2,Product Tag 1,Variant Id,Variant Title,Variant Sku,Variant Barcode,Variant Ean,Variant Upc,Variant Allow Backorder,Variant Manage Inventory,Variant Hs Code,Variant Origin Country,Variant Mid Code,Variant Material,Variant Weight,Variant Length,Variant Height,Variant Width,Variant Metadata,Variant Variant Rank,Variant Product Id,Variant Created At,Variant Updated At,Variant Deleted At,Variant Price USD,Variant Price EUR,Variant Price nonexistent [EUR],Variant Option 1 Name,Variant Option 1 Value,Variant Option 2 Name,Variant Option 2 Value,Shipping Profile Id
|
||||||
prod_01J3CSN791SN1RN7X155Z8S9CN,Proposed product,,proposed,,test-product-description,proposed-product,false,true,test-image.png,,ptyp_01J3CSN76GCRSCDV9V489B5FWQ,,,,,,,,,2024-07-22T08:41:47.040Z,2024-07-22T08:41:47.040Z,,test-image.png,test-image-2.png,new-tag,variant_01J3CSN79CQ2ND94SRJSXMEMNH,Test variant,,,,,false,true,,,,,,,,,,0,prod_01J3CSN791SN1RN7X155Z8S9CN,2024-07-22T08:41:47.053Z,2024-07-22T08:41:47.053Z,,100,45,30,size,large,color,green
|
prod_01J3CSN791SN1RN7X155Z8S9CN,Proposed product,,proposed,,test-product-description,proposed-product,false,true,test-image.png,,ptyp_01J3CSN76GCRSCDV9V489B5FWQ,,,,,,,,,2024-07-22T08:41:47.040Z,2024-07-22T08:41:47.040Z,,test-image.png,test-image-2.png,new-tag,variant_01J3CSN79CQ2ND94SRJSXMEMNH,Test variant,,,,,false,true,,,,,,,,,,0,prod_01J3CSN791SN1RN7X155Z8S9CN,2024-07-22T08:41:47.053Z,2024-07-22T08:41:47.053Z,,100,45,30,size,large,color,green,import-shipping-profile
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
Product Id,Product Handle,Product Title,Product Status,Product Description,Product Subtitle,Product External Id,Product Thumbnail,Product Collection Id,Product Type Id,Product Created At,Product Deleted At,Product Discountable,Product Height,Product Hs Code,Product Image 1,Product Image 2,Product Is Giftcard,Product Length,Product Material,Product Mid Code,Product Origin Country,Product Tag 1,Product Tag 2,Product Updated At,Product Weight,Product Width,Variant Id,Variant Title,Variant Sku,Variant Upc,Variant Ean,Variant Hs Code,Variant Mid Code,Variant Manage Inventory,Variant Allow Backorder,Variant Barcode,Variant Created At,Variant Deleted At,Variant Height,Variant Length,Variant Material,Variant Metadata,Variant Option 1 Name,Variant Option 1 Value,Variant Option 2 Name,Variant Option 2 Value,Variant Origin Country,Variant Price Test Region [USD],Variant Price USD,Variant Product Id,Variant Updated At,Variant Variant Rank,Variant Weight,Variant Width
|
Product Id,Product Handle,Product Title,Product Status,Product Description,Product Subtitle,Product External Id,Product Thumbnail,Product Collection Id,Product Type Id,Product Created At,Product Deleted At,Product Discountable,Product Height,Product Hs Code,Product Image 1,Product Image 2,Product Is Giftcard,Product Length,Product Material,Product Mid Code,Product Origin Country,Product Tag 1,Product Tag 2,Product Updated At,Product Weight,Product Width,Variant Id,Variant Title,Variant Sku,Variant Upc,Variant Ean,Variant Hs Code,Variant Mid Code,Variant Manage Inventory,Variant Allow Backorder,Variant Barcode,Variant Created At,Variant Deleted At,Variant Height,Variant Length,Variant Material,Variant Metadata,Variant Option 1 Name,Variant Option 1 Value,Variant Option 2 Name,Variant Option 2 Value,Variant Origin Country,Variant Price Test Region [USD],Variant Price USD,Variant Product Id,Variant Updated At,Variant Variant Rank,Variant Weight,Variant Width,Shipping Profile Id
|
||||||
prod_01J44RRM579P1EY2ZNQVGN1THT,product-with-prices,Product with prices,draft,test-product-description,,,test-image.png,,,2024-07-31T16:07:56.325Z,,true,,,test-image.png,test-image-2.png,false,,,,,123,456,2024-07-31T16:07:56.325Z,,,variant_01J44RRM5J1569XJD39DM3PNFN,Test variant,,,,,,true,false,,2024-07-31T16:07:56.338Z,,,,,,size,large,color,green,,45,100,prod_01J44RRM579P1EY2ZNQVGN1THT,2024-07-31T16:07:56.338Z,0,,
|
prod_01J44RRM579P1EY2ZNQVGN1THT,product-with-prices,Product with prices,draft,test-product-description,,,test-image.png,,,2024-07-31T16:07:56.325Z,,true,,,test-image.png,test-image-2.png,false,,,,,123,456,2024-07-31T16:07:56.325Z,,,variant_01J44RRM5J1569XJD39DM3PNFN,Test variant,,,,,,true,false,,2024-07-31T16:07:56.338Z,,,,,,size,large,color,green,,45,100,prod_01J44RRM579P1EY2ZNQVGN1THT,2024-07-31T16:07:56.338Z,0,,,import-shipping-profile
|
||||||
|
+3
-3
@@ -1,5 +1,5 @@
|
|||||||
Product Id,Product Handle,Product Title,Product Status,Product Description,Product Subtitle,Product External Id,Product Thumbnail,Product Collection Id,Product Type Id,Product Category 1,Product Created At,Product Deleted At,Product Discountable,Product Height,Product Hs Code,Product Image 1,Product Image 2,Product Is Giftcard,Product Length,Product Material,Product Mid Code,Product Origin Country,Product Tag 1,Product Tag 2,Product Updated At,Product Weight,Product Width,Variant Id,Variant Title,Variant Sku,Variant Upc,Variant Ean,Variant Hs Code,Variant Mid Code,Variant Manage Inventory,Variant Allow Backorder,Variant Barcode,Variant Created At,Variant Deleted At,Variant Height,Variant Length,Variant Material,Variant Metadata,Variant Option 1 Name,Variant Option 1 Value,Variant Option 2 Name,Variant Option 2 Value,Variant Origin Country,Variant Price DKK,Variant Price EUR,Variant Price USD,Variant Product Id,Variant Updated At,Variant Variant Rank,Variant Weight,Variant Width
|
Product Id,Product Handle,Product Title,Product Status,Product Description,Product Subtitle,Product External Id,Product Thumbnail,Product Collection Id,Product Type Id,Product Category 1,Product Created At,Product Deleted At,Product Discountable,Product Height,Product Hs Code,Product Image 1,Product Image 2,Product Is Giftcard,Product Length,Product Material,Product Mid Code,Product Origin Country,Product Tag 1,Product Tag 2,Product Updated At,Product Weight,Product Width,Variant Id,Variant Title,Variant Sku,Variant Upc,Variant Ean,Variant Hs Code,Variant Mid Code,Variant Manage Inventory,Variant Allow Backorder,Variant Barcode,Variant Created At,Variant Deleted At,Variant Height,Variant Length,Variant Material,Variant Metadata,Variant Option 1 Name,Variant Option 1 Value,Variant Option 2 Name,Variant Option 2 Value,Variant Origin Country,Variant Price DKK,Variant Price EUR,Variant Price USD,Variant Product Id,Variant Updated At,Variant Variant Rank,Variant Weight,Variant Width,Shipping Profile Id
|
||||||
prod_01J44RRKH4HH2SANJ0S05YM853,base-product,Base product,draft,"test-product-description
|
prod_01J44RRKH4HH2SANJ0S05YM853,base-product,Base product,draft,"test-product-description
|
||||||
test line 2",,,test-image.png,pcol_01J44RRKG4P1RZ3CKAMBS760TD,ptyp_01J44RRKGHPQMJ1CRMW7MEN3P1,pcat_01J44RRKGS2N86A8V37ZJD877K,2024-07-31T16:07:55.681Z,,true,,,test-image.png,test-image-2.png,false,,,,,123,456,2024-07-31T16:07:55.681Z,,,variant_01J44RRKHRQ7WJ902X4936GEK7,Test variant,,,,,,true,false,,2024-07-31T16:07:55.704Z,,,,,,size,large,color,green,,30,45,100,prod_01J44RRKH4HH2SANJ0S05YM853,2024-07-31T16:07:55.704Z,0,,
|
test line 2",,,test-image.png,pcol_01J44RRKG4P1RZ3CKAMBS760TD,ptyp_01J44RRKGHPQMJ1CRMW7MEN3P1,pcat_01J44RRKGS2N86A8V37ZJD877K,2024-07-31T16:07:55.681Z,,true,,,test-image.png,test-image-2.png,false,,,,,123,456,2024-07-31T16:07:55.681Z,,,variant_01J44RRKHRQ7WJ902X4936GEK7,Test variant,,,,,,true,false,,2024-07-31T16:07:55.704Z,,,,,,size,large,color,green,,30,45,100,prod_01J44RRKH4HH2SANJ0S05YM853,2024-07-31T16:07:55.704Z,0,,,import-shipping-profile
|
||||||
prod_01J44RRKH4HH2SANJ0S05YM853,base-product,Base product,draft,"test-product-description
|
prod_01J44RRKH4HH2SANJ0S05YM853,base-product,Base product,draft,"test-product-description
|
||||||
test line 2",,,test-image.png,pcol_01J44RRKG4P1RZ3CKAMBS760TD,ptyp_01J44RRKGHPQMJ1CRMW7MEN3P1,pcat_01J44RRKGS2N86A8V37ZJD877K,2024-07-31T16:07:55.681Z,,true,,,test-image.png,test-image-2.png,false,,,,,123,456,2024-07-31T16:07:55.681Z,,,variant_01J44RRKHRAYY7Q7NTXNNMDA1S,Test variant 2,,,,,,true,false,,2024-07-31T16:07:55.704Z,,,,,,size,small,color,green,,50,65,200,prod_01J44RRKH4HH2SANJ0S05YM853,2024-07-31T16:07:55.704Z,0,,
|
test line 2",,,test-image.png,pcol_01J44RRKG4P1RZ3CKAMBS760TD,ptyp_01J44RRKGHPQMJ1CRMW7MEN3P1,pcat_01J44RRKGS2N86A8V37ZJD877K,2024-07-31T16:07:55.681Z,,true,,,test-image.png,test-image-2.png,false,,,,,123,456,2024-07-31T16:07:55.681Z,,,variant_01J44RRKHRAYY7Q7NTXNNMDA1S,Test variant 2,,,,,,true,false,,2024-07-31T16:07:55.704Z,,,,,,size,small,color,green,,50,65,200,prod_01J44RRKH4HH2SANJ0S05YM853,2024-07-31T16:07:55.704Z,0,,,import-shipping-profile
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
Product Id,Product Title,Product Subtitle,Product Status,Product External Id,Product Description,Product Handle,Product Is Giftcard,Product Discountable,Product Thumbnail,Product Collection Id,Product Type Id,Product Weight,Product Length,Product Height,Product Width,Product Hs Code,Product Origin Country,Product Mid Code,Product Material,Product Created At,Product Updated At,Product Deleted At,Product Image 1,Product Image 2,Product Tag 1,Variant Id,Variant Title,Variant Sku,Variant Barcode,Variant Ean,Variant Upc,Variant Allow Backorder,Variant Manage Inventory,Variant Hs Code,Variant Origin Country,Variant Mid Code,Variant Material,Variant Weight,Variant Length,Variant Height,Variant Width,Variant Metadata,Variant Variant Rank,Variant Product Id,Variant Created At,Variant Updated At,Variant Deleted At,Variant Price USD,Variant Price EUR,Variant Price DKK,Variant Option 1 Name,Variant Option 1 Value,Variant Option 2 Name,Variant Option 2 Value,Some field
|
Product Id,Product Title,Product Subtitle,Product Status,Product External Id,Product Description,Product Handle,Product Is Giftcard,Product Discountable,Product Thumbnail,Product Collection Id,Product Type Id,Product Weight,Product Length,Product Height,Product Width,Product Hs Code,Product Origin Country,Product Mid Code,Product Material,Product Created At,Product Updated At,Product Deleted At,Product Image 1,Product Image 2,Product Tag 1,Variant Id,Variant Title,Variant Sku,Variant Barcode,Variant Ean,Variant Upc,Variant Allow Backorder,Variant Manage Inventory,Variant Hs Code,Variant Origin Country,Variant Mid Code,Variant Material,Variant Weight,Variant Length,Variant Height,Variant Width,Variant Metadata,Variant Variant Rank,Variant Product Id,Variant Created At,Variant Updated At,Variant Deleted At,Variant Price USD,Variant Price EUR,Variant Price DKK,Variant Option 1 Name,Variant Option 1 Value,Variant Option 2 Name,Variant Option 2 Value,Some field,Shipping Profile Id
|
||||||
prod_01J3CSN791SN1RN7X155Z8S9CN,Proposed product,,proposed,,test-product-description,proposed-product,false,true,test-image.png,,ptyp_01J3CSN76GCRSCDV9V489B5FWQ,,,,,,,,,2024-07-22T08:41:47.040Z,2024-07-22T08:41:47.040Z,,test-image.png,test-image-2.png,new-tag,variant_01J3CSN79CQ2ND94SRJSXMEMNH,Test variant,,,,,false,true,,,,,,,,,,0,prod_01J3CSN791SN1RN7X155Z8S9CN,2024-07-22T08:41:47.053Z,2024-07-22T08:41:47.053Z,,100,45,30,size,large,color,green,someval
|
prod_01J3CSN791SN1RN7X155Z8S9CN,Proposed product,,proposed,,test-product-description,proposed-product,false,true,test-image.png,,ptyp_01J3CSN76GCRSCDV9V489B5FWQ,,,,,,,,,2024-07-22T08:41:47.040Z,2024-07-22T08:41:47.040Z,,test-image.png,test-image-2.png,new-tag,variant_01J3CSN79CQ2ND94SRJSXMEMNH,Test variant,,,,,false,true,,,,,,,,,,0,prod_01J3CSN791SN1RN7X155Z8S9CN,2024-07-22T08:41:47.053Z,2024-07-22T08:41:47.053Z,,100,45,30,size,large,color,green,someval,import-shipping-profile
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
Product Id,Product Handle,Product Title,Product Subtitle,Product Description,Product Status,Product Thumbnail,Product Weight,Product Length,Product Width,Product Height,Product HS Code,Product Origin Country,Product MID Code,Product Material,Product Collection Title,Product Collection Handle,Product Type,Product Tags,Product Discountable,Product External Id,Variant Id,Variant Title,Variant SKU,Variant Barcode,Variant Inventory Quantity,Variant Allow Backorder,Variant Manage Inventory,Variant Weight,Variant Length,Variant Width,Variant Height,Variant HS Code,Variant Origin Country,Variant MID Code,Variant Material,Price Test region [USD],Price USD,Option 1 Name,Option 1 Value,Option 2 Name,Option 2 Value,Image 1 Url
|
Product Id,Product Handle,Product Title,Product Subtitle,Product Description,Product Status,Product Thumbnail,Product Weight,Product Length,Product Width,Product Height,Product HS Code,Product Origin Country,Product MID Code,Product Material,Product Collection Title,Product Collection Handle,Product Type,Product Tags,Product Discountable,Product External Id,Variant Id,Variant Title,Variant SKU,Variant Barcode,Variant Inventory Quantity,Variant Allow Backorder,Variant Manage Inventory,Variant Weight,Variant Length,Variant Width,Variant Height,Variant HS Code,Variant Origin Country,Variant MID Code,Variant Material,Price Test region [USD],Price USD,Option 1 Name,Option 1 Value,Option 2 Name,Option 2 Value,Image 1 Url,Shipping Profile Id
|
||||||
,test-product-product-1,Test product,,"Hopper Stripes Bedding, available as duvet cover, pillow sham and sheet.\n100% organic cotton, soft and crisp to the touch. Made in Portugal.",draft,,,,,,,,,,Test collection 1,test-collection1,,123_1,TRUE,,,Test variant,test-sku-1,test-barcode-1,10,FALSE,TRUE,,,,,,,,,1.00,1.10,test-option-1,option 1 value red,test-option-2,option 2 value 1,test-image.png
|
,test-product-product-1,Test product,,"Hopper Stripes Bedding, available as duvet cover, pillow sham and sheet.\n100% organic cotton, soft and crisp to the touch. Made in Portugal.",draft,,,,,,,,,,Test collection 1,test-collection1,,123_1,TRUE,,,Test variant,test-sku-1,test-barcode-1,10,FALSE,TRUE,,,,,,,,,1.00,1.10,test-option-1,option 1 value red,test-option-2,option 2 value 1,test-image.png,import-shipping-profile
|
||||||
,test-product-product-1-1,Test product,,"Hopper Stripes Bedding, available as duvet cover, pillow sham and sheet.\n100% organic cotton, soft and crisp to the touch. Made in Portugal.",draft,,,,,,,,,,Test collection 1,test-collection1,,,TRUE,,,Test variant,test-sku-1-1,test-barcode-1-1,10,FALSE,TRUE,,,,,,,,,1.00,1.10,test-option-1,option 1 value red,test-option-2,option 2 value 1,test-image.png
|
,test-product-product-1-1,Test product,,"Hopper Stripes Bedding, available as duvet cover, pillow sham and sheet.\n100% organic cotton, soft and crisp to the touch. Made in Portugal.",draft,,,,,,,,,,Test collection 1,test-collection1,,,TRUE,,,Test variant,test-sku-1-1,test-barcode-1-1,10,FALSE,TRUE,,,,,,,,,1.00,1.10,test-option-1,option 1 value red,test-option-2,option 2 value 1,test-image.png,import-shipping-profile
|
||||||
existing-product-id,test-product-product-2,Test product,,test-product-description,draft,test-image.png,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,,Test variant,test-sku-2,test-barcode-2,10,FALSE,TRUE,,,,,,,,,,1.10,Size,Small,,,test-image.png
|
existing-product-id,test-product-product-2,Test product,,test-product-description,draft,test-image.png,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,,Test variant,test-sku-2,test-barcode-2,10,FALSE,TRUE,,,,,,,,,,1.10,Size,Small,,,test-image.png,import-shipping-profile
|
||||||
existing-product-id,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,,Test variant,test-sku-3,test-barcode-3,10,FALSE,TRUE,,,,,,,,,,1.20,Size,Medium,,,test-image.png
|
existing-product-id,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,,Test variant,test-sku-3,test-barcode-3,10,FALSE,TRUE,,,,,,,,,,1.20,Size,Medium,,,test-image.png,import-shipping-profile
|
||||||
existing-product-id,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,existing-variant-id,Test variant changed,test-sku-4,test-barcode-4,10,FALSE,TRUE,,,,,,,,,,,Size,Large,,,test-image.png
|
existing-product-id,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,existing-variant-id,Test variant changed,test-sku-4,test-barcode-4,10,FALSE,TRUE,,,,,,,,,,,Size,Large,,,test-image.png,import-shipping-profile
|
||||||
|
@@ -39,6 +39,9 @@ const compareCSVs = async (filePath, expectedFilePath) => {
|
|||||||
fileContent = fileContent.replace(dateRegex, "<DATE>")
|
fileContent = fileContent.replace(dateRegex, "<DATE>")
|
||||||
fixturesContent = fixturesContent.replace(dateRegex, "<DATE>")
|
fixturesContent = fixturesContent.replace(dateRegex, "<DATE>")
|
||||||
|
|
||||||
|
fixturesContent = fixturesContent.replace(/,Shipping Profile Id*/g, "")
|
||||||
|
fixturesContent = fixturesContent.replace(/,import-shipping-profile*/g, "")
|
||||||
|
|
||||||
expect(fileContent).toEqual(fixturesContent)
|
expect(fileContent).toEqual(fixturesContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +59,7 @@ medusaIntegrationTestRunner({
|
|||||||
let baseTag1
|
let baseTag1
|
||||||
let baseTag2
|
let baseTag2
|
||||||
let newTag
|
let newTag
|
||||||
|
let shippingProfile
|
||||||
|
|
||||||
let eventBus: IEventBusModuleService
|
let eventBus: IEventBusModuleService
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
@@ -92,6 +96,14 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
).data.collection
|
).data.collection
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "Test", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
baseType = (
|
baseType = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/product-types",
|
"/admin/product-types",
|
||||||
@@ -130,6 +142,7 @@ medusaIntegrationTestRunner({
|
|||||||
getProductFixture({
|
getProductFixture({
|
||||||
title: "Base product",
|
title: "Base product",
|
||||||
description: "test-product-description\ntest line 2",
|
description: "test-product-description\ntest line 2",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
collection_id: baseCollection.id,
|
collection_id: baseCollection.id,
|
||||||
type_id: baseType.id,
|
type_id: baseType.id,
|
||||||
categories: [{ id: baseCategory.id }],
|
categories: [{ id: baseCategory.id }],
|
||||||
@@ -191,6 +204,7 @@ medusaIntegrationTestRunner({
|
|||||||
status: "proposed",
|
status: "proposed",
|
||||||
tags: [{ id: newTag.id }],
|
tags: [{ id: newTag.id }],
|
||||||
type_id: baseType.id,
|
type_id: baseType.id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
}),
|
}),
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
@@ -281,6 +295,7 @@ medusaIntegrationTestRunner({
|
|||||||
"/admin/products",
|
"/admin/products",
|
||||||
getProductFixture({
|
getProductFixture({
|
||||||
title: "Product with prices",
|
title: "Product with prices",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ medusaIntegrationTestRunner({
|
|||||||
let baseTag2
|
let baseTag2
|
||||||
let baseTag3
|
let baseTag3
|
||||||
let newTag
|
let newTag
|
||||||
|
let shippingProfile
|
||||||
|
|
||||||
let eventBus: IEventBusModuleService
|
let eventBus: IEventBusModuleService
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
@@ -84,12 +85,21 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
).data.product_tag
|
).data.product_tag
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "Test", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
baseProduct = (
|
baseProduct = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/products",
|
"/admin/products",
|
||||||
getProductFixture({
|
getProductFixture({
|
||||||
title: "Base product",
|
title: "Base product",
|
||||||
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
}),
|
}),
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
@@ -150,6 +160,11 @@ medusaIntegrationTestRunner({
|
|||||||
fileContent = fileContent.replace(/pcol_\w*\d*/g, baseCollection.id)
|
fileContent = fileContent.replace(/pcol_\w*\d*/g, baseCollection.id)
|
||||||
fileContent = fileContent.replace(/ptyp_\w*\d*/g, baseType.id)
|
fileContent = fileContent.replace(/ptyp_\w*\d*/g, baseType.id)
|
||||||
|
|
||||||
|
fileContent = fileContent.replace(
|
||||||
|
/import-shipping-profile*/g,
|
||||||
|
shippingProfile.id
|
||||||
|
)
|
||||||
|
|
||||||
const { form, meta } = getUploadReq({
|
const { form, meta } = getUploadReq({
|
||||||
name: "test.csv",
|
name: "test.csv",
|
||||||
content: fileContent,
|
content: fileContent,
|
||||||
@@ -400,6 +415,11 @@ medusaIntegrationTestRunner({
|
|||||||
fileContent = fileContent.replace(/ptyp_\w*\d*/g, baseType.id)
|
fileContent = fileContent.replace(/ptyp_\w*\d*/g, baseType.id)
|
||||||
fileContent = fileContent.replace(/pcat_\w*\d*/g, baseCategory.id)
|
fileContent = fileContent.replace(/pcat_\w*\d*/g, baseCategory.id)
|
||||||
|
|
||||||
|
fileContent = fileContent.replace(
|
||||||
|
/import-shipping-profile*/g,
|
||||||
|
shippingProfile.id
|
||||||
|
)
|
||||||
|
|
||||||
const { form, meta } = getUploadReq({
|
const { form, meta } = getUploadReq({
|
||||||
name: "test.csv",
|
name: "test.csv",
|
||||||
content: fileContent,
|
content: fileContent,
|
||||||
@@ -440,6 +460,11 @@ medusaIntegrationTestRunner({
|
|||||||
{ encoding: "utf-8" }
|
{ encoding: "utf-8" }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fileContent = fileContent.replace(
|
||||||
|
/import-shipping-profile*/g,
|
||||||
|
shippingProfile.id
|
||||||
|
)
|
||||||
|
|
||||||
const { form, meta } = getUploadReq({
|
const { form, meta } = getUploadReq({
|
||||||
name: "test.csv",
|
name: "test.csv",
|
||||||
content: fileContent,
|
content: fileContent,
|
||||||
@@ -467,6 +492,11 @@ medusaIntegrationTestRunner({
|
|||||||
fileContent = fileContent.replace(/pcol_\w*\d*/g, baseCollection.id)
|
fileContent = fileContent.replace(/pcol_\w*\d*/g, baseCollection.id)
|
||||||
fileContent = fileContent.replace(/ptyp_\w*\d*/g, baseType.id)
|
fileContent = fileContent.replace(/ptyp_\w*\d*/g, baseType.id)
|
||||||
|
|
||||||
|
fileContent = fileContent.replace(
|
||||||
|
/import-shipping-profile*/g,
|
||||||
|
shippingProfile.id
|
||||||
|
)
|
||||||
|
|
||||||
const { form, meta } = getUploadReq({
|
const { form, meta } = getUploadReq({
|
||||||
name: "test.csv",
|
name: "test.csv",
|
||||||
content: fileContent,
|
content: fileContent,
|
||||||
@@ -517,6 +547,11 @@ medusaIntegrationTestRunner({
|
|||||||
fileContent = fileContent.replace(/pcol_\w*\d*/g, baseCollection.id)
|
fileContent = fileContent.replace(/pcol_\w*\d*/g, baseCollection.id)
|
||||||
fileContent = fileContent.replace(/ptyp_\w*\d*/g, baseType.id)
|
fileContent = fileContent.replace(/ptyp_\w*\d*/g, baseType.id)
|
||||||
|
|
||||||
|
fileContent = fileContent.replace(
|
||||||
|
/import-shipping-profile*/g,
|
||||||
|
shippingProfile.id
|
||||||
|
)
|
||||||
|
|
||||||
const { form, meta } = getUploadReq({
|
const { form, meta } = getUploadReq({
|
||||||
name: "test.csv",
|
name: "test.csv",
|
||||||
content: fileContent,
|
content: fileContent,
|
||||||
@@ -582,6 +617,11 @@ medusaIntegrationTestRunner({
|
|||||||
baseCollection.handle
|
baseCollection.handle
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fileContent = fileContent.replace(
|
||||||
|
/import-shipping-profile*/g,
|
||||||
|
shippingProfile.id
|
||||||
|
)
|
||||||
|
|
||||||
const { form, meta } = getUploadReq({
|
const { form, meta } = getUploadReq({
|
||||||
name: "test.csv",
|
name: "test.csv",
|
||||||
content: fileContent,
|
content: fileContent,
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ medusaIntegrationTestRunner({
|
|||||||
let baseTag2
|
let baseTag2
|
||||||
let newTag
|
let newTag
|
||||||
|
|
||||||
|
let shippingProfile
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
||||||
|
|
||||||
@@ -65,6 +67,14 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
).data.product_tag
|
).data.product_tag
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "default", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
baseProduct = (
|
baseProduct = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/products",
|
"/admin/products",
|
||||||
@@ -74,6 +84,7 @@ medusaIntegrationTestRunner({
|
|||||||
// BREAKING: Type input changed from {type: {value: string}} to {type_id: string}
|
// BREAKING: Type input changed from {type: {value: string}} to {type_id: string}
|
||||||
type_id: baseType.id,
|
type_id: baseType.id,
|
||||||
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
images: [
|
images: [
|
||||||
{
|
{
|
||||||
url: "image-one",
|
url: "image-one",
|
||||||
@@ -95,6 +106,7 @@ medusaIntegrationTestRunner({
|
|||||||
status: "proposed",
|
status: "proposed",
|
||||||
tags: [{ id: newTag.id }],
|
tags: [{ id: newTag.id }],
|
||||||
type_id: baseType.id,
|
type_id: baseType.id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
}),
|
}),
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
@@ -108,6 +120,7 @@ medusaIntegrationTestRunner({
|
|||||||
status: "published",
|
status: "published",
|
||||||
collection_id: publishedCollection.id,
|
collection_id: publishedCollection.id,
|
||||||
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
}),
|
}),
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
@@ -115,7 +128,10 @@ medusaIntegrationTestRunner({
|
|||||||
deletedProduct = (
|
deletedProduct = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/products",
|
"/admin/products",
|
||||||
getProductFixture({ title: "Deleted product" }),
|
getProductFixture({
|
||||||
|
title: "Deleted product",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
}),
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
).data.product
|
).data.product
|
||||||
@@ -605,6 +621,7 @@ medusaIntegrationTestRunner({
|
|||||||
is_giftcard: true,
|
is_giftcard: true,
|
||||||
description: "test-giftcard-description",
|
description: "test-giftcard-description",
|
||||||
options: [{ title: "Denominations", values: ["100"] }],
|
options: [{ title: "Denominations", values: ["100"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Test variant",
|
title: "Test variant",
|
||||||
@@ -693,6 +710,7 @@ medusaIntegrationTestRunner({
|
|||||||
is_giftcard: true,
|
is_giftcard: true,
|
||||||
description: "test-giftcard-description",
|
description: "test-giftcard-description",
|
||||||
options: [{ title: "size", values: ["x", "l"] }],
|
options: [{ title: "size", values: ["x", "l"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Test variant",
|
title: "Test variant",
|
||||||
@@ -912,6 +930,7 @@ medusaIntegrationTestRunner({
|
|||||||
getProductFixture({
|
getProductFixture({
|
||||||
title: "Test saleschannel",
|
title: "Test saleschannel",
|
||||||
sales_channels: [{ id: salesChannel.id }],
|
sales_channels: [{ id: salesChannel.id }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
}),
|
}),
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
@@ -1098,6 +1117,7 @@ medusaIntegrationTestRunner({
|
|||||||
title: "Test product - 1",
|
title: "Test product - 1",
|
||||||
handle: "test-1",
|
handle: "test-1",
|
||||||
options: [{ title: "size", values: ["x", "l"] }],
|
options: [{ title: "size", values: ["x", "l"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Custom inventory 1",
|
title: "Custom inventory 1",
|
||||||
@@ -1141,6 +1161,7 @@ medusaIntegrationTestRunner({
|
|||||||
title: "Test product - 1",
|
title: "Test product - 1",
|
||||||
handle: "test-1",
|
handle: "test-1",
|
||||||
options: [{ title: "size", values: ["x", "l"] }],
|
options: [{ title: "size", values: ["x", "l"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Custom inventory 1",
|
title: "Custom inventory 1",
|
||||||
@@ -1202,6 +1223,7 @@ medusaIntegrationTestRunner({
|
|||||||
collection_id: baseCollection.id,
|
collection_id: baseCollection.id,
|
||||||
type_id: baseType.id,
|
type_id: baseType.id,
|
||||||
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
adminHeaders
|
adminHeaders
|
||||||
@@ -1341,6 +1363,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
title: "Test create",
|
title: "Test create",
|
||||||
options: [{ title: "size", values: ["x", "l"] }],
|
options: [{ title: "size", values: ["x", "l"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Price with rules",
|
title: "Price with rules",
|
||||||
@@ -1394,6 +1417,7 @@ medusaIntegrationTestRunner({
|
|||||||
collection_id: baseCollection.id,
|
collection_id: baseCollection.id,
|
||||||
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
||||||
options: [{ title: "size", values: ["large"] }],
|
options: [{ title: "size", values: ["large"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Test variant",
|
title: "Test variant",
|
||||||
@@ -1425,7 +1449,8 @@ medusaIntegrationTestRunner({
|
|||||||
images: [{ url: "test-image.png" }, { url: "test-image-2.png" }],
|
images: [{ url: "test-image.png" }, { url: "test-image-2.png" }],
|
||||||
collection_id: baseCollection.id,
|
collection_id: baseCollection.id,
|
||||||
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
||||||
options: [{ title: "size", values: ["l", x] }],
|
options: [{ title: "size", values: ["l", "x"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Test variant 1",
|
title: "Test variant 1",
|
||||||
@@ -1477,6 +1502,7 @@ medusaIntegrationTestRunner({
|
|||||||
is_giftcard: true,
|
is_giftcard: true,
|
||||||
description: "test-giftcard-description",
|
description: "test-giftcard-description",
|
||||||
options: [{ title: "size", values: ["large"] }],
|
options: [{ title: "size", values: ["large"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Test variant",
|
title: "Test variant",
|
||||||
@@ -1637,6 +1663,7 @@ medusaIntegrationTestRunner({
|
|||||||
|
|
||||||
it("updates product variants (update price on existing variant, create new variant)", async () => {
|
it("updates product variants (update price on existing variant, create new variant)", async () => {
|
||||||
const payload = {
|
const payload = {
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
id: baseProduct.variants[0].id,
|
id: baseProduct.variants[0].id,
|
||||||
@@ -1815,6 +1842,7 @@ medusaIntegrationTestRunner({
|
|||||||
"/admin/products",
|
"/admin/products",
|
||||||
getProductFixture({
|
getProductFixture({
|
||||||
title: "Test metadata",
|
title: "Test metadata",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
metadata: {
|
metadata: {
|
||||||
"test-key": "test-value",
|
"test-key": "test-value",
|
||||||
"test-key-2": "test-value-2",
|
"test-key-2": "test-value-2",
|
||||||
@@ -1877,6 +1905,7 @@ medusaIntegrationTestRunner({
|
|||||||
getProductFixture({
|
getProductFixture({
|
||||||
title: "Test saleschannel",
|
title: "Test saleschannel",
|
||||||
sales_channels: [{ id: salesChannel1.id }],
|
sales_channels: [{ id: salesChannel1.id }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
}),
|
}),
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
@@ -2045,7 +2074,10 @@ medusaIntegrationTestRunner({
|
|||||||
const plainProduct = (
|
const plainProduct = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/products",
|
"/admin/products",
|
||||||
{ title: "Test variant order" },
|
{
|
||||||
|
title: "Test variant order",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
},
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
).data.product
|
).data.product
|
||||||
@@ -2152,6 +2184,7 @@ medusaIntegrationTestRunner({
|
|||||||
title: "Test product - 1",
|
title: "Test product - 1",
|
||||||
handle: "test-1",
|
handle: "test-1",
|
||||||
options: [{ title: "size", values: ["x", "l"] }],
|
options: [{ title: "size", values: ["x", "l"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Custom inventory 1",
|
title: "Custom inventory 1",
|
||||||
@@ -2265,6 +2298,7 @@ medusaIntegrationTestRunner({
|
|||||||
title: "Test product - 1",
|
title: "Test product - 1",
|
||||||
handle: "test-1",
|
handle: "test-1",
|
||||||
options: [{ title: "size", values: ["x", "l"] }],
|
options: [{ title: "size", values: ["x", "l"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Custom inventory 1",
|
title: "Custom inventory 1",
|
||||||
@@ -2316,6 +2350,7 @@ medusaIntegrationTestRunner({
|
|||||||
title: "Test product - 1",
|
title: "Test product - 1",
|
||||||
handle: "test-1",
|
handle: "test-1",
|
||||||
options: [{ title: "size", values: ["x", "l"] }],
|
options: [{ title: "size", values: ["x", "l"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Custom inventory 1",
|
title: "Custom inventory 1",
|
||||||
@@ -2490,6 +2525,7 @@ medusaIntegrationTestRunner({
|
|||||||
title: "Test product - 1",
|
title: "Test product - 1",
|
||||||
handle: "test-1",
|
handle: "test-1",
|
||||||
options: [{ title: "size", values: ["l"] }],
|
options: [{ title: "size", values: ["l"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Custom inventory 1",
|
title: "Custom inventory 1",
|
||||||
@@ -2520,6 +2556,7 @@ medusaIntegrationTestRunner({
|
|||||||
title: "Test product - 2",
|
title: "Test product - 2",
|
||||||
handle: "test-2",
|
handle: "test-2",
|
||||||
options: [{ title: "size", values: ["l"] }],
|
options: [{ title: "size", values: ["l"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "W/ shared inventory item",
|
title: "W/ shared inventory item",
|
||||||
@@ -2604,6 +2641,7 @@ medusaIntegrationTestRunner({
|
|||||||
title: "Test product - 1",
|
title: "Test product - 1",
|
||||||
handle: "test-1",
|
handle: "test-1",
|
||||||
options: [{ title: "size", values: ["l"] }],
|
options: [{ title: "size", values: ["l"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Custom inventory 1",
|
title: "Custom inventory 1",
|
||||||
@@ -2740,6 +2778,7 @@ medusaIntegrationTestRunner({
|
|||||||
title: baseProduct.title,
|
title: baseProduct.title,
|
||||||
handle: baseProduct.handle,
|
handle: baseProduct.handle,
|
||||||
options: [{ title: "size", values: ["x", "l"] }],
|
options: [{ title: "size", values: ["x", "l"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Test variant",
|
title: "Test variant",
|
||||||
@@ -2772,6 +2811,7 @@ medusaIntegrationTestRunner({
|
|||||||
handle: baseProduct.handle,
|
handle: baseProduct.handle,
|
||||||
description: "test-product-description",
|
description: "test-product-description",
|
||||||
options: [{ title: "size", values: ["x", "l"] }],
|
options: [{ title: "size", values: ["x", "l"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Test variant",
|
title: "Test variant",
|
||||||
@@ -2948,6 +2988,7 @@ medusaIntegrationTestRunner({
|
|||||||
const createPayload = getProductFixture({
|
const createPayload = getProductFixture({
|
||||||
title: "Test batch create",
|
title: "Test batch create",
|
||||||
handle: "test-batch-create",
|
handle: "test-batch-create",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
const updatePayload = {
|
const updatePayload = {
|
||||||
@@ -3006,6 +3047,7 @@ medusaIntegrationTestRunner({
|
|||||||
const productWithMultipleVariants = getProductFixture({
|
const productWithMultipleVariants = getProductFixture({
|
||||||
title: "Test batch variants",
|
title: "Test batch variants",
|
||||||
handle: "test-batch-variants",
|
handle: "test-batch-variants",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Variant 1",
|
title: "Variant 1",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ medusaIntegrationTestRunner({
|
|||||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||||
let baseProduct
|
let baseProduct
|
||||||
let baseRegion
|
let baseRegion
|
||||||
|
let shippingProfile
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
||||||
// BREAKING: Creating a region no longer takes tax_rate, payment_providers, fulfillment_providers, countriesr
|
// BREAKING: Creating a region no longer takes tax_rate, payment_providers, fulfillment_providers, countriesr
|
||||||
@@ -26,11 +26,20 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
).data.region
|
).data.region
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "default", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
baseProduct = (
|
baseProduct = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/products",
|
"/admin/products",
|
||||||
getProductFixture({
|
getProductFixture({
|
||||||
title: "Base product",
|
title: "Base product",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
}),
|
}),
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
@@ -67,6 +76,7 @@ medusaIntegrationTestRunner({
|
|||||||
{ title: "First variant", prices: [] },
|
{ title: "First variant", prices: [] },
|
||||||
{ title: "Second variant", prices: [] },
|
{ title: "Second variant", prices: [] },
|
||||||
],
|
],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
}),
|
}),
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
@@ -801,6 +811,7 @@ medusaIntegrationTestRunner({
|
|||||||
"/admin/products",
|
"/admin/products",
|
||||||
{
|
{
|
||||||
title: "product 1",
|
title: "product 1",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
options: [
|
options: [
|
||||||
{ title: "size", values: ["large", "medium", "small"] },
|
{ title: "size", values: ["large", "medium", "small"] },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ medusaIntegrationTestRunner({
|
|||||||
let publishableKey
|
let publishableKey
|
||||||
let storeHeadersWithCustomer
|
let storeHeadersWithCustomer
|
||||||
let customer
|
let customer
|
||||||
|
let shippingProfile
|
||||||
|
|
||||||
const createProducts = async (data) => {
|
const createProducts = async (data) => {
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
@@ -135,6 +136,14 @@ medusaIntegrationTestRunner({
|
|||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
).data.region
|
).data.region
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "default", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Get products based on publishable key", () => {
|
describe("Get products based on publishable key", () => {
|
||||||
@@ -145,7 +154,11 @@ medusaIntegrationTestRunner({
|
|||||||
product1 = (
|
product1 = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/products",
|
"/admin/products",
|
||||||
getProductFixture({ title: "test1", status: "published" }),
|
getProductFixture({
|
||||||
|
title: "test1",
|
||||||
|
status: "published",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
}),
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
).data.product
|
).data.product
|
||||||
@@ -153,7 +166,11 @@ medusaIntegrationTestRunner({
|
|||||||
product2 = (
|
product2 = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/products",
|
"/admin/products",
|
||||||
getProductFixture({ title: "test2", status: "published" }),
|
getProductFixture({
|
||||||
|
title: "test2",
|
||||||
|
status: "published",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
}),
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
).data.product
|
).data.product
|
||||||
@@ -161,7 +178,11 @@ medusaIntegrationTestRunner({
|
|||||||
product3 = (
|
product3 = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/products",
|
"/admin/products",
|
||||||
getProductFixture({ title: "test3", status: "published" }),
|
getProductFixture({
|
||||||
|
title: "test3",
|
||||||
|
status: "published",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
}),
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
).data.product
|
).data.product
|
||||||
@@ -500,6 +521,7 @@ medusaIntegrationTestRunner({
|
|||||||
title: "test product 1",
|
title: "test product 1",
|
||||||
collection_id: collection.id,
|
collection_id: collection.id,
|
||||||
status: ProductStatus.PUBLISHED,
|
status: ProductStatus.PUBLISHED,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
options: [
|
options: [
|
||||||
{ title: "size", values: ["large", "small"] },
|
{ title: "size", values: ["large", "small"] },
|
||||||
{ title: "color", values: ["green"] },
|
{ title: "color", values: ["green"] },
|
||||||
@@ -538,6 +560,7 @@ medusaIntegrationTestRunner({
|
|||||||
;[product2, [variant2]] = await createProducts({
|
;[product2, [variant2]] = await createProducts({
|
||||||
title: "test product 2 uniquely",
|
title: "test product 2 uniquely",
|
||||||
status: ProductStatus.PUBLISHED,
|
status: ProductStatus.PUBLISHED,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
options: [
|
options: [
|
||||||
{ title: "size", values: ["large", "small"] },
|
{ title: "size", values: ["large", "small"] },
|
||||||
{ title: "material", values: ["cotton", "polyester"] },
|
{ title: "material", values: ["cotton", "polyester"] },
|
||||||
@@ -557,6 +580,7 @@ medusaIntegrationTestRunner({
|
|||||||
;[product3, [variant3]] = await createProducts({
|
;[product3, [variant3]] = await createProducts({
|
||||||
title: "product not in price list",
|
title: "product not in price list",
|
||||||
status: ProductStatus.PUBLISHED,
|
status: ProductStatus.PUBLISHED,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
options: [{ title: "size", values: ["large", "small"] }],
|
options: [{ title: "size", values: ["large", "small"] }],
|
||||||
variants: [
|
variants: [
|
||||||
{ title: "test variant 3", prices: [], options: { size: "large" } },
|
{ title: "test variant 3", prices: [], options: { size: "large" } },
|
||||||
@@ -565,6 +589,7 @@ medusaIntegrationTestRunner({
|
|||||||
;[product4, [variant4]] = await createProducts({
|
;[product4, [variant4]] = await createProducts({
|
||||||
title: "draft product",
|
title: "draft product",
|
||||||
status: ProductStatus.DRAFT,
|
status: ProductStatus.DRAFT,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
options: [{ title: "size", values: ["large", "small"] }],
|
options: [{ title: "size", values: ["large", "small"] }],
|
||||||
variants: [
|
variants: [
|
||||||
{ title: "test variant 4", prices: [], options: { size: "large" } },
|
{ title: "test variant 4", prices: [], options: { size: "large" } },
|
||||||
@@ -1727,6 +1752,7 @@ medusaIntegrationTestRunner({
|
|||||||
;[product, [variant]] = await createProducts({
|
;[product, [variant]] = await createProducts({
|
||||||
title: "test product 1",
|
title: "test product 1",
|
||||||
status: ProductStatus.PUBLISHED,
|
status: ProductStatus.PUBLISHED,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
options: [{ title: "size", values: ["large"] }],
|
options: [{ title: "size", values: ["large"] }],
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
@@ -2206,6 +2232,7 @@ medusaIntegrationTestRunner({
|
|||||||
getProductFixture({
|
getProductFixture({
|
||||||
title: "test1",
|
title: "test1",
|
||||||
status: "published",
|
status: "published",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Test taxes",
|
title: "Test taxes",
|
||||||
@@ -2239,6 +2266,7 @@ medusaIntegrationTestRunner({
|
|||||||
getProductFixture({
|
getProductFixture({
|
||||||
title: "test2",
|
title: "test2",
|
||||||
status: "published",
|
status: "published",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
}),
|
}),
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ medusaIntegrationTestRunner({
|
|||||||
let appContainer
|
let appContainer
|
||||||
let promotion
|
let promotion
|
||||||
let standardPromotion
|
let standardPromotion
|
||||||
|
let shippingProfile
|
||||||
|
|
||||||
const promotionRule = {
|
const promotionRule = {
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
@@ -90,6 +91,14 @@ medusaIntegrationTestRunner({
|
|||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
).data.promotion
|
).data.promotion
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "default", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("GET /admin/promotions/:id", () => {
|
describe("GET /admin/promotions/:id", () => {
|
||||||
@@ -512,7 +521,10 @@ medusaIntegrationTestRunner({
|
|||||||
const product = (
|
const product = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/products",
|
"/admin/products",
|
||||||
medusaTshirtProduct,
|
{
|
||||||
|
...medusaTshirtProduct,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
},
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
).data.product
|
).data.product
|
||||||
@@ -1494,6 +1506,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
title: "Test product 1",
|
title: "Test product 1",
|
||||||
options: [{ title: "size", values: ["large", "small"] }],
|
options: [{ title: "size", values: ["large", "small"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
},
|
},
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
@@ -1505,6 +1518,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
title: "Test product 2",
|
title: "Test product 2",
|
||||||
options: [{ title: "size", values: ["large", "small"] }],
|
options: [{ title: "size", values: ["large", "small"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
},
|
},
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,12 +25,24 @@ medusaIntegrationTestRunner({
|
|||||||
const container = getContainer()
|
const container = getContainer()
|
||||||
await createAdminUser(dbConnection, adminHeaders, container)
|
await createAdminUser(dbConnection, adminHeaders, container)
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{
|
||||||
|
name: "Test",
|
||||||
|
type: "default",
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
const product = (
|
const product = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/products",
|
"/admin/products",
|
||||||
{
|
{
|
||||||
title: "Test product",
|
title: "Test product",
|
||||||
options: [{ title: "size", values: ["x", "l"] }],
|
options: [{ title: "size", values: ["x", "l"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Test variant",
|
title: "Test variant",
|
||||||
@@ -149,17 +161,6 @@ medusaIntegrationTestRunner({
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
shippingProfile = (
|
|
||||||
await api.post(
|
|
||||||
`/admin/shipping-profiles`,
|
|
||||||
{
|
|
||||||
name: "Test",
|
|
||||||
type: "default",
|
|
||||||
},
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
).data.shipping_profile
|
|
||||||
|
|
||||||
location = (
|
location = (
|
||||||
await api.post(
|
await api.post(
|
||||||
`/admin/stock-locations`,
|
`/admin/stock-locations`,
|
||||||
|
|||||||
@@ -354,11 +354,20 @@ medusaIntegrationTestRunner({
|
|||||||
// to: /admin/sales-channels/:id/products
|
// to: /admin/sales-channels/:id/products
|
||||||
let product
|
let product
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
const shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "Test", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
product = (
|
product = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/products",
|
"/admin/products",
|
||||||
{
|
{
|
||||||
title: "test name",
|
title: "test name",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
options: [{ title: "size", values: ["large"] }],
|
options: [{ title: "size", values: ["large"] }],
|
||||||
},
|
},
|
||||||
adminHeaders
|
adminHeaders
|
||||||
|
|||||||
+9
-8
@@ -58,6 +58,14 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
).data.region
|
).data.region
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "Test", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
salesChannel = (
|
salesChannel = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/sales-channels",
|
"/admin/sales-channels",
|
||||||
@@ -71,6 +79,7 @@ medusaIntegrationTestRunner({
|
|||||||
"/admin/products",
|
"/admin/products",
|
||||||
{
|
{
|
||||||
title: "Test fixture",
|
title: "Test fixture",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
options: [
|
options: [
|
||||||
{ title: "size", values: ["large", "small"] },
|
{ title: "size", values: ["large", "small"] },
|
||||||
{ title: "color", values: ["green"] },
|
{ title: "color", values: ["green"] },
|
||||||
@@ -114,14 +123,6 @@ medusaIntegrationTestRunner({
|
|||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
|
|
||||||
shippingProfile = (
|
|
||||||
await api.post(
|
|
||||||
`/admin/shipping-profiles`,
|
|
||||||
{ name: "Test", type: "default" },
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
).data.shipping_profile
|
|
||||||
|
|
||||||
const fulfillmentSets = (
|
const fulfillmentSets = (
|
||||||
await api.post(
|
await api.post(
|
||||||
`/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`,
|
`/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`,
|
||||||
|
|||||||
@@ -57,6 +57,14 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
).data.region
|
).data.region
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "Test", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
salesChannel = (
|
salesChannel = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/sales-channels",
|
"/admin/sales-channels",
|
||||||
@@ -74,6 +82,7 @@ medusaIntegrationTestRunner({
|
|||||||
{ title: "size", values: ["large", "small"] },
|
{ title: "size", values: ["large", "small"] },
|
||||||
{ title: "color", values: ["green"] },
|
{ title: "color", values: ["green"] },
|
||||||
],
|
],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "Test variant",
|
title: "Test variant",
|
||||||
@@ -113,14 +122,6 @@ medusaIntegrationTestRunner({
|
|||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
|
|
||||||
shippingProfile = (
|
|
||||||
await api.post(
|
|
||||||
`/admin/shipping-profiles`,
|
|
||||||
{ name: "Test", type: "default" },
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
).data.shipping_profile
|
|
||||||
|
|
||||||
const fulfillmentSets = (
|
const fulfillmentSets = (
|
||||||
await api.post(
|
await api.post(
|
||||||
`/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`,
|
`/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`,
|
||||||
|
|||||||
@@ -658,6 +658,7 @@ medusaIntegrationTestRunner({
|
|||||||
|
|
||||||
describe("POST /store/carts/:id/line-items", () => {
|
describe("POST /store/carts/:id/line-items", () => {
|
||||||
let region
|
let region
|
||||||
|
|
||||||
const productData = {
|
const productData = {
|
||||||
title: "Medusa T-Shirt",
|
title: "Medusa T-Shirt",
|
||||||
handle: "t-shirt",
|
handle: "t-shirt",
|
||||||
@@ -716,8 +717,21 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("adding an existing variant should update or create line item depending on metadata", async () => {
|
it("adding an existing variant should update or create line item depending on metadata", async () => {
|
||||||
|
const shippingProfile =
|
||||||
|
await fulfillmentModule.createShippingProfiles({
|
||||||
|
name: "Test",
|
||||||
|
type: "default",
|
||||||
|
})
|
||||||
|
|
||||||
const product = (
|
const product = (
|
||||||
await api.post(`/admin/products`, productData, adminHeaders)
|
await api.post(
|
||||||
|
`/admin/products`,
|
||||||
|
{
|
||||||
|
...productData,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
).data.product
|
).data.product
|
||||||
|
|
||||||
const cart = (
|
const cart = (
|
||||||
@@ -1203,6 +1217,7 @@ medusaIntegrationTestRunner({
|
|||||||
"/admin/products",
|
"/admin/products",
|
||||||
{
|
{
|
||||||
title: "Test fixture",
|
title: "Test fixture",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
options: [
|
options: [
|
||||||
{ title: "size", values: ["large", "small"] },
|
{ title: "size", values: ["large", "small"] },
|
||||||
{ title: "color", values: ["green"] },
|
{ title: "color", values: ["green"] },
|
||||||
|
|||||||
@@ -30,11 +30,20 @@ medusaIntegrationTestRunner({
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
||||||
|
|
||||||
|
const shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "Test", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
product = (
|
product = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/products",
|
"/admin/products",
|
||||||
{
|
{
|
||||||
title: "product 1",
|
title: "product 1",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
options: [{ title: "size", values: ["x", "l"] }],
|
options: [{ title: "size", values: ["x", "l"] }],
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,9 +31,18 @@ medusaIntegrationTestRunner({
|
|||||||
|
|
||||||
describe("Index engine", () => {
|
describe("Index engine", () => {
|
||||||
it("should search through the indexed data and return the correct results ordered and filtered [1]", async () => {
|
it("should search through the indexed data and return the correct results ordered and filtered [1]", async () => {
|
||||||
|
const shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "Test", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
title: "Test Giftcard",
|
title: "Test Giftcard",
|
||||||
is_giftcard: true,
|
is_giftcard: true,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
description: "test-giftcard-description",
|
description: "test-giftcard-description",
|
||||||
options: [{ title: "Denominations", values: ["100"] }],
|
options: [{ title: "Denominations", values: ["100"] }],
|
||||||
variants: new Array(10).fill(0).map((_, i) => ({
|
variants: new Array(10).fill(0).map((_, i) => ({
|
||||||
@@ -101,10 +110,19 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should search through the indexed data and return the correct results ordered and filtered [2]", async () => {
|
it("should search through the indexed data and return the correct results ordered and filtered [2]", async () => {
|
||||||
|
const shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "Test", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
title: "Test Giftcard",
|
title: "Test Giftcard",
|
||||||
is_giftcard: true,
|
is_giftcard: true,
|
||||||
description: "test-giftcard-description",
|
description: "test-giftcard-description",
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
options: [{ title: "Denominations", values: ["100"] }],
|
options: [{ title: "Denominations", values: ["100"] }],
|
||||||
variants: new Array(10).fill(0).map((_, i) => ({
|
variants: new Array(10).fill(0).map((_, i) => ({
|
||||||
title: `Test variant ${i}`,
|
title: `Test variant ${i}`,
|
||||||
@@ -171,9 +189,18 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
it.skip("should search through the indexed data and return the correct results ordered and filtered [3]", async () => {
|
it.skip("should search through the indexed data and return the correct results ordered and filtered [3]", async () => {
|
||||||
|
const shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "Test", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
const payloads = new Array(50).fill(0).map((_, a) => ({
|
const payloads = new Array(50).fill(0).map((_, a) => ({
|
||||||
title: "Test Giftcard-" + a,
|
title: "Test Giftcard-" + a,
|
||||||
is_giftcard: true,
|
is_giftcard: true,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
description: "test-giftcard-description" + a,
|
description: "test-giftcard-description" + a,
|
||||||
options: [{ title: "Denominations", values: ["100"] }],
|
options: [{ title: "Denominations", values: ["100"] }],
|
||||||
variants: new Array(10).fill(0).map((_, i) => ({
|
variants: new Array(10).fill(0).map((_, i) => ({
|
||||||
|
|||||||
@@ -209,9 +209,18 @@ medusaIntegrationTestRunner({
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await createAdminUser(dbConnection, adminHeaders, appContainer)
|
await createAdminUser(dbConnection, adminHeaders, appContainer)
|
||||||
|
|
||||||
|
const shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "Test", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
title: "Test Giftcard",
|
title: "Test Giftcard",
|
||||||
is_giftcard: true,
|
is_giftcard: true,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
description: "test-giftcard-description",
|
description: "test-giftcard-description",
|
||||||
options: [{ title: "Denominations", values: ["100"] }],
|
options: [{ title: "Denominations", values: ["100"] }],
|
||||||
variants: [
|
variants: [
|
||||||
|
|||||||
@@ -130,6 +130,17 @@ async function prepareDataFixtures({ container }) {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
await remoteLink.create([
|
||||||
|
{
|
||||||
|
[Modules.PRODUCT]: {
|
||||||
|
product_id: product.id,
|
||||||
|
},
|
||||||
|
[Modules.FULFILLMENT]: {
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
await remoteLink.create([
|
await remoteLink.create([
|
||||||
{
|
{
|
||||||
[Modules.STOCK_LOCATION]: {
|
[Modules.STOCK_LOCATION]: {
|
||||||
|
|||||||
@@ -124,6 +124,17 @@ async function prepareDataFixtures({ container }) {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
await remoteLink.create([
|
||||||
|
{
|
||||||
|
[Modules.PRODUCT]: {
|
||||||
|
product_id: product.id,
|
||||||
|
},
|
||||||
|
[Modules.FULFILLMENT]: {
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
await remoteLink.create([
|
await remoteLink.create([
|
||||||
{
|
{
|
||||||
[Modules.STOCK_LOCATION]: {
|
[Modules.STOCK_LOCATION]: {
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import {
|
|||||||
batchProductVariantsWorkflow,
|
batchProductVariantsWorkflow,
|
||||||
batchProductVariantsWorkflowId,
|
batchProductVariantsWorkflowId,
|
||||||
} from "@medusajs/core-flows"
|
} from "@medusajs/core-flows"
|
||||||
import { IProductModuleService } from "@medusajs/types"
|
import {
|
||||||
|
IFulfillmentModuleService,
|
||||||
|
IProductModuleService,
|
||||||
|
} from "@medusajs/types"
|
||||||
import { Modules } from "@medusajs/utils"
|
import { Modules } from "@medusajs/utils"
|
||||||
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
||||||
|
|
||||||
@@ -17,9 +20,20 @@ medusaIntegrationTestRunner({
|
|||||||
let appContainer
|
let appContainer
|
||||||
let service: IProductModuleService
|
let service: IProductModuleService
|
||||||
|
|
||||||
|
let fulfullmentService: IFulfillmentModuleService
|
||||||
|
let shippingProfile
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
appContainer = getContainer()
|
appContainer = getContainer()
|
||||||
service = appContainer.resolve(Modules.PRODUCT)
|
service = appContainer.resolve(Modules.PRODUCT)
|
||||||
|
fulfullmentService = appContainer.resolve(Modules.FULFILLMENT)
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
shippingProfile = await fulfullmentService.createShippingProfiles({
|
||||||
|
name: "Test",
|
||||||
|
type: "default",
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("batchProductWorkflow", () => {
|
describe("batchProductWorkflow", () => {
|
||||||
@@ -43,7 +57,8 @@ medusaIntegrationTestRunner({
|
|||||||
create: [
|
create: [
|
||||||
{
|
{
|
||||||
title: "test3",
|
title: "test3",
|
||||||
options: [{ title: "size", options: ["x"] }],
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
options: [{ title: "size", values: ["x"] }],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
update: [{ id: product1.id, title: "test1-updated" }],
|
update: [{ id: product1.id, title: "test1-updated" }],
|
||||||
@@ -94,6 +109,7 @@ medusaIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
title: "test1",
|
title: "test1",
|
||||||
options: [{ title: "size", values: ["x", "l", "m"] }],
|
options: [{ title: "size", values: ["x", "l", "m"] }],
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
title: "variant1",
|
title: "variant1",
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./sidebar-link"
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { ReactNode } from "react"
|
||||||
|
import { Link } from "react-router-dom"
|
||||||
|
|
||||||
|
import { IconAvatar } from "../icon-avatar"
|
||||||
|
import { Text } from "@medusajs/ui"
|
||||||
|
import { TriangleRightMini } from "@medusajs/icons"
|
||||||
|
|
||||||
|
export interface SidebarLinkProps {
|
||||||
|
to: string
|
||||||
|
labelKey: string
|
||||||
|
descriptionKey: string
|
||||||
|
icon: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SidebarLink = ({
|
||||||
|
to,
|
||||||
|
labelKey,
|
||||||
|
descriptionKey,
|
||||||
|
icon,
|
||||||
|
}: SidebarLinkProps) => {
|
||||||
|
return (
|
||||||
|
<Link to={to} className="group outline-none">
|
||||||
|
<div className="flex flex-col gap-2 px-2 pb-2">
|
||||||
|
<div className="shadow-elevation-card-rest bg-ui-bg-component transition-fg hover:bg-ui-bg-component-hover active:bg-ui-bg-component-pressed group-focus-visible:shadow-borders-interactive-with-active rounded-md px-4 py-2">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<IconAvatar>{icon}</IconAvatar>
|
||||||
|
<div className="flex flex-1 flex-col">
|
||||||
|
<Text size="small" leading="compact" weight="plus">
|
||||||
|
{labelKey}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
size="small"
|
||||||
|
leading="compact"
|
||||||
|
className="text-ui-fg-subtle"
|
||||||
|
>
|
||||||
|
{descriptionKey}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<div className="flex size-7 items-center justify-center">
|
||||||
|
<TriangleRightMini className="text-ui-fg-muted" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -554,6 +554,10 @@
|
|||||||
"label": "Discountable",
|
"label": "Discountable",
|
||||||
"hint": "When unchecked, discounts will not be applied to this product"
|
"hint": "When unchecked, discounts will not be applied to this product"
|
||||||
},
|
},
|
||||||
|
"shipping_profile": {
|
||||||
|
"label": "Shipping profile",
|
||||||
|
"hint": "Connect the product to a shipping profile"
|
||||||
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"label": "Type"
|
"label": "Type"
|
||||||
},
|
},
|
||||||
@@ -681,6 +685,20 @@
|
|||||||
"alreadyManagedWithSku": "This inventory item is already editable under {{title}} ({{sku}})."
|
"alreadyManagedWithSku": "This inventory item is already editable under {{title}} ({{sku}})."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"shippingProfile": {
|
||||||
|
"header": "Shipping configuration",
|
||||||
|
"edit": {
|
||||||
|
"header": "Shipping Configuration",
|
||||||
|
"toasts": {
|
||||||
|
"success": "Successfully updated the shipping profile for {{title}}."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"create": {
|
||||||
|
"errors": {
|
||||||
|
"required": "Shipping profile is required"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"toasts": {
|
"toasts": {
|
||||||
"delete": {
|
"delete": {
|
||||||
"success": {
|
"success": {
|
||||||
@@ -1249,6 +1267,8 @@
|
|||||||
"fulfillment": {
|
"fulfillment": {
|
||||||
"cancelWarning": "You are about to cancel a fulfillment. This action cannot be undone.",
|
"cancelWarning": "You are about to cancel a fulfillment. This action cannot be undone.",
|
||||||
"markAsDeliveredWarning": "You are about to mark fulfillment as delivered. This action cannot be undone.",
|
"markAsDeliveredWarning": "You are about to mark fulfillment as delivered. This action cannot be undone.",
|
||||||
|
"differentOptionSelected": "The selected shipping option is different from the one selected by the customer.",
|
||||||
|
"disabledItemTooltip": "The shipping option you have selected don’t allow fulfillment of this item",
|
||||||
"unfulfilledItems": "Unfulfilled Items",
|
"unfulfilledItems": "Unfulfilled Items",
|
||||||
"statusLabel": "Fulfillment status",
|
"statusLabel": "Fulfillment status",
|
||||||
"statusTitle": "Fulfillment Status",
|
"statusTitle": "Fulfillment Status",
|
||||||
@@ -1269,7 +1289,9 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"wrongQuantity": "Only one item is available for fulfillment",
|
"wrongQuantity": "Only one item is available for fulfillment",
|
||||||
"wrongQuantity_other": "Quantity should be a number between 1 and {{number}}",
|
"wrongQuantity_other": "Quantity should be a number between 1 and {{number}}",
|
||||||
"noItems": "No items to fulfill."
|
"noItems": "No items to fulfill.",
|
||||||
|
"noShippingOption": "Shipping option is required",
|
||||||
|
"noLocation": "Location is required"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"notFulfilled": "Not fulfilled",
|
"notFulfilled": "Not fulfilled",
|
||||||
@@ -2749,7 +2771,9 @@
|
|||||||
"added": "Added",
|
"added": "Added",
|
||||||
"removed": "Removed",
|
"removed": "Removed",
|
||||||
"from": "From",
|
"from": "From",
|
||||||
"to": "To"
|
"to": "To",
|
||||||
|
"beaware": "Be aware",
|
||||||
|
"loading": "Loading"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"amount": "Amount",
|
"amount": "Amount",
|
||||||
|
|||||||
@@ -102,6 +102,13 @@ export const RouteMap: RouteObject[] = [
|
|||||||
lazy: () =>
|
lazy: () =>
|
||||||
import("../../routes/products/product-organization"),
|
import("../../routes/products/product-organization"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "shipping-profile",
|
||||||
|
lazy: () =>
|
||||||
|
import(
|
||||||
|
"../../routes/products/product-shipping-profile"
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "media",
|
path: "media",
|
||||||
lazy: () =>
|
lazy: () =>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { IconAvatar } from "../../../components/common/icon-avatar"
|
|||||||
import { TwoColumnPage } from "../../../components/layout/pages"
|
import { TwoColumnPage } from "../../../components/layout/pages"
|
||||||
import { useDashboardExtension } from "../../../extensions"
|
import { useDashboardExtension } from "../../../extensions"
|
||||||
import { LocationListHeader } from "./components/location-list-header"
|
import { LocationListHeader } from "./components/location-list-header"
|
||||||
|
import { SidebarLink } from "../../../components/common/sidebar-link/sidebar-link"
|
||||||
|
|
||||||
export function LocationList() {
|
export function LocationList() {
|
||||||
const initialData = useLoaderData() as Awaited<
|
const initialData = useLoaderData() as Awaited<
|
||||||
@@ -61,47 +62,6 @@ export function LocationList() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SidebarLinkProps {
|
|
||||||
to: string
|
|
||||||
labelKey: string
|
|
||||||
descriptionKey: string
|
|
||||||
icon: ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
const SidebarLink = ({
|
|
||||||
to,
|
|
||||||
labelKey,
|
|
||||||
descriptionKey,
|
|
||||||
icon,
|
|
||||||
}: SidebarLinkProps) => {
|
|
||||||
return (
|
|
||||||
<Link to={to} className="group outline-none">
|
|
||||||
<div className="flex flex-col gap-2 px-2 pb-2">
|
|
||||||
<div className="shadow-elevation-card-rest bg-ui-bg-component transition-fg hover:bg-ui-bg-component-hover active:bg-ui-bg-component-pressed group-focus-visible:shadow-borders-interactive-with-active rounded-md px-4 py-2">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<IconAvatar>{icon}</IconAvatar>
|
|
||||||
<div className="flex flex-1 flex-col">
|
|
||||||
<Text size="small" leading="compact" weight="plus">
|
|
||||||
{labelKey}
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
size="small"
|
|
||||||
leading="compact"
|
|
||||||
className="text-ui-fg-subtle"
|
|
||||||
>
|
|
||||||
{descriptionKey}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<div className="flex size-7 items-center justify-center">
|
|
||||||
<TriangleRightMini className="text-ui-fg-muted" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const LinksSection = () => {
|
const LinksSection = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
|||||||
-1
@@ -2,7 +2,6 @@ import { z } from "zod"
|
|||||||
|
|
||||||
export const CreateFulfillmentSchema = z.object({
|
export const CreateFulfillmentSchema = z.object({
|
||||||
quantity: z.record(z.string(), z.number()),
|
quantity: z.record(z.string(), z.number()),
|
||||||
|
|
||||||
location_id: z.string(),
|
location_id: z.string(),
|
||||||
shipping_option_id: z.string().optional(),
|
shipping_option_id: z.string().optional(),
|
||||||
send_notification: z.boolean().optional(),
|
send_notification: z.boolean().optional(),
|
||||||
|
|||||||
+175
-63
@@ -3,7 +3,7 @@ import { useEffect, useMemo, useState } from "react"
|
|||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import * as zod from "zod"
|
import * as zod from "zod"
|
||||||
|
|
||||||
import { AdminOrder } from "@medusajs/types"
|
import { AdminOrder, HttpTypes } from "@medusajs/types"
|
||||||
import { Alert, Button, Select, Switch, toast } from "@medusajs/ui"
|
import { Alert, Button, Select, Switch, toast } from "@medusajs/ui"
|
||||||
import { useForm, useWatch } from "react-hook-form"
|
import { useForm, useWatch } from "react-hook-form"
|
||||||
|
|
||||||
@@ -19,7 +19,10 @@ import { useStockLocations } from "../../../../../hooks/api/stock-locations"
|
|||||||
import { getFulfillableQuantity } from "../../../../../lib/order-item"
|
import { getFulfillableQuantity } from "../../../../../lib/order-item"
|
||||||
import { CreateFulfillmentSchema } from "./constants"
|
import { CreateFulfillmentSchema } from "./constants"
|
||||||
import { OrderCreateFulfillmentItem } from "./order-create-fulfillment-item"
|
import { OrderCreateFulfillmentItem } from "./order-create-fulfillment-item"
|
||||||
import { useReservationItems } from "../../../../../hooks/api"
|
import {
|
||||||
|
useReservationItems,
|
||||||
|
useShippingOptions,
|
||||||
|
} from "../../../../../hooks/api"
|
||||||
|
|
||||||
type OrderCreateFulfillmentFormProps = {
|
type OrderCreateFulfillmentFormProps = {
|
||||||
order: AdminOrder
|
order: AdminOrder
|
||||||
@@ -56,30 +59,86 @@ export function OrderCreateFulfillmentForm({
|
|||||||
|
|
||||||
const form = useForm<zod.infer<typeof CreateFulfillmentSchema>>({
|
const form = useForm<zod.infer<typeof CreateFulfillmentSchema>>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
quantity: fulfillableItems.reduce((acc, item) => {
|
quantity: fulfillableItems.reduce(
|
||||||
|
(acc, item) => {
|
||||||
acc[item.id] = getFulfillableQuantity(item)
|
acc[item.id] = getFulfillableQuantity(item)
|
||||||
return acc
|
return acc
|
||||||
}, {} as Record<string, number>),
|
},
|
||||||
|
{} as Record<string, number>
|
||||||
|
),
|
||||||
send_notification: !order.no_notification,
|
send_notification: !order.no_notification,
|
||||||
},
|
},
|
||||||
resolver: zodResolver(CreateFulfillmentSchema),
|
resolver: zodResolver(CreateFulfillmentSchema),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const selectedLocationId = useWatch({
|
||||||
|
name: "location_id",
|
||||||
|
control: form.control,
|
||||||
|
})
|
||||||
|
|
||||||
const { stock_locations = [] } = useStockLocations()
|
const { stock_locations = [] } = useStockLocations()
|
||||||
|
|
||||||
|
const { shipping_options = [], isLoading: isShippingOptionsLoading } =
|
||||||
|
useShippingOptions({
|
||||||
|
stock_location_id: selectedLocationId,
|
||||||
|
// is_return: false, // TODO: 500 when enabled
|
||||||
|
fields: "+service_zone.fulfillment_set.location.id",
|
||||||
|
})
|
||||||
|
|
||||||
|
const shippingOptionId = useWatch({
|
||||||
|
name: "shipping_option_id",
|
||||||
|
control: form.control,
|
||||||
|
})
|
||||||
|
|
||||||
const handleSubmit = form.handleSubmit(async (data) => {
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
try {
|
const selectedShippingOption = shipping_options.find(
|
||||||
await createOrderFulfillment({
|
(o) => o.id === shippingOptionId
|
||||||
location_id: data.location_id,
|
)
|
||||||
// shipping_option_id: data.shipping_option_id,
|
|
||||||
|
if (!selectedShippingOption) {
|
||||||
|
form.setError("shipping_option_id", {
|
||||||
|
type: "manual",
|
||||||
|
message: t("orders.fulfillment.error.noShippingOption"),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedLocationId) {
|
||||||
|
form.setError("location_id", {
|
||||||
|
type: "manual",
|
||||||
|
message: t("orders.fulfillment.error.noLocation"),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedShippingProfileId =
|
||||||
|
selectedShippingOption?.shipping_profile_id
|
||||||
|
|
||||||
|
const itemShippingProfileMap = order.items.reduce(
|
||||||
|
(acc, item) => {
|
||||||
|
acc[item.id] = item.variant?.product?.shipping_profile?.id
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
{} as Record<string, string | null>
|
||||||
|
)
|
||||||
|
|
||||||
|
const payload: HttpTypes.AdminCreateOrderFulfillment = {
|
||||||
|
location_id: selectedLocationId,
|
||||||
|
shipping_option_id: shippingOptionId,
|
||||||
no_notification: !data.send_notification,
|
no_notification: !data.send_notification,
|
||||||
items: Object.entries(data.quantity)
|
items: Object.entries(data.quantity)
|
||||||
.filter(([, value]) => !!value)
|
.filter(
|
||||||
|
([id, value]) =>
|
||||||
|
!!value && itemShippingProfileMap[id] === selectedShippingProfileId
|
||||||
|
)
|
||||||
.map(([id, quantity]) => ({
|
.map(([id, quantity]) => ({
|
||||||
id,
|
id,
|
||||||
quantity,
|
quantity,
|
||||||
})),
|
})),
|
||||||
})
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await createOrderFulfillment(payload)
|
||||||
|
|
||||||
toast.success(t("orders.fulfillment.toast.created"))
|
toast.success(t("orders.fulfillment.toast.created"))
|
||||||
handleSuccess(`/orders/${order.id}`)
|
handleSuccess(`/orders/${order.id}`)
|
||||||
@@ -89,15 +148,28 @@ export function OrderCreateFulfillmentForm({
|
|||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (stock_locations?.length) {
|
if (stock_locations?.length && shipping_options?.length) {
|
||||||
form.setValue("location_id", stock_locations[0].id)
|
const initialShippingOptionId =
|
||||||
}
|
order.shipping_methods?.[0]?.shipping_option_id
|
||||||
}, [stock_locations?.length])
|
|
||||||
|
|
||||||
const selectedLocationId = useWatch({
|
if (initialShippingOptionId) {
|
||||||
name: "location_id",
|
const shippingOption = shipping_options.find(
|
||||||
control: form.control,
|
(o) => o.id === initialShippingOptionId
|
||||||
})
|
)
|
||||||
|
|
||||||
|
if (shippingOption) {
|
||||||
|
const locationId =
|
||||||
|
shippingOption.service_zone.fulfillment_set.location.id
|
||||||
|
|
||||||
|
form.setValue("location_id", locationId)
|
||||||
|
form.setValue(
|
||||||
|
"shipping_option_id",
|
||||||
|
initialShippingOptionId || undefined
|
||||||
|
)
|
||||||
|
} // else -> TODO: what if original shipping option is deleted?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [stock_locations?.length, shipping_options?.length])
|
||||||
|
|
||||||
const fulfilledQuantityArray = (order.items || []).map(
|
const fulfilledQuantityArray = (order.items || []).map(
|
||||||
(item) =>
|
(item) =>
|
||||||
@@ -124,14 +196,21 @@ export function OrderCreateFulfillmentForm({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const quantityMap = itemsToFulfill.reduce((acc, item) => {
|
const quantityMap = itemsToFulfill.reduce(
|
||||||
|
(acc, item) => {
|
||||||
acc[item.id] = getFulfillableQuantity(item as OrderLineItemDTO)
|
acc[item.id] = getFulfillableQuantity(item as OrderLineItemDTO)
|
||||||
return acc
|
return acc
|
||||||
}, {} as Record<string, number>)
|
},
|
||||||
|
{} as Record<string, number>
|
||||||
|
)
|
||||||
|
|
||||||
form.setValue("quantity", quantityMap)
|
form.setValue("quantity", quantityMap)
|
||||||
}, [...fulfilledQuantityArray, requiresShipping])
|
}, [...fulfilledQuantityArray, requiresShipping])
|
||||||
|
|
||||||
|
const differentOptionSelected =
|
||||||
|
shippingOptionId &&
|
||||||
|
order.shipping_methods?.[0]?.shipping_option_id !== shippingOptionId
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RouteFocusModal.Form form={form}>
|
<RouteFocusModal.Form form={form}>
|
||||||
<KeyboundForm
|
<KeyboundForm
|
||||||
@@ -185,48 +264,69 @@ export function OrderCreateFulfillmentForm({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* <div className="py-8">*/}
|
<div className="py-8">
|
||||||
{/* <Form.Field*/}
|
<Form.Field
|
||||||
{/* control={form.control}*/}
|
control={form.control}
|
||||||
{/* name="shipping_option_id"*/}
|
name="shipping_option_id"
|
||||||
{/* render={({ field: { onChange, ref, ...field } }) => {*/}
|
render={({ field: { onChange, ref, ...field } }) => {
|
||||||
{/* return (*/}
|
return (
|
||||||
{/* <Form.Item>*/}
|
<Form.Item>
|
||||||
{/* <div className="flex flex-col gap-2 xl:flex-row xl:items-center">*/}
|
<div className="flex flex-col gap-2 xl:flex-row xl:items-center">
|
||||||
{/* <div className="flex-1">*/}
|
<div className="flex-1">
|
||||||
{/* <Form.Label>*/}
|
<Form.Label>
|
||||||
{/* {t("fields.shippingMethod")}*/}
|
{t("fields.shippingMethod")}
|
||||||
{/* </Form.Label>*/}
|
</Form.Label>
|
||||||
{/* <Form.Hint>*/}
|
<Form.Hint>
|
||||||
{/* {t("orders.fulfillment.methodDescription")}*/}
|
{t("orders.fulfillment.methodDescription")}
|
||||||
{/* </Form.Hint>*/}
|
</Form.Hint>
|
||||||
{/* </div>*/}
|
</div>
|
||||||
{/* <div className="flex-1">*/}
|
<div className="flex-1">
|
||||||
{/* <Form.Control>*/}
|
<Form.Control>
|
||||||
{/* <Select onValueChange={onChange} {...field}>*/}
|
<Select
|
||||||
{/* <Select.Trigger*/}
|
onValueChange={onChange}
|
||||||
{/* className="bg-ui-bg-base"*/}
|
{...field}
|
||||||
{/* ref={ref}*/}
|
disabled={!selectedLocationId}
|
||||||
{/* >*/}
|
>
|
||||||
{/* <Select.Value />*/}
|
<Select.Trigger
|
||||||
{/* </Select.Trigger>*/}
|
className="bg-ui-bg-base"
|
||||||
{/* <Select.Content>*/}
|
ref={ref}
|
||||||
{/* {shipping_options.map((o) => (*/}
|
>
|
||||||
{/* <Select.Item key={o.id} value={o.id}>*/}
|
{isShippingOptionsLoading ? (
|
||||||
{/* {o.name}*/}
|
<span className="text-right">
|
||||||
{/* </Select.Item>*/}
|
{t("labels.loading")}...
|
||||||
{/* ))}*/}
|
</span>
|
||||||
{/* </Select.Content>*/}
|
) : (
|
||||||
{/* </Select>*/}
|
<Select.Value />
|
||||||
{/* </Form.Control>*/}
|
)}
|
||||||
{/* </div>*/}
|
</Select.Trigger>
|
||||||
{/* </div>*/}
|
<Select.Content>
|
||||||
{/* <Form.ErrorMessage />*/}
|
{shipping_options.map((o) => (
|
||||||
{/* </Form.Item>*/}
|
<Select.Item key={o.id} value={o.id}>
|
||||||
{/* )*/}
|
{o.name}
|
||||||
{/* }}*/}
|
</Select.Item>
|
||||||
{/* />*/}
|
))}
|
||||||
{/* </div>*/}
|
</Select.Content>
|
||||||
|
</Select>
|
||||||
|
</Form.Control>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{differentOptionSelected && (
|
||||||
|
<Alert className="mt-4 p-4" variant="warning">
|
||||||
|
<span className="-mt-[3px] block font-semibold">
|
||||||
|
{t("labels.beaware")}
|
||||||
|
</span>
|
||||||
|
<span className="text-ui-fg-muted">
|
||||||
|
{t("orders.fulfillment.differentOptionSelected")}
|
||||||
|
</span>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Form.Item className="mt-8">
|
<Form.Item className="mt-8">
|
||||||
<Form.Label>
|
<Form.Label>
|
||||||
@@ -238,12 +338,19 @@ export function OrderCreateFulfillmentForm({
|
|||||||
|
|
||||||
<div className="flex flex-col gap-y-1">
|
<div className="flex flex-col gap-y-1">
|
||||||
{fulfillableItems.map((item) => {
|
{fulfillableItems.map((item) => {
|
||||||
|
const isShippingProfileMatching =
|
||||||
|
shipping_options.find(
|
||||||
|
(o) => o.id === shippingOptionId
|
||||||
|
)?.shipping_profile_id ===
|
||||||
|
item.variant?.product?.shipping_profile?.id
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OrderCreateFulfillmentItem
|
<OrderCreateFulfillmentItem
|
||||||
key={item.id}
|
key={item.id}
|
||||||
form={form}
|
form={form}
|
||||||
item={item}
|
item={item}
|
||||||
locationId={selectedLocationId}
|
locationId={selectedLocationId}
|
||||||
|
disabled={isShippingProfileMatching}
|
||||||
itemReservedQuantitiesMap={
|
itemReservedQuantitiesMap={
|
||||||
itemReservedQuantitiesMap
|
itemReservedQuantitiesMap
|
||||||
}
|
}
|
||||||
@@ -305,7 +412,12 @@ export function OrderCreateFulfillmentForm({
|
|||||||
{t("actions.cancel")}
|
{t("actions.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
</RouteFocusModal.Close>
|
</RouteFocusModal.Close>
|
||||||
<Button size="small" type="submit" isLoading={isMutating}>
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isMutating}
|
||||||
|
disabled={!shippingOptionId}
|
||||||
|
>
|
||||||
{t("orders.fulfillment.create")}
|
{t("orders.fulfillment.create")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+23
-2
@@ -1,7 +1,7 @@
|
|||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import * as zod from "zod"
|
import * as zod from "zod"
|
||||||
import { Input, Text } from "@medusajs/ui"
|
import { clx, Input, Text, Tooltip } from "@medusajs/ui"
|
||||||
import { UseFormReturn } from "react-hook-form"
|
import { UseFormReturn } from "react-hook-form"
|
||||||
import { HttpTypes } from "@medusajs/types"
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ import { Thumbnail } from "../../../../../components/common/thumbnail/index"
|
|||||||
import { useProductVariant } from "../../../../../hooks/api/products"
|
import { useProductVariant } from "../../../../../hooks/api/products"
|
||||||
import { getFulfillableQuantity } from "../../../../../lib/order-item"
|
import { getFulfillableQuantity } from "../../../../../lib/order-item"
|
||||||
import { CreateFulfillmentSchema } from "./constants"
|
import { CreateFulfillmentSchema } from "./constants"
|
||||||
|
import { InformationCircleSolid } from "@medusajs/icons"
|
||||||
|
|
||||||
type OrderEditItemProps = {
|
type OrderEditItemProps = {
|
||||||
item: HttpTypes.AdminOrderLineItem
|
item: HttpTypes.AdminOrderLineItem
|
||||||
@@ -18,6 +19,7 @@ type OrderEditItemProps = {
|
|||||||
onItemRemove: (itemId: string) => void
|
onItemRemove: (itemId: string) => void
|
||||||
itemReservedQuantitiesMap: Map<string, number>
|
itemReservedQuantitiesMap: Map<string, number>
|
||||||
form: UseFormReturn<zod.infer<typeof CreateFulfillmentSchema>>
|
form: UseFormReturn<zod.infer<typeof CreateFulfillmentSchema>>
|
||||||
|
disabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function OrderCreateFulfillmentItem({
|
export function OrderCreateFulfillmentItem({
|
||||||
@@ -25,6 +27,7 @@ export function OrderCreateFulfillmentItem({
|
|||||||
form,
|
form,
|
||||||
locationId,
|
locationId,
|
||||||
itemReservedQuantitiesMap,
|
itemReservedQuantitiesMap,
|
||||||
|
disabled,
|
||||||
}: OrderEditItemProps) {
|
}: OrderEditItemProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
@@ -71,7 +74,24 @@ export function OrderCreateFulfillmentItem({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-ui-bg-subtle shadow-elevation-card-rest my-2 rounded-xl">
|
<div className="bg-ui-bg-subtle shadow-elevation-card-rest my-2 rounded-xl">
|
||||||
<div className="flex flex-col gap-x-2 gap-y-2 border-b p-3 text-sm sm:flex-row">
|
<div className="flex flex-row items-center">
|
||||||
|
{disabled && (
|
||||||
|
<div className="inline-flex items-center ml-4">
|
||||||
|
<Tooltip
|
||||||
|
content={t("orders.fulfillment.disabledItemTooltip")}
|
||||||
|
side="top"
|
||||||
|
>
|
||||||
|
<InformationCircleSolid className="text-ui-tag-orange-icon" />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={clx(
|
||||||
|
"flex flex-col flex-1 gap-x-2 gap-y-2 border-b p-3 text-sm sm:flex-row",
|
||||||
|
disabled && "opacity-50 pointer-events-none"
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className="flex flex-1 items-center gap-x-3">
|
<div className="flex flex-1 items-center gap-x-3">
|
||||||
<Thumbnail src={item.thumbnail} />
|
<Thumbnail src={item.thumbnail} />
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
@@ -170,5 +190,6 @@ export function OrderCreateFulfillmentItem({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
+2
-1
@@ -10,7 +10,8 @@ export function OrderCreateFulfillment() {
|
|||||||
const requiresShipping = searchParams.get("requires_shipping") === "true"
|
const requiresShipping = searchParams.get("requires_shipping") === "true"
|
||||||
|
|
||||||
const { order, isLoading, isError, error } = useOrder(id!, {
|
const { order, isLoading, isError, error } = useOrder(id!, {
|
||||||
fields: "currency_code,*items,*items.variant,*shipping_address",
|
fields:
|
||||||
|
"currency_code,*items,*items.variant,+items.variant.product.shipping_profile.id,*shipping_address,+shipping_methods.shipping_option_id",
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
|
|||||||
+16
-3
@@ -79,10 +79,13 @@ export const ProductCreateForm = ({
|
|||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
return regions.reduce((acc, reg) => {
|
return regions.reduce(
|
||||||
|
(acc, reg) => {
|
||||||
acc[reg.id] = reg.currency_code
|
acc[reg.id] = reg.currency_code
|
||||||
return acc
|
return acc
|
||||||
}, {} as Record<string, string>)
|
},
|
||||||
|
{} as Record<string, string>
|
||||||
|
)
|
||||||
}, [regions])
|
}, [regions])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -178,6 +181,15 @@ export const ProductCreateForm = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (currentTab === Tab.ORGANIZE) {
|
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)
|
setTab(Tab.VARIANTS)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +220,8 @@ export const ProductCreateForm = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
setTabState({ ...currentState })
|
setTabState({ ...currentState })
|
||||||
}, [tab, tabState])
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- we only want this effect to run when the tab changes
|
||||||
|
}, [tab])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RouteFocusModal.Form form={form}>
|
<RouteFocusModal.Form form={form}>
|
||||||
|
|||||||
+38
@@ -51,6 +51,16 @@ export const ProductCreateOrganizationSection = ({
|
|||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const shippingProfiles = useComboboxData({
|
||||||
|
queryKey: ["shipping_profiles"],
|
||||||
|
queryFn: (params) => sdk.admin.shippingProfile.list(params),
|
||||||
|
getOptions: (data) =>
|
||||||
|
data.shipping_profiles.map((shippingProfile) => ({
|
||||||
|
label: shippingProfile.name,
|
||||||
|
value: shippingProfile.id,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
|
||||||
const { fields, remove, replace } = useFieldArray({
|
const { fields, remove, replace } = useFieldArray({
|
||||||
control: form.control,
|
control: form.control,
|
||||||
name: "sales_channels",
|
name: "sales_channels",
|
||||||
@@ -161,6 +171,34 @@ export const ProductCreateOrganizationSection = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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.Hint>
|
||||||
|
<Trans i18nKey={"products.fields.shipping_profile.hint"} />
|
||||||
|
</Form.Hint>
|
||||||
|
</div>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="shipping_profile_id"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Control>
|
||||||
|
<Combobox
|
||||||
|
{...field}
|
||||||
|
options={shippingProfiles.options}
|
||||||
|
searchValue={shippingProfiles.searchValue}
|
||||||
|
onSearchValueChange={shippingProfiles.onSearchValueChange}
|
||||||
|
fetchNextPage={shippingProfiles.fetchNextPage}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="grid grid-cols-1 gap-y-4">
|
<div className="grid grid-cols-1 gap-y-4">
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export const ProductCreateSchema = z
|
|||||||
discountable: z.boolean(),
|
discountable: z.boolean(),
|
||||||
type_id: z.string().optional(),
|
type_id: z.string().optional(),
|
||||||
collection_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
|
||||||
categories: z.array(z.string()),
|
categories: z.array(z.string()),
|
||||||
tags: z.array(z.string()).optional(),
|
tags: z.array(z.string()).optional(),
|
||||||
sales_channels: z
|
sales_channels: z
|
||||||
@@ -145,6 +146,7 @@ export const PRODUCT_CREATE_FORM_DEFAULTS: Partial<
|
|||||||
media: [],
|
media: [],
|
||||||
categories: [],
|
categories: [],
|
||||||
collection_id: "",
|
collection_id: "",
|
||||||
|
shipping_profile_id: "",
|
||||||
description: "",
|
description: "",
|
||||||
handle: "",
|
handle: "",
|
||||||
height: "",
|
height: "",
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export const normalizeProductFormValues = (
|
|||||||
: undefined,
|
: undefined,
|
||||||
images,
|
images,
|
||||||
collection_id: values.collection_id || undefined,
|
collection_id: values.collection_id || undefined,
|
||||||
|
shipping_profile_id: values.shipping_profile_id,
|
||||||
categories: values.categories.map((id) => ({ id })),
|
categories: values.categories.map((id) => ({ id })),
|
||||||
type_id: values.type_id || undefined,
|
type_id: values.type_id || undefined,
|
||||||
handle: values.handle || undefined,
|
handle: values.handle || undefined,
|
||||||
|
|||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./product-shipping-profile-section"
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
import { PencilSquare, ShoppingBag } from "@medusajs/icons"
|
||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { Container, Heading } from "@medusajs/ui"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
import { SidebarLink } from "../../../../../components/common/sidebar-link/sidebar-link"
|
||||||
|
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||||
|
|
||||||
|
type ProductShippingProfileSectionProps = {
|
||||||
|
product: HttpTypes.AdminProduct & {
|
||||||
|
shipping_profile: HttpTypes.AdminShippingProfile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProductShippingProfileSection = ({
|
||||||
|
product,
|
||||||
|
}: ProductShippingProfileSectionProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const shippingProfile = product.shipping_profile
|
||||||
|
|
||||||
|
if (!shippingProfile) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="p-0">
|
||||||
|
<div className="flex items-center justify-between px-6 py-4">
|
||||||
|
<Heading level="h2">{t("products.shippingProfile.header")}</Heading>
|
||||||
|
<ActionMenu
|
||||||
|
groups={[
|
||||||
|
{
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: t("actions.edit"),
|
||||||
|
to: "shipping-profile",
|
||||||
|
icon: <PencilSquare />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SidebarLink
|
||||||
|
to={`/settings/locations/shipping-profiles/${shippingProfile.id}`}
|
||||||
|
labelKey={shippingProfile.name}
|
||||||
|
descriptionKey={shippingProfile.type}
|
||||||
|
icon={<ShoppingBag />}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -2,5 +2,5 @@ import { getLinkedFields } from "../../../extensions"
|
|||||||
|
|
||||||
export const PRODUCT_DETAIL_FIELDS = getLinkedFields(
|
export const PRODUCT_DETAIL_FIELDS = getLinkedFields(
|
||||||
"product",
|
"product",
|
||||||
"*categories,-variants"
|
"*categories,*shipping_profile,-variants"
|
||||||
)
|
)
|
||||||
@@ -14,6 +14,7 @@ import { PRODUCT_DETAIL_FIELDS } from "./constants"
|
|||||||
import { productLoader } from "./loader"
|
import { productLoader } from "./loader"
|
||||||
|
|
||||||
import { useDashboardExtension } from "../../../extensions"
|
import { useDashboardExtension } from "../../../extensions"
|
||||||
|
import { ProductShippingProfileSection } from "./components/product-shipping-profile-section"
|
||||||
|
|
||||||
export const ProductDetail = () => {
|
export const ProductDetail = () => {
|
||||||
const initialData = useLoaderData() as Awaited<
|
const initialData = useLoaderData() as Awaited<
|
||||||
@@ -71,6 +72,7 @@ export const ProductDetail = () => {
|
|||||||
</TwoColumnPage.Main>
|
</TwoColumnPage.Main>
|
||||||
<TwoColumnPage.Sidebar>
|
<TwoColumnPage.Sidebar>
|
||||||
<ProductSalesChannelSection product={product} />
|
<ProductSalesChannelSection product={product} />
|
||||||
|
<ProductShippingProfileSection product={product} />
|
||||||
<ProductOrganizationSection product={product} />
|
<ProductOrganizationSection product={product} />
|
||||||
<ProductAttributeSection product={product} />
|
<ProductAttributeSection product={product} />
|
||||||
</TwoColumnPage.Sidebar>
|
</TwoColumnPage.Sidebar>
|
||||||
|
|||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./product-shipping-profile-form"
|
||||||
+122
@@ -0,0 +1,122 @@
|
|||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { Button, toast } from "@medusajs/ui"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import * as zod from "zod"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
type ProductShippingProfileFormProps = {
|
||||||
|
product: HttpTypes.AdminProduct & {
|
||||||
|
shipping_profile?: HttpTypes.AdminShippingProfile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProductShippingProfileSchema = zod.object({
|
||||||
|
shipping_profile_id: zod.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const ProductShippingProfileForm = ({
|
||||||
|
product,
|
||||||
|
}: ProductShippingProfileFormProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { handleSuccess } = useRouteModal()
|
||||||
|
|
||||||
|
const shippingProfiles = useComboboxData({
|
||||||
|
queryKey: ["shipping_profiles"],
|
||||||
|
queryFn: (params) => sdk.admin.shippingProfile.list(params),
|
||||||
|
getOptions: (data) =>
|
||||||
|
data.shipping_profiles.map((shippingProfile) => ({
|
||||||
|
label: shippingProfile.name,
|
||||||
|
value: shippingProfile.id,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
defaultValues: {
|
||||||
|
shipping_profile_id: product.shipping_profile?.id ?? "",
|
||||||
|
},
|
||||||
|
resolver: zodResolver(ProductShippingProfileSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mutateAsync, isPending } = useUpdateProduct(product.id)
|
||||||
|
|
||||||
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
|
await mutateAsync(
|
||||||
|
{
|
||||||
|
shipping_profile_id: data.shipping_profile_id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: ({ product }) => {
|
||||||
|
toast.success(
|
||||||
|
t("products.shippingProfile.edit.toasts.success", {
|
||||||
|
title: product.title,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
handleSuccess()
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteDrawer.Form form={form}>
|
||||||
|
<KeyboundForm onSubmit={handleSubmit} className="flex h-full flex-col">
|
||||||
|
<RouteDrawer.Body>
|
||||||
|
<div className="flex h-full flex-col gap-y-4">
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="shipping_profile_id"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>
|
||||||
|
{t("products.fields.shipping_profile.label")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Combobox
|
||||||
|
{...field}
|
||||||
|
options={shippingProfiles.options}
|
||||||
|
searchValue={shippingProfiles.searchValue}
|
||||||
|
onSearchValueChange={
|
||||||
|
shippingProfiles.onSearchValueChange
|
||||||
|
}
|
||||||
|
fetchNextPage={shippingProfiles.fetchNextPage}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* <FormExtensionZone fields={fields} form={form} /> */}
|
||||||
|
</div>
|
||||||
|
</RouteDrawer.Body>
|
||||||
|
<RouteDrawer.Footer>
|
||||||
|
<div className="flex items-center justify-end gap-x-2">
|
||||||
|
<RouteDrawer.Close asChild>
|
||||||
|
<Button size="small" variant="secondary">
|
||||||
|
{t("actions.cancel")}
|
||||||
|
</Button>
|
||||||
|
</RouteDrawer.Close>
|
||||||
|
<Button size="small" type="submit" isLoading={isPending}>
|
||||||
|
{t("actions.save")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</RouteDrawer.Footer>
|
||||||
|
</KeyboundForm>
|
||||||
|
</RouteDrawer.Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { ProductShippingProfile as Component } from "./product-shipping-profile"
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
import { Heading } from "@medusajs/ui"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { useParams } from "react-router-dom"
|
||||||
|
|
||||||
|
import { RouteDrawer } from "../../../components/modals"
|
||||||
|
import { useProduct } from "../../../hooks/api/products"
|
||||||
|
import { PRODUCT_DETAIL_FIELDS } from "../product-detail/constants"
|
||||||
|
import { ProductShippingProfileForm } from "./components/product-organization-form"
|
||||||
|
|
||||||
|
export const ProductShippingProfile = () => {
|
||||||
|
const { id } = useParams()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const { product, isLoading, isError, error } = useProduct(id!, {
|
||||||
|
fields: PRODUCT_DETAIL_FIELDS,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteDrawer>
|
||||||
|
<RouteDrawer.Header>
|
||||||
|
<RouteDrawer.Title asChild>
|
||||||
|
<Heading>{t("products.shippingProfile.edit.header")}</Heading>
|
||||||
|
</RouteDrawer.Title>
|
||||||
|
</RouteDrawer.Header>
|
||||||
|
{!isLoading && product && (
|
||||||
|
<ProductShippingProfileForm product={product} />
|
||||||
|
)}
|
||||||
|
</RouteDrawer>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -82,10 +82,7 @@ export type CreateFulfillmentValidateOrderStepInput = {
|
|||||||
*/
|
*/
|
||||||
export const createFulfillmentValidateOrder = createStep(
|
export const createFulfillmentValidateOrder = createStep(
|
||||||
"create-fulfillment-validate-order",
|
"create-fulfillment-validate-order",
|
||||||
({
|
({ order, inputItems }: CreateFulfillmentValidateOrderStepInput) => {
|
||||||
order,
|
|
||||||
inputItems,
|
|
||||||
}: CreateFulfillmentValidateOrderStepInput) => {
|
|
||||||
throwIfOrderIsCancelled({ order })
|
throwIfOrderIsCancelled({ order })
|
||||||
throwIfItemsDoesNotExistsInOrder({ order, inputItems })
|
throwIfItemsDoesNotExistsInOrder({ order, inputItems })
|
||||||
throwIfItemsAreNotGroupedByShippingRequirement({ order, inputItems })
|
throwIfItemsAreNotGroupedByShippingRequirement({ order, inputItems })
|
||||||
@@ -128,6 +125,7 @@ function prepareFulfillmentData({
|
|||||||
id: string
|
id: string
|
||||||
provider_id: string
|
provider_id: string
|
||||||
service_zone: { fulfillment_set: { location?: { id: string } } }
|
service_zone: { fulfillment_set: { location?: { id: string } } }
|
||||||
|
shipping_profile_id: string
|
||||||
}
|
}
|
||||||
shippingMethod: { data?: Record<string, unknown> | null }
|
shippingMethod: { data?: Record<string, unknown> | null }
|
||||||
reservations: ReservationItemDTO[]
|
reservations: ReservationItemDTO[]
|
||||||
@@ -156,6 +154,18 @@ function prepareFulfillmentData({
|
|||||||
const orderItem = orderItemsMap.get(i.id)!
|
const orderItem = orderItemsMap.get(i.id)!
|
||||||
const reservation = reservationItemMap.get(i.id)!
|
const reservation = reservationItemMap.get(i.id)!
|
||||||
|
|
||||||
|
if (
|
||||||
|
orderItem.requires_shipping &&
|
||||||
|
(orderItem as any).variant?.product &&
|
||||||
|
(orderItem as any).variant?.product.shipping_profile?.id !==
|
||||||
|
shippingOption.shipping_profile_id
|
||||||
|
) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Shipping profile ${shippingOption.shipping_profile_id} does not match the shipping profile of the order item ${orderItem.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
line_item_id: i.id,
|
line_item_id: i.id,
|
||||||
inventory_item_id: reservation?.inventory_item_id,
|
inventory_item_id: reservation?.inventory_item_id,
|
||||||
@@ -273,7 +283,8 @@ function prepareInventoryUpdate({
|
|||||||
/**
|
/**
|
||||||
* The details of the fulfillment to create, along with custom data that's passed to the workflow's hooks.
|
* The details of the fulfillment to create, along with custom data that's passed to the workflow's hooks.
|
||||||
*/
|
*/
|
||||||
export type CreateOrderFulfillmentWorkflowInput = OrderWorkflow.CreateOrderFulfillmentWorkflowInput & AdditionalData
|
export type CreateOrderFulfillmentWorkflowInput =
|
||||||
|
OrderWorkflow.CreateOrderFulfillmentWorkflowInput & AdditionalData
|
||||||
|
|
||||||
export const createOrderFulfillmentWorkflowId = "create-order-fulfillment"
|
export const createOrderFulfillmentWorkflowId = "create-order-fulfillment"
|
||||||
/**
|
/**
|
||||||
@@ -309,9 +320,7 @@ export const createOrderFulfillmentWorkflowId = "create-order-fulfillment"
|
|||||||
*/
|
*/
|
||||||
export const createOrderFulfillmentWorkflow = createWorkflow(
|
export const createOrderFulfillmentWorkflow = createWorkflow(
|
||||||
createOrderFulfillmentWorkflowId,
|
createOrderFulfillmentWorkflowId,
|
||||||
(
|
(input: WorkflowData<CreateOrderFulfillmentWorkflowInput>) => {
|
||||||
input: WorkflowData<CreateOrderFulfillmentWorkflowInput>
|
|
||||||
) => {
|
|
||||||
const order: OrderDTO = useRemoteQueryStep({
|
const order: OrderDTO = useRemoteQueryStep({
|
||||||
entry_point: "orders",
|
entry_point: "orders",
|
||||||
fields: [
|
fields: [
|
||||||
@@ -323,12 +332,14 @@ export const createOrderFulfillmentWorkflow = createWorkflow(
|
|||||||
"items.variant.manage_inventory",
|
"items.variant.manage_inventory",
|
||||||
"items.variant.allow_backorder",
|
"items.variant.allow_backorder",
|
||||||
"items.variant.product.id",
|
"items.variant.product.id",
|
||||||
|
"items.variant.product.shipping_profile.id",
|
||||||
"items.variant.weight",
|
"items.variant.weight",
|
||||||
"items.variant.length",
|
"items.variant.length",
|
||||||
"items.variant.height",
|
"items.variant.height",
|
||||||
"items.variant.width",
|
"items.variant.width",
|
||||||
"items.variant.material",
|
"items.variant.material",
|
||||||
"shipping_address.*",
|
"shipping_address.*",
|
||||||
|
"shipping_methods.id",
|
||||||
"shipping_methods.shipping_option_id",
|
"shipping_methods.shipping_option_id",
|
||||||
"shipping_methods.data",
|
"shipping_methods.data",
|
||||||
],
|
],
|
||||||
@@ -346,17 +357,29 @@ export const createOrderFulfillmentWorkflow = createWorkflow(
|
|||||||
}, {})
|
}, {})
|
||||||
})
|
})
|
||||||
|
|
||||||
const shippingMethod = transform(order, (data) => {
|
const shippingOptionId = transform({ order, input }, (data) => {
|
||||||
return { data: data.shipping_methods?.[0]?.data }
|
return (
|
||||||
|
data.input.shipping_option_id ??
|
||||||
|
data.order.shipping_methods?.[0]?.shipping_option_id
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const shippingOptionId = transform(order, (data) => {
|
const shippingMethod = transform({ order, shippingOptionId }, (data) => {
|
||||||
return data.shipping_methods?.[0]?.shipping_option_id
|
return {
|
||||||
|
data: data.order.shipping_methods?.find(
|
||||||
|
(sm) => sm.shipping_option_id === data.shippingOptionId
|
||||||
|
)?.data,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const shippingOption = useRemoteQueryStep({
|
const shippingOption = useRemoteQueryStep({
|
||||||
entry_point: "shipping_options",
|
entry_point: "shipping_options",
|
||||||
fields: ["id", "provider_id", "service_zone.fulfillment_set.location.id"],
|
fields: [
|
||||||
|
"id",
|
||||||
|
"provider_id",
|
||||||
|
"service_zone.fulfillment_set.location.id",
|
||||||
|
"shipping_profile_id",
|
||||||
|
],
|
||||||
variables: {
|
variables: {
|
||||||
id: shippingOptionId,
|
id: shippingOptionId,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -136,6 +136,11 @@ const normalizeProductForImport = (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (normalizedKey.startsWith("shipping_profile_id")) {
|
||||||
|
response["shipping_profile_id"] = normalizedValue
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (normalizedKey.startsWith("product_category_")) {
|
if (normalizedKey.startsWith("product_category_")) {
|
||||||
response["categories"] = [
|
response["categories"] = [
|
||||||
...(response["categories"] || []),
|
...(response["categories"] || []),
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { ProductTypes, SalesChannelTypes } from "@medusajs/framework/types"
|
import {
|
||||||
|
ProductTypes,
|
||||||
|
SalesChannelTypes,
|
||||||
|
ShippingProfileDTO,
|
||||||
|
} from "@medusajs/framework/types"
|
||||||
import { MedusaError } from "@medusajs/framework/utils"
|
import { MedusaError } from "@medusajs/framework/utils"
|
||||||
|
|
||||||
const basicFieldsToOmit = [
|
const basicFieldsToOmit = [
|
||||||
@@ -32,6 +36,7 @@ export const normalizeV1Products = (
|
|||||||
productTypes: ProductTypes.ProductTypeDTO[]
|
productTypes: ProductTypes.ProductTypeDTO[]
|
||||||
productCollections: ProductTypes.ProductCollectionDTO[]
|
productCollections: ProductTypes.ProductCollectionDTO[]
|
||||||
salesChannels: SalesChannelTypes.SalesChannelDTO[]
|
salesChannels: SalesChannelTypes.SalesChannelDTO[]
|
||||||
|
shippingProfiles: ShippingProfileDTO[]
|
||||||
}
|
}
|
||||||
): object[] => {
|
): object[] => {
|
||||||
const productTypesMap = new Map(
|
const productTypesMap = new Map(
|
||||||
@@ -43,6 +48,9 @@ export const normalizeV1Products = (
|
|||||||
const salesChannelsMap = new Map(
|
const salesChannelsMap = new Map(
|
||||||
supportingData.salesChannels.map((sc) => [sc.name, sc.id])
|
supportingData.salesChannels.map((sc) => [sc.name, sc.id])
|
||||||
)
|
)
|
||||||
|
const shippingProfilesIds = new Set(
|
||||||
|
supportingData.shippingProfiles.map((sp) => sp.id)
|
||||||
|
)
|
||||||
|
|
||||||
return rawProducts.map((product) => {
|
return rawProducts.map((product) => {
|
||||||
let finalRes = {
|
let finalRes = {
|
||||||
@@ -140,6 +148,21 @@ export const normalizeV1Products = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (key.startsWith("Shipping Profile Id")) {
|
||||||
|
if (!value) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"Shipping Profile Id is required when importing products"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (!shippingProfilesIds.has(value)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Shipping profile: '${value}' does not exist`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
key.startsWith("Product Category") &&
|
key.startsWith("Product Category") &&
|
||||||
(key.endsWith("Handle") ||
|
(key.endsWith("Handle") ||
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
IFulfillmentModuleService,
|
||||||
IProductModuleService,
|
IProductModuleService,
|
||||||
IRegionModuleService,
|
IRegionModuleService,
|
||||||
ISalesChannelModuleService,
|
ISalesChannelModuleService,
|
||||||
@@ -35,20 +36,25 @@ export const parseProductCsvStep = createStep(
|
|||||||
Modules.SALES_CHANNEL
|
Modules.SALES_CHANNEL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const fulfillmentService = container.resolve<IFulfillmentModuleService>(
|
||||||
|
Modules.FULFILLMENT
|
||||||
|
)
|
||||||
|
|
||||||
const csvProducts = convertCsvToJson(fileContent)
|
const csvProducts = convertCsvToJson(fileContent)
|
||||||
|
|
||||||
const [productTypes, productCollections, salesChannels] = await Promise.all(
|
const [productTypes, productCollections, salesChannels, shippingProfiles] =
|
||||||
[
|
await Promise.all([
|
||||||
productService.listProductTypes({}, {}),
|
productService.listProductTypes({}, {}),
|
||||||
productService.listProductCollections({}, {}),
|
productService.listProductCollections({}, {}),
|
||||||
salesChannelService.listSalesChannels({}, {}),
|
salesChannelService.listSalesChannels({}, {}),
|
||||||
]
|
fulfillmentService.listShippingProfiles({}, {}),
|
||||||
)
|
])
|
||||||
|
|
||||||
const v1Normalized = normalizeV1Products(csvProducts, {
|
const v1Normalized = normalizeV1Products(csvProducts, {
|
||||||
productTypes,
|
productTypes,
|
||||||
productCollections,
|
productCollections,
|
||||||
salesChannels,
|
salesChannels,
|
||||||
|
shippingProfiles,
|
||||||
})
|
})
|
||||||
|
|
||||||
// We use the handle to group products and variants correctly.
|
// We use the handle to group products and variants correctly.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
AdditionalData,
|
AdditionalData,
|
||||||
CreateProductWorkflowInputDTO,
|
CreateProductWorkflowInputDTO,
|
||||||
|
LinkDefinition,
|
||||||
PricingTypes,
|
PricingTypes,
|
||||||
ProductTypes,
|
ProductTypes,
|
||||||
} from "@medusajs/framework/types"
|
} from "@medusajs/framework/types"
|
||||||
@@ -8,6 +9,7 @@ import {
|
|||||||
ProductWorkflowEvents,
|
ProductWorkflowEvents,
|
||||||
isPresent,
|
isPresent,
|
||||||
MedusaError,
|
MedusaError,
|
||||||
|
Modules,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
import {
|
import {
|
||||||
WorkflowData,
|
WorkflowData,
|
||||||
@@ -17,7 +19,11 @@ import {
|
|||||||
transform,
|
transform,
|
||||||
createStep,
|
createStep,
|
||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
import { emitEventStep } from "../../common"
|
import {
|
||||||
|
createRemoteLinkStep,
|
||||||
|
emitEventStep,
|
||||||
|
useQueryGraphStep,
|
||||||
|
} from "../../common"
|
||||||
import { associateProductsWithSalesChannelsStep } from "../../sales-channel"
|
import { associateProductsWithSalesChannelsStep } from "../../sales-channel"
|
||||||
import { createProductsStep } from "../steps/create-products"
|
import { createProductsStep } from "../steps/create-products"
|
||||||
import { createProductVariantsWorkflow } from "./create-product-variants"
|
import { createProductVariantsWorkflow } from "./create-product-variants"
|
||||||
@@ -29,7 +35,12 @@ export interface ValidateProductInputStepInput {
|
|||||||
/**
|
/**
|
||||||
* The products to validate.
|
* The products to validate.
|
||||||
*/
|
*/
|
||||||
products: CreateProductWorkflowInputDTO[]
|
products: Omit<CreateProductWorkflowInputDTO, "sales_channels">[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The shipping profiles to validate.
|
||||||
|
*/
|
||||||
|
shippingProfiles: { id: string }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateProductInputStepId = "validate-product-input"
|
const validateProductInputStepId = "validate-product-input"
|
||||||
@@ -71,7 +82,7 @@ const validateProductInputStepId = "validate-product-input"
|
|||||||
export const validateProductInputStep = createStep(
|
export const validateProductInputStep = createStep(
|
||||||
validateProductInputStepId,
|
validateProductInputStepId,
|
||||||
async (data: ValidateProductInputStepInput) => {
|
async (data: ValidateProductInputStepInput) => {
|
||||||
const { products } = data
|
const { products, shippingProfiles } = data
|
||||||
|
|
||||||
const missingOptionsProductTitles = products
|
const missingOptionsProductTitles = products
|
||||||
.filter((product) => !product.options?.length)
|
.filter((product) => !product.options?.length)
|
||||||
@@ -85,6 +96,25 @@ 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(
|
||||||
|
", "
|
||||||
|
)}].`
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -154,15 +184,34 @@ export const createProductsWorkflow = createWorkflow(
|
|||||||
createProductsWorkflowId,
|
createProductsWorkflowId,
|
||||||
(input: WorkflowData<CreateProductsWorkflowInput>) => {
|
(input: WorkflowData<CreateProductsWorkflowInput>) => {
|
||||||
// Passing prices to the product module will fail, we want to keep them for after the product is created.
|
// Passing prices to the product module will fail, we want to keep them for after the product is created.
|
||||||
const productWithoutExternalRelations = transform({ input }, (data) =>
|
const { products: productWithoutExternalRelations, shippingPorfileIds } =
|
||||||
data.input.products.map((p) => ({
|
transform({ input }, (data) => {
|
||||||
|
const shippingPorfileIds: string[] = []
|
||||||
|
const productsData = data.input.products.map((p) => {
|
||||||
|
if (p.shipping_profile_id) {
|
||||||
|
shippingPorfileIds.push(p.shipping_profile_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
...p,
|
...p,
|
||||||
sales_channels: undefined,
|
sales_channels: undefined,
|
||||||
|
shipping_profile_id: undefined,
|
||||||
variants: undefined,
|
variants: undefined,
|
||||||
}))
|
}
|
||||||
)
|
})
|
||||||
|
|
||||||
validateProductInputStep({ products: productWithoutExternalRelations })
|
return { products: productsData, shippingPorfileIds }
|
||||||
|
})
|
||||||
|
|
||||||
|
const { data: shippingProfiles } = useQueryGraphStep({
|
||||||
|
entity: "shipping_profile",
|
||||||
|
fields: ["id"],
|
||||||
|
filters: {
|
||||||
|
id: shippingPorfileIds,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
validateProductInputStep({ products: input.products, shippingProfiles })
|
||||||
|
|
||||||
const createdProducts = createProductsStep(productWithoutExternalRelations)
|
const createdProducts = createProductsStep(productWithoutExternalRelations)
|
||||||
|
|
||||||
@@ -182,6 +231,24 @@ export const createProductsWorkflow = createWorkflow(
|
|||||||
|
|
||||||
associateProductsWithSalesChannelsStep({ links: salesChannelLinks })
|
associateProductsWithSalesChannelsStep({ links: salesChannelLinks })
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
createRemoteLinkStep(shippingProfileLinks as LinkDefinition[])
|
||||||
|
|
||||||
const variantsInput = transform({ input, createdProducts }, (data) => {
|
const variantsInput = transform({ input, createdProducts }, (data) => {
|
||||||
// TODO: Move this to a unified place for all product workflow types
|
// TODO: Move this to a unified place for all product workflow types
|
||||||
const productVariants: (ProductTypes.CreateProductVariantDTO & {
|
const productVariants: (ProductTypes.CreateProductVariantDTO & {
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ export type UpdateProductsWorkflowInputSelector = {
|
|||||||
* The variants to update.
|
* The variants to update.
|
||||||
*/
|
*/
|
||||||
variants?: UpdateProductVariantWorkflowInputDTO[]
|
variants?: UpdateProductVariantWorkflowInputDTO[]
|
||||||
|
/**
|
||||||
|
* The shipping profile to set.
|
||||||
|
*/
|
||||||
|
shipping_profile_id?: string
|
||||||
}
|
}
|
||||||
} & AdditionalData
|
} & AdditionalData
|
||||||
|
|
||||||
@@ -66,6 +70,10 @@ export type UpdateProductsWorkflowInputProducts = {
|
|||||||
* The variants to update.
|
* The variants to update.
|
||||||
*/
|
*/
|
||||||
variants?: UpdateProductVariantWorkflowInputDTO[]
|
variants?: UpdateProductVariantWorkflowInputDTO[]
|
||||||
|
/**
|
||||||
|
* The shipping profile to set.
|
||||||
|
*/
|
||||||
|
shipping_profile_id?: string
|
||||||
})[]
|
})[]
|
||||||
} & AdditionalData
|
} & AdditionalData
|
||||||
|
|
||||||
@@ -90,6 +98,7 @@ function prepareUpdateProductInput({
|
|||||||
products: input.products.map((p) => ({
|
products: input.products.map((p) => ({
|
||||||
...p,
|
...p,
|
||||||
sales_channels: undefined,
|
sales_channels: undefined,
|
||||||
|
shipping_profile_id: undefined,
|
||||||
variants: p.variants?.map((v) => ({
|
variants: p.variants?.map((v) => ({
|
||||||
...v,
|
...v,
|
||||||
prices: undefined,
|
prices: undefined,
|
||||||
@@ -103,6 +112,7 @@ function prepareUpdateProductInput({
|
|||||||
update: {
|
update: {
|
||||||
...input.update,
|
...input.update,
|
||||||
sales_channels: undefined,
|
sales_channels: undefined,
|
||||||
|
shipping_profile_id: undefined,
|
||||||
variants: input.update?.variants?.map((v) => ({
|
variants: input.update?.variants?.map((v) => ({
|
||||||
...v,
|
...v,
|
||||||
prices: undefined,
|
prices: undefined,
|
||||||
@@ -173,6 +183,44 @@ function prepareSalesChannelLinks({
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function prepareShippingProfileLinks({
|
||||||
|
input,
|
||||||
|
updatedProducts,
|
||||||
|
}: {
|
||||||
|
updatedProducts: ProductTypes.ProductDTO[]
|
||||||
|
input: UpdateProductWorkflowInput
|
||||||
|
}): Record<string, Record<string, any>>[] {
|
||||||
|
if ("products" in input) {
|
||||||
|
if (!input.products.length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return input.products
|
||||||
|
.filter((p) => p.shipping_profile_id)
|
||||||
|
.map((p) => ({
|
||||||
|
[Modules.PRODUCT]: {
|
||||||
|
product_id: p.id,
|
||||||
|
},
|
||||||
|
[Modules.FULFILLMENT]: {
|
||||||
|
shipping_profile_id: p.shipping_profile_id,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.selector && input.update?.shipping_profile_id) {
|
||||||
|
return updatedProducts.map((p) => ({
|
||||||
|
[Modules.PRODUCT]: {
|
||||||
|
product_id: p.id,
|
||||||
|
},
|
||||||
|
[Modules.FULFILLMENT]: {
|
||||||
|
shipping_profile_id: input.update.shipping_profile_id,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
function prepareVariantPrices({
|
function prepareVariantPrices({
|
||||||
input,
|
input,
|
||||||
updatedProducts,
|
updatedProducts,
|
||||||
@@ -243,6 +291,30 @@ function prepareToDeleteSalesChannelLinks({
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function prepareToDeleteShippingProfileLinks({
|
||||||
|
currentShippingProfileLinks,
|
||||||
|
}: {
|
||||||
|
currentShippingProfileLinks: {
|
||||||
|
product_id: string
|
||||||
|
shipping_profile_id: string
|
||||||
|
}[]
|
||||||
|
}) {
|
||||||
|
if (!currentShippingProfileLinks.length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentShippingProfileLinks.map(
|
||||||
|
({ product_id, shipping_profile_id }) => ({
|
||||||
|
[Modules.PRODUCT]: {
|
||||||
|
product_id,
|
||||||
|
},
|
||||||
|
[Modules.FULFILLMENT]: {
|
||||||
|
shipping_profile_id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const updateProductsWorkflowId = "update-products"
|
export const updateProductsWorkflowId = "update-products"
|
||||||
/**
|
/**
|
||||||
* This workflow updates one or more products. It's used by the [Update Product Admin API Route](https://docs.medusajs.com/api/admin#products_postproductsid).
|
* This workflow updates one or more products. It's used by the [Update Product Admin API Route](https://docs.medusajs.com/api/admin#products_postproductsid).
|
||||||
@@ -345,11 +417,20 @@ export const updateProductsWorkflow = createWorkflow(
|
|||||||
const toUpdateInput = transform({ input }, prepareUpdateProductInput)
|
const toUpdateInput = transform({ input }, prepareUpdateProductInput)
|
||||||
const updatedProducts = updateProductsStep(toUpdateInput)
|
const updatedProducts = updateProductsStep(toUpdateInput)
|
||||||
|
|
||||||
|
const updatedPorductIds = transform({ updatedProducts }, (data) => {
|
||||||
|
return data.updatedProducts.map((p) => p.id)
|
||||||
|
})
|
||||||
|
|
||||||
const salesChannelLinks = transform(
|
const salesChannelLinks = transform(
|
||||||
{ input, updatedProducts },
|
{ input, updatedProducts },
|
||||||
prepareSalesChannelLinks
|
prepareSalesChannelLinks
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const shippingProfileLinks = transform(
|
||||||
|
{ input, updatedProducts },
|
||||||
|
prepareShippingProfileLinks
|
||||||
|
)
|
||||||
|
|
||||||
const variantPrices = transform(
|
const variantPrices = transform(
|
||||||
{ input, updatedProducts },
|
{ input, updatedProducts },
|
||||||
prepareVariantPrices
|
prepareVariantPrices
|
||||||
@@ -366,16 +447,33 @@ export const updateProductsWorkflow = createWorkflow(
|
|||||||
variables: { filters: { product_id: productsWithSalesChannels } },
|
variables: { filters: { product_id: productsWithSalesChannels } },
|
||||||
}).config({ name: "get-current-sales-channel-links-step" })
|
}).config({ name: "get-current-sales-channel-links-step" })
|
||||||
|
|
||||||
|
const currentShippingProfileLinks = useRemoteQueryStep({
|
||||||
|
entry_point: "product_shipping_profile",
|
||||||
|
fields: ["product_id", "shipping_profile_id"],
|
||||||
|
variables: { filters: { product_id: updatedPorductIds } },
|
||||||
|
}).config({ name: "get-current-shipping-profile-links-step" })
|
||||||
|
|
||||||
const toDeleteSalesChannelLinks = transform(
|
const toDeleteSalesChannelLinks = transform(
|
||||||
{ currentSalesChannelLinks },
|
{ currentSalesChannelLinks },
|
||||||
prepareToDeleteSalesChannelLinks
|
prepareToDeleteSalesChannelLinks
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const toDeleteShippingProfileLinks = transform(
|
||||||
|
{ currentShippingProfileLinks },
|
||||||
|
prepareToDeleteShippingProfileLinks
|
||||||
|
)
|
||||||
|
|
||||||
upsertVariantPricesWorkflow.runAsStep({
|
upsertVariantPricesWorkflow.runAsStep({
|
||||||
input: { variantPrices, previousVariantIds },
|
input: { variantPrices, previousVariantIds },
|
||||||
})
|
})
|
||||||
|
|
||||||
dismissRemoteLinkStep(toDeleteSalesChannelLinks)
|
dismissRemoteLinkStep(toDeleteSalesChannelLinks).config({
|
||||||
|
name: "delete-sales-channel-links-step",
|
||||||
|
})
|
||||||
|
|
||||||
|
dismissRemoteLinkStep(toDeleteShippingProfileLinks).config({
|
||||||
|
name: "delete-shipping-profile-links-step",
|
||||||
|
})
|
||||||
|
|
||||||
const productIdEvents = transform(
|
const productIdEvents = transform(
|
||||||
{ updatedProducts },
|
{ updatedProducts },
|
||||||
@@ -387,7 +485,12 @@ export const updateProductsWorkflow = createWorkflow(
|
|||||||
)
|
)
|
||||||
|
|
||||||
parallelize(
|
parallelize(
|
||||||
createRemoteLinkStep(salesChannelLinks),
|
createRemoteLinkStep(salesChannelLinks).config({
|
||||||
|
name: "create-sales-channel-links-step",
|
||||||
|
}),
|
||||||
|
createRemoteLinkStep(shippingProfileLinks).config({
|
||||||
|
name: "create-shipping-profile-links-step",
|
||||||
|
}),
|
||||||
emitEventStep({
|
emitEventStep({
|
||||||
eventName: ProductWorkflowEvents.UPDATED,
|
eventName: ProductWorkflowEvents.UPDATED,
|
||||||
data: productIdEvents,
|
data: productIdEvents,
|
||||||
|
|||||||
@@ -1,8 +1,31 @@
|
|||||||
import { createWorkflow, WorkflowData } from "@medusajs/framework/workflows-sdk"
|
import {
|
||||||
|
createStep,
|
||||||
|
createWorkflow,
|
||||||
|
WorkflowData,
|
||||||
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
|
import { MedusaError, Modules } from "@medusajs/framework/utils"
|
||||||
|
|
||||||
import { deleteShippingProfilesStep } from "../steps"
|
import { deleteShippingProfilesStep } from "../steps"
|
||||||
import { removeRemoteLinkStep } from "../../common"
|
import { removeRemoteLinkStep, useQueryGraphStep } from "../../common"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
/**
|
||||||
|
* This step validates that the shipping profiles to delete are not linked to any products.
|
||||||
|
*/
|
||||||
|
const validateStepShippingProfileDelete = createStep(
|
||||||
|
"validate-step-shipping-profile-delete",
|
||||||
|
(data: { links: { product_id: string; shipping_profile_id: string }[] }) => {
|
||||||
|
const { links } = data
|
||||||
|
|
||||||
|
if (links.length > 0) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Cannot delete following shipping profiles because they are linked to products: ${links
|
||||||
|
.map((l) => l.product_id)
|
||||||
|
.join(", ")}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The data to delete shipping profiles.
|
* The data to delete shipping profiles.
|
||||||
@@ -19,6 +42,7 @@ export const deleteShippingProfileWorkflowId =
|
|||||||
/**
|
/**
|
||||||
* This workflow deletes one or more shipping profiles. It's used by the
|
* This workflow deletes one or more shipping profiles. It's used by the
|
||||||
* [Delete Shipping Profile Admin API Route](https://docs.medusajs.com/api/admin#shipping-profiles_deleteshippingprofilesid).
|
* [Delete Shipping Profile Admin API Route](https://docs.medusajs.com/api/admin#shipping-profiles_deleteshippingprofilesid).
|
||||||
|
* Shipping profiles that are linked to products cannot be deleted.
|
||||||
*
|
*
|
||||||
* You can use this workflow within your customizations or your own custom workflows, allowing you to
|
* You can use this workflow within your customizations or your own custom workflows, allowing you to
|
||||||
* delete shipping profiles within your custom flows.
|
* delete shipping profiles within your custom flows.
|
||||||
@@ -38,6 +62,16 @@ export const deleteShippingProfileWorkflowId =
|
|||||||
export const deleteShippingProfileWorkflow = createWorkflow(
|
export const deleteShippingProfileWorkflow = createWorkflow(
|
||||||
deleteShippingProfileWorkflowId,
|
deleteShippingProfileWorkflowId,
|
||||||
(input: WorkflowData<DeleteShippingProfilesWorkflowInput>) => {
|
(input: WorkflowData<DeleteShippingProfilesWorkflowInput>) => {
|
||||||
|
const currentShippingProfileLinks = useQueryGraphStep({
|
||||||
|
entity: "product_shipping_profile",
|
||||||
|
fields: ["product_id", "shipping_profile_id"],
|
||||||
|
filters: { shipping_profile_id: input.ids },
|
||||||
|
})
|
||||||
|
|
||||||
|
validateStepShippingProfileDelete({
|
||||||
|
links: currentShippingProfileLinks.data,
|
||||||
|
})
|
||||||
|
|
||||||
deleteShippingProfilesStep(input.ids)
|
deleteShippingProfilesStep(input.ids)
|
||||||
|
|
||||||
removeRemoteLinkStep({
|
removeRemoteLinkStep({
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ export interface AdminCreateOrderFulfillment {
|
|||||||
* to fulfill the items from.
|
* to fulfill the items from.
|
||||||
*/
|
*/
|
||||||
location_id?: string
|
location_id?: string
|
||||||
|
/**
|
||||||
|
* The ID of the shipping option to use for the fulfillment.
|
||||||
|
* Overrides the shipping option selected by the customer.
|
||||||
|
*/
|
||||||
|
shipping_option_id?: string
|
||||||
/**
|
/**
|
||||||
* Whether to notify the customer about this change.
|
* Whether to notify the customer about this change.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -210,6 +210,10 @@ export interface AdminCreateProduct {
|
|||||||
* The ID of the product's collection.
|
* The ID of the product's collection.
|
||||||
*/
|
*/
|
||||||
collection_id?: string
|
collection_id?: string
|
||||||
|
/**
|
||||||
|
* The ID of the product's shipping profile.
|
||||||
|
*/
|
||||||
|
shipping_profile_id: string
|
||||||
/**
|
/**
|
||||||
* The product's categories.
|
* The product's categories.
|
||||||
*/
|
*/
|
||||||
@@ -363,7 +367,8 @@ export interface AdminUpdateProductVariant {
|
|||||||
options?: Record<string, string>
|
options?: Record<string, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminBatchUpdateProductVariant extends AdminUpdateProductVariant {
|
export interface AdminBatchUpdateProductVariant
|
||||||
|
extends AdminUpdateProductVariant {
|
||||||
/**
|
/**
|
||||||
* The ID of the variant to update.
|
* The ID of the variant to update.
|
||||||
*/
|
*/
|
||||||
@@ -459,6 +464,10 @@ export interface AdminUpdateProduct {
|
|||||||
*/
|
*/
|
||||||
id: string
|
id: string
|
||||||
}[]
|
}[]
|
||||||
|
/**
|
||||||
|
* The ID of the product's shipping profile.
|
||||||
|
*/
|
||||||
|
shipping_profile_id?: string
|
||||||
/**
|
/**
|
||||||
* The product's weight.
|
* The product's weight.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -62,6 +62,11 @@ export interface CreateOrderFulfillmentWorkflowInput {
|
|||||||
*/
|
*/
|
||||||
metadata?: Record<string, any> | null
|
metadata?: Record<string, any> | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shipping option to be used for the fulfillment.
|
||||||
|
*/
|
||||||
|
shipping_option_id?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the fulfillment should be shipped.
|
* Whether the fulfillment should be shipped.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ export type CreateProductWorkflowInputDTO = Omit<
|
|||||||
* The sales channels that the product is available in.
|
* The sales channels that the product is available in.
|
||||||
*/
|
*/
|
||||||
sales_channels?: { id: string }[]
|
sales_channels?: { id: string }[]
|
||||||
|
/**
|
||||||
|
* The product's shipping profile.
|
||||||
|
*/
|
||||||
|
shipping_profile_id: string
|
||||||
/**
|
/**
|
||||||
* The product's variants.
|
* The product's variants.
|
||||||
*/
|
*/
|
||||||
@@ -41,4 +45,5 @@ export type CreateProductWorkflowInputDTO = Omit<
|
|||||||
|
|
||||||
export type UpdateProductWorkflowInputDTO = ProductTypes.UpsertProductDTO & {
|
export type UpdateProductWorkflowInputDTO = ProductTypes.UpsertProductDTO & {
|
||||||
sales_channels?: { id: string }[]
|
sales_channels?: { id: string }[]
|
||||||
|
shipping_profile_id?: string
|
||||||
}
|
}
|
||||||
@@ -116,4 +116,10 @@ export const LINKS = {
|
|||||||
Modules.FULFILLMENT,
|
Modules.FULFILLMENT,
|
||||||
"fulfillment_id"
|
"fulfillment_id"
|
||||||
),
|
),
|
||||||
|
ProductShippingProfile: composeLinkName(
|
||||||
|
Modules.PRODUCT,
|
||||||
|
"product_id",
|
||||||
|
Modules.FULFILLMENT,
|
||||||
|
"shipping_profile_id"
|
||||||
|
),
|
||||||
}
|
}
|
||||||
@@ -75,6 +75,7 @@ export type AdminOrderCreateFulfillmentType = z.infer<
|
|||||||
export const OrderCreateFulfillment = z.object({
|
export const OrderCreateFulfillment = z.object({
|
||||||
items: z.array(Item),
|
items: z.array(Item),
|
||||||
location_id: z.string().nullish(),
|
location_id: z.string().nullish(),
|
||||||
|
shipping_option_id: z.string().optional(),
|
||||||
no_notification: z.boolean().optional(),
|
no_notification: z.boolean().optional(),
|
||||||
metadata: z.record(z.unknown()).nullish(),
|
metadata: z.record(z.unknown()).nullish(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -232,6 +232,7 @@ export const CreateProduct = z
|
|||||||
options: z.array(CreateProductOption).optional(),
|
options: z.array(CreateProductOption).optional(),
|
||||||
variants: z.array(CreateProductVariant).optional(),
|
variants: z.array(CreateProductVariant).optional(),
|
||||||
sales_channels: z.array(z.object({ id: z.string() })).optional(),
|
sales_channels: z.array(z.object({ id: z.string() })).optional(),
|
||||||
|
shipping_profile_id: z.string(),
|
||||||
weight: z.number().nullish(),
|
weight: z.number().nullish(),
|
||||||
length: z.number().nullish(),
|
length: z.number().nullish(),
|
||||||
height: z.number().nullish(),
|
height: z.number().nullish(),
|
||||||
@@ -266,6 +267,7 @@ export const UpdateProduct = z
|
|||||||
categories: z.array(IdAssociation).optional(),
|
categories: z.array(IdAssociation).optional(),
|
||||||
tags: z.array(IdAssociation).optional(),
|
tags: z.array(IdAssociation).optional(),
|
||||||
sales_channels: z.array(z.object({ id: z.string() })).optional(),
|
sales_channels: z.array(z.object({ id: z.string() })).optional(),
|
||||||
|
shipping_profile_id: z.string().optional(),
|
||||||
weight: z.number().nullish(),
|
weight: z.number().nullish(),
|
||||||
length: z.number().nullish(),
|
length: z.number().nullish(),
|
||||||
height: z.number().nullish(),
|
height: z.number().nullish(),
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import {
|
||||||
|
createRemoteLinkStep,
|
||||||
|
createShippingProfilesStep,
|
||||||
|
useQueryGraphStep,
|
||||||
|
} from "@medusajs/core-flows"
|
||||||
|
import { ExecArgs } from "@medusajs/framework/types"
|
||||||
|
import { ContainerRegistrationKeys, Modules } from "@medusajs/framework/utils"
|
||||||
|
import {
|
||||||
|
transform,
|
||||||
|
when,
|
||||||
|
WorkflowResponse,
|
||||||
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
|
import { createWorkflow } from "@medusajs/framework/workflows-sdk"
|
||||||
|
|
||||||
|
const assignProductsToShippingProfileWorkflow = createWorkflow(
|
||||||
|
"assign-products-to-shipping-profile",
|
||||||
|
() => {
|
||||||
|
const { data: shippingProfiles } = useQueryGraphStep({
|
||||||
|
entity: "shipping_profile",
|
||||||
|
fields: ["id", "name"],
|
||||||
|
}).config({ name: "get-shipping-profiles" })
|
||||||
|
|
||||||
|
const { data: products } = useQueryGraphStep({
|
||||||
|
entity: "product",
|
||||||
|
fields: ["id"],
|
||||||
|
}).config({ name: "get-products" })
|
||||||
|
|
||||||
|
const shippingProfileId = transform(
|
||||||
|
{ shippingProfiles },
|
||||||
|
({ shippingProfiles }) =>
|
||||||
|
shippingProfiles.find((sp) =>
|
||||||
|
sp.name.toLocaleLowerCase().includes("default")
|
||||||
|
)?.id ?? shippingProfiles[0]?.id
|
||||||
|
)
|
||||||
|
|
||||||
|
const createdShippingProfileId = when(
|
||||||
|
"create-shipping-profile",
|
||||||
|
{
|
||||||
|
shippingProfileId,
|
||||||
|
},
|
||||||
|
({ shippingProfileId }) => !shippingProfileId
|
||||||
|
).then(() => {
|
||||||
|
const createdShippingProfiles = createShippingProfilesStep([
|
||||||
|
{
|
||||||
|
name: "Default Shipping Profile",
|
||||||
|
type: "default",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
return createdShippingProfiles[0].id
|
||||||
|
})
|
||||||
|
|
||||||
|
const links = transform(
|
||||||
|
{ products, shippingProfileId, createdShippingProfileId },
|
||||||
|
({ products, shippingProfileId, createdShippingProfileId }) => {
|
||||||
|
return products.map((product) => ({
|
||||||
|
[Modules.PRODUCT]: {
|
||||||
|
product_id: product.id,
|
||||||
|
},
|
||||||
|
[Modules.FULFILLMENT]: {
|
||||||
|
shipping_profile_id: shippingProfileId ?? createdShippingProfileId,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
createRemoteLinkStep(links)
|
||||||
|
|
||||||
|
return new WorkflowResponse(void 0)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default async function assignProductsToShippingProfile({
|
||||||
|
container,
|
||||||
|
}: ExecArgs) {
|
||||||
|
const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
|
||||||
|
|
||||||
|
logger.info("Assigning products to shipping profile")
|
||||||
|
|
||||||
|
await assignProductsToShippingProfileWorkflow(container)
|
||||||
|
.run()
|
||||||
|
.then(() => {
|
||||||
|
logger.info("Products assigned to shipping profile")
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
logger.error(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -15,3 +15,4 @@ export * from "./readonly"
|
|||||||
export * from "./region-payment-provider"
|
export * from "./region-payment-provider"
|
||||||
export * from "./sales-channel-location"
|
export * from "./sales-channel-location"
|
||||||
export * from "./shipping-option-price-set"
|
export * from "./shipping-option-price-set"
|
||||||
|
export * from "./product-shipping-profile"
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { ModuleJoinerConfig } from "@medusajs/framework/types"
|
||||||
|
import { LINKS, Modules } from "@medusajs/framework/utils"
|
||||||
|
|
||||||
|
export const ProductShippingProfile: ModuleJoinerConfig = {
|
||||||
|
serviceName: LINKS.ProductShippingProfile,
|
||||||
|
isLink: true,
|
||||||
|
databaseConfig: {
|
||||||
|
tableName: "product_shipping_profile",
|
||||||
|
idPrefix: "prodsp",
|
||||||
|
},
|
||||||
|
alias: [
|
||||||
|
{
|
||||||
|
name: "product_shipping_profile",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "product_shipping_profiles",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
primaryKeys: ["id", "product_id", "shipping_profile_id"],
|
||||||
|
relationships: [
|
||||||
|
{
|
||||||
|
serviceName: Modules.PRODUCT,
|
||||||
|
entity: "Product",
|
||||||
|
primaryKey: "id",
|
||||||
|
foreignKey: "product_id",
|
||||||
|
alias: "product",
|
||||||
|
args: {
|
||||||
|
methodSuffix: "Products",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
serviceName: Modules.FULFILLMENT,
|
||||||
|
entity: "ShippingProfile",
|
||||||
|
primaryKey: "id",
|
||||||
|
foreignKey: "shipping_profile_id",
|
||||||
|
alias: "shipping_profile",
|
||||||
|
args: {
|
||||||
|
methodSuffix: "ShippingProfiles",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extends: [
|
||||||
|
{
|
||||||
|
serviceName: Modules.PRODUCT,
|
||||||
|
entity: "Product",
|
||||||
|
fieldAlias: {
|
||||||
|
shipping_profile: {
|
||||||
|
path: "shipping_profiles_link.shipping_profile",
|
||||||
|
isList: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
relationship: {
|
||||||
|
serviceName: LINKS.ProductShippingProfile,
|
||||||
|
primaryKey: "product_id",
|
||||||
|
foreignKey: "id",
|
||||||
|
alias: "shipping_profiles_link",
|
||||||
|
isList: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
serviceName: Modules.FULFILLMENT,
|
||||||
|
entity: "ShippingProfile",
|
||||||
|
relationship: {
|
||||||
|
serviceName: LINKS.ProductShippingProfile,
|
||||||
|
primaryKey: "shipping_profile_id",
|
||||||
|
foreignKey: "id",
|
||||||
|
alias: "products_link",
|
||||||
|
isList: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
Reference in new issue
Block a user