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:
@@ -43,7 +43,8 @@ medusaIntegrationTestRunner({
|
||||
salesChannel,
|
||||
cart,
|
||||
customer,
|
||||
promotion
|
||||
promotion,
|
||||
shippingProfile
|
||||
|
||||
beforeAll(async () => {
|
||||
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))
|
||||
|
||||
region = (
|
||||
@@ -87,7 +96,7 @@ medusaIntegrationTestRunner({
|
||||
).data.region
|
||||
|
||||
product = (
|
||||
await api.post("/admin/products", medusaTshirtProduct, adminHeaders)
|
||||
await api.post("/admin/products", { ...medusaTshirtProduct, shipping_profile_id: shippingProfile.id }, adminHeaders)
|
||||
).data.product
|
||||
|
||||
salesChannel = (
|
||||
|
||||
@@ -34,6 +34,17 @@ medusaIntegrationTestRunner({
|
||||
const container = getContainer()
|
||||
await createAdminUser(dbConnection, adminHeaders, container)
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{
|
||||
name: "Test",
|
||||
type: "default",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
const region = (
|
||||
await api.post(
|
||||
"/admin/regions",
|
||||
@@ -72,6 +83,7 @@ medusaIntegrationTestRunner({
|
||||
{
|
||||
title: "Test product",
|
||||
options: [{ title: "size", values: ["large", "small"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Test variant",
|
||||
@@ -96,6 +108,7 @@ medusaIntegrationTestRunner({
|
||||
{
|
||||
title: "Extra product",
|
||||
options: [{ title: "size", values: ["large", "small"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "my variant",
|
||||
@@ -213,17 +226,6 @@ medusaIntegrationTestRunner({
|
||||
customer_id: customer.id,
|
||||
})
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{
|
||||
name: "Test",
|
||||
type: "default",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
location = (
|
||||
await api.post(
|
||||
`/admin/stock-locations`,
|
||||
|
||||
@@ -16,6 +16,8 @@ medusaIntegrationTestRunner({
|
||||
let baseProduct
|
||||
let baseProduct1
|
||||
|
||||
let shippingProfile
|
||||
|
||||
beforeEach(async () => {
|
||||
const container = getContainer()
|
||||
await createAdminUser(dbConnection, adminHeaders, container)
|
||||
@@ -44,12 +46,21 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
).data.collection
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: "Test", type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
baseProduct = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
{
|
||||
title: "test-product",
|
||||
options: [{ title: "size", values: ["x", "l"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
@@ -61,6 +72,7 @@ medusaIntegrationTestRunner({
|
||||
{
|
||||
title: "test-product1",
|
||||
options: [{ title: "size", values: ["x", "l"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
@@ -41,6 +41,17 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
).data.region
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{
|
||||
name: "Test",
|
||||
type: "default",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
const customer = (
|
||||
await api.post(
|
||||
"/admin/customers",
|
||||
@@ -67,6 +78,7 @@ medusaIntegrationTestRunner({
|
||||
"/admin/products",
|
||||
{
|
||||
title: "Test product",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [{ title: "size", values: ["large", "small"] }],
|
||||
variants: [
|
||||
{
|
||||
@@ -91,6 +103,7 @@ medusaIntegrationTestRunner({
|
||||
"/admin/products",
|
||||
{
|
||||
title: "Extra product",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [{ title: "size", values: ["large", "small"] }],
|
||||
variants: [
|
||||
{
|
||||
@@ -203,17 +216,6 @@ medusaIntegrationTestRunner({
|
||||
customer_id: customer.id,
|
||||
})
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{
|
||||
name: "Test",
|
||||
type: "default",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
location = (
|
||||
await api.post(
|
||||
`/admin/stock-locations`,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
AdminInventoryItem,
|
||||
AdminProduct,
|
||||
AdminShippingProfile,
|
||||
AdminStockLocation,
|
||||
MedusaContainer,
|
||||
} from "@medusajs/types"
|
||||
@@ -18,6 +19,8 @@ export async function createOrderSeeder({
|
||||
additionalProducts,
|
||||
stockChannelOverride,
|
||||
inventoryItemOverride,
|
||||
shippingProfileOverride,
|
||||
withoutShipping,
|
||||
}: {
|
||||
api: any
|
||||
container: MedusaContainer
|
||||
@@ -26,6 +29,8 @@ export async function createOrderSeeder({
|
||||
stockChannelOverride?: AdminStockLocation
|
||||
additionalProducts?: { variant_id: string; quantity: number }[]
|
||||
inventoryItemOverride?: AdminInventoryItem
|
||||
shippingProfileOverride?: AdminShippingProfile
|
||||
withoutShipping?: boolean
|
||||
}) {
|
||||
const publishableKey = await generatePublishableKey(container)
|
||||
|
||||
@@ -86,13 +91,15 @@ export async function createOrderSeeder({
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: `test-${stockLocation.id}`, type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
const shippingProfile =
|
||||
shippingProfileOverride ??
|
||||
(
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: `test-${stockLocation.id}`, type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
const product =
|
||||
productOverride ??
|
||||
@@ -101,6 +108,7 @@ export async function createOrderSeeder({
|
||||
"/admin/products",
|
||||
{
|
||||
title: `Test fixture ${shippingProfile.id}`,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [
|
||||
{ title: "size", values: ["large", "small"] },
|
||||
{ title: "color", values: ["green"] },
|
||||
@@ -217,6 +225,14 @@ export async function createOrderSeeder({
|
||||
)
|
||||
).data.cart
|
||||
|
||||
if (!withoutShipping) {
|
||||
await api.post(
|
||||
`/store/carts/${cart.id}/shipping-methods`,
|
||||
{ option_id: shippingOption.id },
|
||||
storeHeaders
|
||||
)
|
||||
}
|
||||
|
||||
const paymentCollection = (
|
||||
await api.post(
|
||||
`/store/payment-collections`,
|
||||
|
||||
@@ -13,9 +13,19 @@ medusaIntegrationTestRunner({
|
||||
let stockLocation1
|
||||
let stockLocation2
|
||||
let stockLocation3
|
||||
|
||||
let shippingProfile
|
||||
beforeEach(async () => {
|
||||
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: "Test", type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
stockLocation1 = (
|
||||
await api.post(`/admin/stock-locations`, { name: "loc1" }, adminHeaders)
|
||||
).data.stock_location
|
||||
@@ -990,6 +1000,7 @@ medusaIntegrationTestRunner({
|
||||
{
|
||||
title: "product 1",
|
||||
options: [{ title: "size", values: ["large"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "variant 1",
|
||||
|
||||
@@ -85,12 +85,24 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
).data.sales_channel
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{
|
||||
name: "Test",
|
||||
type: "default",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
const product = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
{
|
||||
title: "Test product",
|
||||
options: [{ title: "size", values: ["large", "small"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Test variant",
|
||||
@@ -115,6 +127,7 @@ medusaIntegrationTestRunner({
|
||||
{
|
||||
title: "Extra product",
|
||||
options: [{ title: "size", values: ["large", "small"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "my variant",
|
||||
@@ -174,17 +187,6 @@ medusaIntegrationTestRunner({
|
||||
customer_id: customer.id,
|
||||
})
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{
|
||||
name: "Test",
|
||||
type: "default",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
location = (
|
||||
await api.post(
|
||||
`/admin/stock-locations`,
|
||||
|
||||
@@ -11,13 +11,21 @@ jest.setTimeout(300000)
|
||||
|
||||
medusaIntegrationTestRunner({
|
||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||
let order, seeder, inventoryItemOverride3, productOverride3
|
||||
let order, seeder, inventoryItemOverride3, productOverride3, shippingProfile
|
||||
|
||||
beforeEach(async () => {
|
||||
const container = getContainer()
|
||||
|
||||
await setupTaxStructure(container.resolve(ModuleRegistrationName.TAX))
|
||||
await createAdminUser(dbConnection, adminHeaders, container)
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: "Test", type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
})
|
||||
|
||||
describe("POST /orders/:id", () => {
|
||||
@@ -345,9 +353,19 @@ medusaIntegrationTestRunner({
|
||||
|
||||
describe("POST /orders/:id/cancel", () => {
|
||||
beforeEach(async () => {
|
||||
const inventoryItemOverride = (
|
||||
await api.post(
|
||||
`/admin/inventory-items`,
|
||||
{ sku: "test-variant", requires_shipping: false },
|
||||
adminHeaders
|
||||
)
|
||||
).data.inventory_item
|
||||
|
||||
seeder = await createOrderSeeder({
|
||||
api,
|
||||
container: getContainer(),
|
||||
inventoryItemOverride,
|
||||
withoutShipping: true,
|
||||
})
|
||||
order = seeder.order
|
||||
|
||||
@@ -536,8 +554,12 @@ medusaIntegrationTestRunner({
|
||||
})
|
||||
|
||||
describe("POST /orders/:id/fulfillments", () => {
|
||||
let productOverride4WithOverrideShippingProfile,
|
||||
shippingProfileOverride,
|
||||
stockChannelOverride
|
||||
|
||||
beforeEach(async () => {
|
||||
const stockChannelOverride = (
|
||||
stockChannelOverride = (
|
||||
await api.post(
|
||||
`/admin/stock-locations`,
|
||||
{ name: "test location" },
|
||||
@@ -558,6 +580,7 @@ medusaIntegrationTestRunner({
|
||||
"/admin/products",
|
||||
{
|
||||
title: `Test fixture`,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [
|
||||
{ title: "size", values: ["large", "small"] },
|
||||
{ title: "color", values: ["green"] },
|
||||
@@ -605,6 +628,14 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
).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(
|
||||
`/admin/inventory-items/${inventoryItemOverride2.id}/location-levels`,
|
||||
{
|
||||
@@ -623,11 +654,21 @@ medusaIntegrationTestRunner({
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
await api.post(
|
||||
`/admin/inventory-items/${inventoryItemOverride4RequiresShipping.id}/location-levels`,
|
||||
{
|
||||
location_id: stockChannelOverride.id,
|
||||
stocked_quantity: 10,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const productOverride2 = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
{
|
||||
title: `Test fixture 2`,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [
|
||||
{ title: "size", values: ["large", "small"] },
|
||||
{ title: "color", values: ["green"] },
|
||||
@@ -664,6 +705,7 @@ medusaIntegrationTestRunner({
|
||||
"/admin/products",
|
||||
{
|
||||
title: `Test fixture 3`,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [
|
||||
{ title: "size", values: ["large", "small"] },
|
||||
{ title: "color", values: ["green"] },
|
||||
@@ -695,6 +737,52 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
).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({
|
||||
api,
|
||||
container: getContainer(),
|
||||
@@ -702,9 +790,15 @@ medusaIntegrationTestRunner({
|
||||
additionalProducts: [
|
||||
{ variant_id: productOverride2.variants[0].id, quantity: 1 },
|
||||
{ variant_id: productOverride3.variants[0].id, quantity: 3 },
|
||||
{
|
||||
variant_id:
|
||||
productOverride4WithOverrideShippingProfile.variants[0].id,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
stockChannelOverride,
|
||||
inventoryItemOverride,
|
||||
shippingProfileOverride: shippingProfile,
|
||||
})
|
||||
order = seeder.order
|
||||
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 () => {
|
||||
const {
|
||||
response: { data },
|
||||
|
||||
@@ -24,7 +24,21 @@ medusaIntegrationTestRunner({
|
||||
|
||||
await setupTaxStructure(container.resolve(Modules.TAX))
|
||||
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
|
||||
|
||||
shippingProfile = (
|
||||
|
||||
@@ -24,6 +24,7 @@ medusaIntegrationTestRunner({
|
||||
let region
|
||||
let product
|
||||
let cart
|
||||
let shippingProfile
|
||||
|
||||
beforeEach(async () => {
|
||||
region = (
|
||||
@@ -34,12 +35,21 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
).data.region
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: "Test", type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
product = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
getProductFixture({
|
||||
title: "test",
|
||||
status: "published",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Test variant",
|
||||
|
||||
@@ -38,7 +38,22 @@ medusaIntegrationTestRunner({
|
||||
beforeEach(async () => {
|
||||
container = getContainer()
|
||||
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
|
||||
|
||||
await api.post(
|
||||
|
||||
@@ -18,6 +18,7 @@ medusaIntegrationTestRunner({
|
||||
let region1
|
||||
let product1
|
||||
let customerGroup1
|
||||
let shippingProfile
|
||||
|
||||
beforeEach(async () => {
|
||||
const container = getContainer()
|
||||
@@ -34,10 +35,21 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
).data.region
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: "Test", type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
product1 = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
getProductFixture({ title: "Test product" }),
|
||||
getProductFixture({
|
||||
title: "Test product",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
}),
|
||||
adminHeaders
|
||||
)
|
||||
).data.product
|
||||
|
||||
@@ -18,9 +18,19 @@ medusaIntegrationTestRunner({
|
||||
let productCategoryChild2
|
||||
let productCategoryChild3
|
||||
|
||||
let shippingProfile
|
||||
|
||||
beforeEach(async () => {
|
||||
const appContainer = getContainer()
|
||||
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", () => {
|
||||
@@ -1349,6 +1359,7 @@ medusaIntegrationTestRunner({
|
||||
title: "product 1",
|
||||
options: [{ title: "size", values: ["x", "l"] }],
|
||||
categories: [{ id: productCategory.id }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
@@ -1358,6 +1369,7 @@ medusaIntegrationTestRunner({
|
||||
{
|
||||
title: "product 2",
|
||||
options: [{ title: "color", values: ["r", "g"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
},
|
||||
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
|
||||
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
|
||||
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,,
|
||||
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,,
|
||||
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,,,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
|
||||
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
|
||||
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;;
|
||||
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;;
|
||||
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;;;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
|
||||
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,,
|
||||
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,,,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
|
||||
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
|
||||
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,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
|
||||
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
|
||||
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,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
|
||||
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,,
|
||||
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,,,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
|
||||
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
|
||||
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
|
||||
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
|
||||
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,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
|
||||
,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-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
|
||||
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 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,,existing-variant-id,Test variant changed,test-sku-4,test-barcode-4,10,FALSE,TRUE,,,,,,,,,,,Size,Large,,,test-image.png
|
||||
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,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,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,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,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,import-shipping-profile
|
||||
|
@@ -39,6 +39,9 @@ const compareCSVs = async (filePath, expectedFilePath) => {
|
||||
fileContent = fileContent.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)
|
||||
}
|
||||
|
||||
@@ -56,6 +59,7 @@ medusaIntegrationTestRunner({
|
||||
let baseTag1
|
||||
let baseTag2
|
||||
let newTag
|
||||
let shippingProfile
|
||||
|
||||
let eventBus: IEventBusModuleService
|
||||
beforeAll(async () => {
|
||||
@@ -92,6 +96,14 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
).data.collection
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: "Test", type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
baseType = (
|
||||
await api.post(
|
||||
"/admin/product-types",
|
||||
@@ -130,6 +142,7 @@ medusaIntegrationTestRunner({
|
||||
getProductFixture({
|
||||
title: "Base product",
|
||||
description: "test-product-description\ntest line 2",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
collection_id: baseCollection.id,
|
||||
type_id: baseType.id,
|
||||
categories: [{ id: baseCategory.id }],
|
||||
@@ -191,6 +204,7 @@ medusaIntegrationTestRunner({
|
||||
status: "proposed",
|
||||
tags: [{ id: newTag.id }],
|
||||
type_id: baseType.id,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
}),
|
||||
adminHeaders
|
||||
)
|
||||
@@ -281,6 +295,7 @@ medusaIntegrationTestRunner({
|
||||
"/admin/products",
|
||||
getProductFixture({
|
||||
title: "Product with prices",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
||||
variants: [
|
||||
{
|
||||
|
||||
@@ -40,6 +40,7 @@ medusaIntegrationTestRunner({
|
||||
let baseTag2
|
||||
let baseTag3
|
||||
let newTag
|
||||
let shippingProfile
|
||||
|
||||
let eventBus: IEventBusModuleService
|
||||
beforeAll(async () => {
|
||||
@@ -84,12 +85,21 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
).data.product_tag
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: "Test", type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
baseProduct = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
getProductFixture({
|
||||
title: "Base product",
|
||||
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
}),
|
||||
adminHeaders
|
||||
)
|
||||
@@ -150,6 +160,11 @@ medusaIntegrationTestRunner({
|
||||
fileContent = fileContent.replace(/pcol_\w*\d*/g, baseCollection.id)
|
||||
fileContent = fileContent.replace(/ptyp_\w*\d*/g, baseType.id)
|
||||
|
||||
fileContent = fileContent.replace(
|
||||
/import-shipping-profile*/g,
|
||||
shippingProfile.id
|
||||
)
|
||||
|
||||
const { form, meta } = getUploadReq({
|
||||
name: "test.csv",
|
||||
content: fileContent,
|
||||
@@ -400,6 +415,11 @@ medusaIntegrationTestRunner({
|
||||
fileContent = fileContent.replace(/ptyp_\w*\d*/g, baseType.id)
|
||||
fileContent = fileContent.replace(/pcat_\w*\d*/g, baseCategory.id)
|
||||
|
||||
fileContent = fileContent.replace(
|
||||
/import-shipping-profile*/g,
|
||||
shippingProfile.id
|
||||
)
|
||||
|
||||
const { form, meta } = getUploadReq({
|
||||
name: "test.csv",
|
||||
content: fileContent,
|
||||
@@ -440,6 +460,11 @@ medusaIntegrationTestRunner({
|
||||
{ encoding: "utf-8" }
|
||||
)
|
||||
|
||||
fileContent = fileContent.replace(
|
||||
/import-shipping-profile*/g,
|
||||
shippingProfile.id
|
||||
)
|
||||
|
||||
const { form, meta } = getUploadReq({
|
||||
name: "test.csv",
|
||||
content: fileContent,
|
||||
@@ -467,6 +492,11 @@ medusaIntegrationTestRunner({
|
||||
fileContent = fileContent.replace(/pcol_\w*\d*/g, baseCollection.id)
|
||||
fileContent = fileContent.replace(/ptyp_\w*\d*/g, baseType.id)
|
||||
|
||||
fileContent = fileContent.replace(
|
||||
/import-shipping-profile*/g,
|
||||
shippingProfile.id
|
||||
)
|
||||
|
||||
const { form, meta } = getUploadReq({
|
||||
name: "test.csv",
|
||||
content: fileContent,
|
||||
@@ -517,6 +547,11 @@ medusaIntegrationTestRunner({
|
||||
fileContent = fileContent.replace(/pcol_\w*\d*/g, baseCollection.id)
|
||||
fileContent = fileContent.replace(/ptyp_\w*\d*/g, baseType.id)
|
||||
|
||||
fileContent = fileContent.replace(
|
||||
/import-shipping-profile*/g,
|
||||
shippingProfile.id
|
||||
)
|
||||
|
||||
const { form, meta } = getUploadReq({
|
||||
name: "test.csv",
|
||||
content: fileContent,
|
||||
@@ -582,6 +617,11 @@ medusaIntegrationTestRunner({
|
||||
baseCollection.handle
|
||||
)
|
||||
|
||||
fileContent = fileContent.replace(
|
||||
/import-shipping-profile*/g,
|
||||
shippingProfile.id
|
||||
)
|
||||
|
||||
const { form, meta } = getUploadReq({
|
||||
name: "test.csv",
|
||||
content: fileContent,
|
||||
|
||||
@@ -22,6 +22,8 @@ medusaIntegrationTestRunner({
|
||||
let baseTag2
|
||||
let newTag
|
||||
|
||||
let shippingProfile
|
||||
|
||||
beforeEach(async () => {
|
||||
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
||||
|
||||
@@ -65,6 +67,14 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
).data.product_tag
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: "default", type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
baseProduct = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
@@ -74,6 +84,7 @@ medusaIntegrationTestRunner({
|
||||
// BREAKING: Type input changed from {type: {value: string}} to {type_id: string}
|
||||
type_id: baseType.id,
|
||||
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
images: [
|
||||
{
|
||||
url: "image-one",
|
||||
@@ -95,6 +106,7 @@ medusaIntegrationTestRunner({
|
||||
status: "proposed",
|
||||
tags: [{ id: newTag.id }],
|
||||
type_id: baseType.id,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
}),
|
||||
adminHeaders
|
||||
)
|
||||
@@ -108,6 +120,7 @@ medusaIntegrationTestRunner({
|
||||
status: "published",
|
||||
collection_id: publishedCollection.id,
|
||||
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
}),
|
||||
adminHeaders
|
||||
)
|
||||
@@ -115,7 +128,10 @@ medusaIntegrationTestRunner({
|
||||
deletedProduct = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
getProductFixture({ title: "Deleted product" }),
|
||||
getProductFixture({
|
||||
title: "Deleted product",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
}),
|
||||
adminHeaders
|
||||
)
|
||||
).data.product
|
||||
@@ -605,6 +621,7 @@ medusaIntegrationTestRunner({
|
||||
is_giftcard: true,
|
||||
description: "test-giftcard-description",
|
||||
options: [{ title: "Denominations", values: ["100"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Test variant",
|
||||
@@ -693,6 +710,7 @@ medusaIntegrationTestRunner({
|
||||
is_giftcard: true,
|
||||
description: "test-giftcard-description",
|
||||
options: [{ title: "size", values: ["x", "l"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Test variant",
|
||||
@@ -912,6 +930,7 @@ medusaIntegrationTestRunner({
|
||||
getProductFixture({
|
||||
title: "Test saleschannel",
|
||||
sales_channels: [{ id: salesChannel.id }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
}),
|
||||
adminHeaders
|
||||
)
|
||||
@@ -1098,6 +1117,7 @@ medusaIntegrationTestRunner({
|
||||
title: "Test product - 1",
|
||||
handle: "test-1",
|
||||
options: [{ title: "size", values: ["x", "l"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Custom inventory 1",
|
||||
@@ -1141,6 +1161,7 @@ medusaIntegrationTestRunner({
|
||||
title: "Test product - 1",
|
||||
handle: "test-1",
|
||||
options: [{ title: "size", values: ["x", "l"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Custom inventory 1",
|
||||
@@ -1202,6 +1223,7 @@ medusaIntegrationTestRunner({
|
||||
collection_id: baseCollection.id,
|
||||
type_id: baseType.id,
|
||||
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
}),
|
||||
|
||||
adminHeaders
|
||||
@@ -1341,6 +1363,7 @@ medusaIntegrationTestRunner({
|
||||
{
|
||||
title: "Test create",
|
||||
options: [{ title: "size", values: ["x", "l"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Price with rules",
|
||||
@@ -1394,6 +1417,7 @@ medusaIntegrationTestRunner({
|
||||
collection_id: baseCollection.id,
|
||||
tags: [{ id: baseTag1.id }, { id: baseTag2.id }],
|
||||
options: [{ title: "size", values: ["large"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Test variant",
|
||||
@@ -1425,7 +1449,8 @@ medusaIntegrationTestRunner({
|
||||
images: [{ url: "test-image.png" }, { url: "test-image-2.png" }],
|
||||
collection_id: baseCollection.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: [
|
||||
{
|
||||
title: "Test variant 1",
|
||||
@@ -1477,6 +1502,7 @@ medusaIntegrationTestRunner({
|
||||
is_giftcard: true,
|
||||
description: "test-giftcard-description",
|
||||
options: [{ title: "size", values: ["large"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Test variant",
|
||||
@@ -1637,6 +1663,7 @@ medusaIntegrationTestRunner({
|
||||
|
||||
it("updates product variants (update price on existing variant, create new variant)", async () => {
|
||||
const payload = {
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
id: baseProduct.variants[0].id,
|
||||
@@ -1815,6 +1842,7 @@ medusaIntegrationTestRunner({
|
||||
"/admin/products",
|
||||
getProductFixture({
|
||||
title: "Test metadata",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
metadata: {
|
||||
"test-key": "test-value",
|
||||
"test-key-2": "test-value-2",
|
||||
@@ -1877,6 +1905,7 @@ medusaIntegrationTestRunner({
|
||||
getProductFixture({
|
||||
title: "Test saleschannel",
|
||||
sales_channels: [{ id: salesChannel1.id }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
}),
|
||||
adminHeaders
|
||||
)
|
||||
@@ -2045,7 +2074,10 @@ medusaIntegrationTestRunner({
|
||||
const plainProduct = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
{ title: "Test variant order" },
|
||||
{
|
||||
title: "Test variant order",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.product
|
||||
@@ -2152,6 +2184,7 @@ medusaIntegrationTestRunner({
|
||||
title: "Test product - 1",
|
||||
handle: "test-1",
|
||||
options: [{ title: "size", values: ["x", "l"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Custom inventory 1",
|
||||
@@ -2265,6 +2298,7 @@ medusaIntegrationTestRunner({
|
||||
title: "Test product - 1",
|
||||
handle: "test-1",
|
||||
options: [{ title: "size", values: ["x", "l"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Custom inventory 1",
|
||||
@@ -2316,6 +2350,7 @@ medusaIntegrationTestRunner({
|
||||
title: "Test product - 1",
|
||||
handle: "test-1",
|
||||
options: [{ title: "size", values: ["x", "l"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Custom inventory 1",
|
||||
@@ -2490,6 +2525,7 @@ medusaIntegrationTestRunner({
|
||||
title: "Test product - 1",
|
||||
handle: "test-1",
|
||||
options: [{ title: "size", values: ["l"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Custom inventory 1",
|
||||
@@ -2520,6 +2556,7 @@ medusaIntegrationTestRunner({
|
||||
title: "Test product - 2",
|
||||
handle: "test-2",
|
||||
options: [{ title: "size", values: ["l"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "W/ shared inventory item",
|
||||
@@ -2604,6 +2641,7 @@ medusaIntegrationTestRunner({
|
||||
title: "Test product - 1",
|
||||
handle: "test-1",
|
||||
options: [{ title: "size", values: ["l"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Custom inventory 1",
|
||||
@@ -2740,6 +2778,7 @@ medusaIntegrationTestRunner({
|
||||
title: baseProduct.title,
|
||||
handle: baseProduct.handle,
|
||||
options: [{ title: "size", values: ["x", "l"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Test variant",
|
||||
@@ -2772,6 +2811,7 @@ medusaIntegrationTestRunner({
|
||||
handle: baseProduct.handle,
|
||||
description: "test-product-description",
|
||||
options: [{ title: "size", values: ["x", "l"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Test variant",
|
||||
@@ -2948,6 +2988,7 @@ medusaIntegrationTestRunner({
|
||||
const createPayload = getProductFixture({
|
||||
title: "Test batch create",
|
||||
handle: "test-batch-create",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
})
|
||||
|
||||
const updatePayload = {
|
||||
@@ -3006,6 +3047,7 @@ medusaIntegrationTestRunner({
|
||||
const productWithMultipleVariants = getProductFixture({
|
||||
title: "Test batch variants",
|
||||
handle: "test-batch-variants",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Variant 1",
|
||||
|
||||
@@ -11,7 +11,7 @@ medusaIntegrationTestRunner({
|
||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||
let baseProduct
|
||||
let baseRegion
|
||||
|
||||
let shippingProfile
|
||||
beforeEach(async () => {
|
||||
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
||||
// BREAKING: Creating a region no longer takes tax_rate, payment_providers, fulfillment_providers, countriesr
|
||||
@@ -26,11 +26,20 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
).data.region
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: "default", type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
baseProduct = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
getProductFixture({
|
||||
title: "Base product",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
}),
|
||||
adminHeaders
|
||||
)
|
||||
@@ -67,6 +76,7 @@ medusaIntegrationTestRunner({
|
||||
{ title: "First variant", prices: [] },
|
||||
{ title: "Second variant", prices: [] },
|
||||
],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
}),
|
||||
adminHeaders
|
||||
)
|
||||
@@ -801,6 +811,7 @@ medusaIntegrationTestRunner({
|
||||
"/admin/products",
|
||||
{
|
||||
title: "product 1",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [
|
||||
{ title: "size", values: ["large", "medium", "small"] },
|
||||
],
|
||||
|
||||
@@ -41,6 +41,7 @@ medusaIntegrationTestRunner({
|
||||
let publishableKey
|
||||
let storeHeadersWithCustomer
|
||||
let customer
|
||||
let shippingProfile
|
||||
|
||||
const createProducts = async (data) => {
|
||||
const response = await api.post(
|
||||
@@ -135,6 +136,14 @@ medusaIntegrationTestRunner({
|
||||
adminHeaders
|
||||
)
|
||||
).data.region
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: "default", type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
})
|
||||
|
||||
describe("Get products based on publishable key", () => {
|
||||
@@ -145,7 +154,11 @@ medusaIntegrationTestRunner({
|
||||
product1 = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
getProductFixture({ title: "test1", status: "published" }),
|
||||
getProductFixture({
|
||||
title: "test1",
|
||||
status: "published",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
}),
|
||||
adminHeaders
|
||||
)
|
||||
).data.product
|
||||
@@ -153,7 +166,11 @@ medusaIntegrationTestRunner({
|
||||
product2 = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
getProductFixture({ title: "test2", status: "published" }),
|
||||
getProductFixture({
|
||||
title: "test2",
|
||||
status: "published",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
}),
|
||||
adminHeaders
|
||||
)
|
||||
).data.product
|
||||
@@ -161,7 +178,11 @@ medusaIntegrationTestRunner({
|
||||
product3 = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
getProductFixture({ title: "test3", status: "published" }),
|
||||
getProductFixture({
|
||||
title: "test3",
|
||||
status: "published",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
}),
|
||||
adminHeaders
|
||||
)
|
||||
).data.product
|
||||
@@ -500,6 +521,7 @@ medusaIntegrationTestRunner({
|
||||
title: "test product 1",
|
||||
collection_id: collection.id,
|
||||
status: ProductStatus.PUBLISHED,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [
|
||||
{ title: "size", values: ["large", "small"] },
|
||||
{ title: "color", values: ["green"] },
|
||||
@@ -538,6 +560,7 @@ medusaIntegrationTestRunner({
|
||||
;[product2, [variant2]] = await createProducts({
|
||||
title: "test product 2 uniquely",
|
||||
status: ProductStatus.PUBLISHED,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [
|
||||
{ title: "size", values: ["large", "small"] },
|
||||
{ title: "material", values: ["cotton", "polyester"] },
|
||||
@@ -557,6 +580,7 @@ medusaIntegrationTestRunner({
|
||||
;[product3, [variant3]] = await createProducts({
|
||||
title: "product not in price list",
|
||||
status: ProductStatus.PUBLISHED,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [{ title: "size", values: ["large", "small"] }],
|
||||
variants: [
|
||||
{ title: "test variant 3", prices: [], options: { size: "large" } },
|
||||
@@ -565,6 +589,7 @@ medusaIntegrationTestRunner({
|
||||
;[product4, [variant4]] = await createProducts({
|
||||
title: "draft product",
|
||||
status: ProductStatus.DRAFT,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [{ title: "size", values: ["large", "small"] }],
|
||||
variants: [
|
||||
{ title: "test variant 4", prices: [], options: { size: "large" } },
|
||||
@@ -1727,6 +1752,7 @@ medusaIntegrationTestRunner({
|
||||
;[product, [variant]] = await createProducts({
|
||||
title: "test product 1",
|
||||
status: ProductStatus.PUBLISHED,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [{ title: "size", values: ["large"] }],
|
||||
variants: [
|
||||
{
|
||||
@@ -2206,6 +2232,7 @@ medusaIntegrationTestRunner({
|
||||
getProductFixture({
|
||||
title: "test1",
|
||||
status: "published",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Test taxes",
|
||||
@@ -2239,6 +2266,7 @@ medusaIntegrationTestRunner({
|
||||
getProductFixture({
|
||||
title: "test2",
|
||||
status: "published",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
}),
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
@@ -56,6 +56,7 @@ medusaIntegrationTestRunner({
|
||||
let appContainer
|
||||
let promotion
|
||||
let standardPromotion
|
||||
let shippingProfile
|
||||
|
||||
const promotionRule = {
|
||||
operator: "eq",
|
||||
@@ -90,6 +91,14 @@ medusaIntegrationTestRunner({
|
||||
adminHeaders
|
||||
)
|
||||
).data.promotion
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: "default", type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
})
|
||||
|
||||
describe("GET /admin/promotions/:id", () => {
|
||||
@@ -512,7 +521,10 @@ medusaIntegrationTestRunner({
|
||||
const product = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
medusaTshirtProduct,
|
||||
{
|
||||
...medusaTshirtProduct,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.product
|
||||
@@ -1494,6 +1506,7 @@ medusaIntegrationTestRunner({
|
||||
{
|
||||
title: "Test product 1",
|
||||
options: [{ title: "size", values: ["large", "small"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
@@ -1505,6 +1518,7 @@ medusaIntegrationTestRunner({
|
||||
{
|
||||
title: "Test product 2",
|
||||
options: [{ title: "size", values: ["large", "small"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
@@ -25,12 +25,24 @@ medusaIntegrationTestRunner({
|
||||
const container = getContainer()
|
||||
await createAdminUser(dbConnection, adminHeaders, container)
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{
|
||||
name: "Test",
|
||||
type: "default",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
const product = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
{
|
||||
title: "Test product",
|
||||
options: [{ title: "size", values: ["x", "l"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Test variant",
|
||||
@@ -149,17 +161,6 @@ medusaIntegrationTestRunner({
|
||||
],
|
||||
})
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{
|
||||
name: "Test",
|
||||
type: "default",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
location = (
|
||||
await api.post(
|
||||
`/admin/stock-locations`,
|
||||
|
||||
@@ -354,11 +354,20 @@ medusaIntegrationTestRunner({
|
||||
// to: /admin/sales-channels/:id/products
|
||||
let product
|
||||
beforeEach(async () => {
|
||||
const shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: "Test", type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
product = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
{
|
||||
title: "test name",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [{ title: "size", values: ["large"] }],
|
||||
},
|
||||
adminHeaders
|
||||
|
||||
+9
-8
@@ -58,6 +58,14 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
).data.region
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: "Test", type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
salesChannel = (
|
||||
await api.post(
|
||||
"/admin/sales-channels",
|
||||
@@ -71,6 +79,7 @@ medusaIntegrationTestRunner({
|
||||
"/admin/products",
|
||||
{
|
||||
title: "Test fixture",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [
|
||||
{ title: "size", values: ["large", "small"] },
|
||||
{ title: "color", values: ["green"] },
|
||||
@@ -114,14 +123,6 @@ medusaIntegrationTestRunner({
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: "Test", type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
const fulfillmentSets = (
|
||||
await api.post(
|
||||
`/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`,
|
||||
|
||||
@@ -57,6 +57,14 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
).data.region
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: "Test", type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
salesChannel = (
|
||||
await api.post(
|
||||
"/admin/sales-channels",
|
||||
@@ -74,6 +82,7 @@ medusaIntegrationTestRunner({
|
||||
{ title: "size", values: ["large", "small"] },
|
||||
{ title: "color", values: ["green"] },
|
||||
],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
title: "Test variant",
|
||||
@@ -113,14 +122,6 @@ medusaIntegrationTestRunner({
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: "Test", type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
const fulfillmentSets = (
|
||||
await api.post(
|
||||
`/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`,
|
||||
|
||||
@@ -658,6 +658,7 @@ medusaIntegrationTestRunner({
|
||||
|
||||
describe("POST /store/carts/:id/line-items", () => {
|
||||
let region
|
||||
|
||||
const productData = {
|
||||
title: "Medusa 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 () => {
|
||||
const shippingProfile =
|
||||
await fulfillmentModule.createShippingProfiles({
|
||||
name: "Test",
|
||||
type: "default",
|
||||
})
|
||||
|
||||
const product = (
|
||||
await api.post(`/admin/products`, productData, adminHeaders)
|
||||
await api.post(
|
||||
`/admin/products`,
|
||||
{
|
||||
...productData,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.product
|
||||
|
||||
const cart = (
|
||||
@@ -1203,6 +1217,7 @@ medusaIntegrationTestRunner({
|
||||
"/admin/products",
|
||||
{
|
||||
title: "Test fixture",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [
|
||||
{ title: "size", values: ["large", "small"] },
|
||||
{ title: "color", values: ["green"] },
|
||||
|
||||
@@ -30,11 +30,20 @@ medusaIntegrationTestRunner({
|
||||
beforeEach(async () => {
|
||||
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
||||
|
||||
const shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: "Test", type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
product = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
{
|
||||
title: "product 1",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [{ title: "size", values: ["x", "l"] }],
|
||||
variants: [
|
||||
{
|
||||
|
||||
@@ -31,9 +31,18 @@ medusaIntegrationTestRunner({
|
||||
|
||||
describe("Index engine", () => {
|
||||
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 = {
|
||||
title: "Test Giftcard",
|
||||
is_giftcard: true,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
description: "test-giftcard-description",
|
||||
options: [{ title: "Denominations", values: ["100"] }],
|
||||
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 () => {
|
||||
const shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: "Test", type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
const payload = {
|
||||
title: "Test Giftcard",
|
||||
is_giftcard: true,
|
||||
description: "test-giftcard-description",
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [{ title: "Denominations", values: ["100"] }],
|
||||
variants: new Array(10).fill(0).map((_, 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 () => {
|
||||
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) => ({
|
||||
title: "Test Giftcard-" + a,
|
||||
is_giftcard: true,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
description: "test-giftcard-description" + a,
|
||||
options: [{ title: "Denominations", values: ["100"] }],
|
||||
variants: new Array(10).fill(0).map((_, i) => ({
|
||||
|
||||
@@ -209,9 +209,18 @@ medusaIntegrationTestRunner({
|
||||
beforeEach(async () => {
|
||||
await createAdminUser(dbConnection, adminHeaders, appContainer)
|
||||
|
||||
const shippingProfile = (
|
||||
await api.post(
|
||||
`/admin/shipping-profiles`,
|
||||
{ name: "Test", type: "default" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.shipping_profile
|
||||
|
||||
const payload = {
|
||||
title: "Test Giftcard",
|
||||
is_giftcard: true,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
description: "test-giftcard-description",
|
||||
options: [{ title: "Denominations", values: ["100"] }],
|
||||
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([
|
||||
{
|
||||
[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([
|
||||
{
|
||||
[Modules.STOCK_LOCATION]: {
|
||||
|
||||
@@ -4,7 +4,10 @@ import {
|
||||
batchProductVariantsWorkflow,
|
||||
batchProductVariantsWorkflowId,
|
||||
} from "@medusajs/core-flows"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import {
|
||||
IFulfillmentModuleService,
|
||||
IProductModuleService,
|
||||
} from "@medusajs/types"
|
||||
import { Modules } from "@medusajs/utils"
|
||||
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
||||
|
||||
@@ -17,9 +20,20 @@ medusaIntegrationTestRunner({
|
||||
let appContainer
|
||||
let service: IProductModuleService
|
||||
|
||||
let fulfullmentService: IFulfillmentModuleService
|
||||
let shippingProfile
|
||||
|
||||
beforeAll(async () => {
|
||||
appContainer = getContainer()
|
||||
service = appContainer.resolve(Modules.PRODUCT)
|
||||
fulfullmentService = appContainer.resolve(Modules.FULFILLMENT)
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
shippingProfile = await fulfullmentService.createShippingProfiles({
|
||||
name: "Test",
|
||||
type: "default",
|
||||
})
|
||||
})
|
||||
|
||||
describe("batchProductWorkflow", () => {
|
||||
@@ -43,7 +57,8 @@ medusaIntegrationTestRunner({
|
||||
create: [
|
||||
{
|
||||
title: "test3",
|
||||
options: [{ title: "size", options: ["x"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
options: [{ title: "size", values: ["x"] }],
|
||||
},
|
||||
],
|
||||
update: [{ id: product1.id, title: "test1-updated" }],
|
||||
@@ -94,6 +109,7 @@ medusaIntegrationTestRunner({
|
||||
{
|
||||
title: "test1",
|
||||
options: [{ title: "size", values: ["x", "l", "m"] }],
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
variants: [
|
||||
{
|
||||
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",
|
||||
"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": {
|
||||
"label": "Type"
|
||||
},
|
||||
@@ -681,6 +685,20 @@
|
||||
"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": {
|
||||
"delete": {
|
||||
"success": {
|
||||
@@ -1249,6 +1267,8 @@
|
||||
"fulfillment": {
|
||||
"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.",
|
||||
"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",
|
||||
"statusLabel": "Fulfillment status",
|
||||
"statusTitle": "Fulfillment Status",
|
||||
@@ -1269,7 +1289,9 @@
|
||||
"error": {
|
||||
"wrongQuantity": "Only one item is available for fulfillment",
|
||||
"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": {
|
||||
"notFulfilled": "Not fulfilled",
|
||||
@@ -2749,7 +2771,9 @@
|
||||
"added": "Added",
|
||||
"removed": "Removed",
|
||||
"from": "From",
|
||||
"to": "To"
|
||||
"to": "To",
|
||||
"beaware": "Be aware",
|
||||
"loading": "Loading"
|
||||
},
|
||||
"fields": {
|
||||
"amount": "Amount",
|
||||
|
||||
@@ -102,6 +102,13 @@ export const RouteMap: RouteObject[] = [
|
||||
lazy: () =>
|
||||
import("../../routes/products/product-organization"),
|
||||
},
|
||||
{
|
||||
path: "shipping-profile",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../routes/products/product-shipping-profile"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "media",
|
||||
lazy: () =>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { IconAvatar } from "../../../components/common/icon-avatar"
|
||||
import { TwoColumnPage } from "../../../components/layout/pages"
|
||||
import { useDashboardExtension } from "../../../extensions"
|
||||
import { LocationListHeader } from "./components/location-list-header"
|
||||
import { SidebarLink } from "../../../components/common/sidebar-link/sidebar-link"
|
||||
|
||||
export function LocationList() {
|
||||
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 { t } = useTranslation()
|
||||
|
||||
|
||||
-1
@@ -2,7 +2,6 @@ import { z } from "zod"
|
||||
|
||||
export const CreateFulfillmentSchema = z.object({
|
||||
quantity: z.record(z.string(), z.number()),
|
||||
|
||||
location_id: z.string(),
|
||||
shipping_option_id: z.string().optional(),
|
||||
send_notification: z.boolean().optional(),
|
||||
|
||||
+184
-72
@@ -3,7 +3,7 @@ import { useEffect, useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
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 { useForm, useWatch } from "react-hook-form"
|
||||
|
||||
@@ -19,7 +19,10 @@ import { useStockLocations } from "../../../../../hooks/api/stock-locations"
|
||||
import { getFulfillableQuantity } from "../../../../../lib/order-item"
|
||||
import { CreateFulfillmentSchema } from "./constants"
|
||||
import { OrderCreateFulfillmentItem } from "./order-create-fulfillment-item"
|
||||
import { useReservationItems } from "../../../../../hooks/api"
|
||||
import {
|
||||
useReservationItems,
|
||||
useShippingOptions,
|
||||
} from "../../../../../hooks/api"
|
||||
|
||||
type OrderCreateFulfillmentFormProps = {
|
||||
order: AdminOrder
|
||||
@@ -56,30 +59,86 @@ export function OrderCreateFulfillmentForm({
|
||||
|
||||
const form = useForm<zod.infer<typeof CreateFulfillmentSchema>>({
|
||||
defaultValues: {
|
||||
quantity: fulfillableItems.reduce((acc, item) => {
|
||||
acc[item.id] = getFulfillableQuantity(item)
|
||||
return acc
|
||||
}, {} as Record<string, number>),
|
||||
quantity: fulfillableItems.reduce(
|
||||
(acc, item) => {
|
||||
acc[item.id] = getFulfillableQuantity(item)
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, number>
|
||||
),
|
||||
send_notification: !order.no_notification,
|
||||
},
|
||||
resolver: zodResolver(CreateFulfillmentSchema),
|
||||
})
|
||||
|
||||
const selectedLocationId = useWatch({
|
||||
name: "location_id",
|
||||
control: form.control,
|
||||
})
|
||||
|
||||
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) => {
|
||||
try {
|
||||
await createOrderFulfillment({
|
||||
location_id: data.location_id,
|
||||
// shipping_option_id: data.shipping_option_id,
|
||||
no_notification: !data.send_notification,
|
||||
items: Object.entries(data.quantity)
|
||||
.filter(([, value]) => !!value)
|
||||
.map(([id, quantity]) => ({
|
||||
id,
|
||||
quantity,
|
||||
})),
|
||||
const selectedShippingOption = shipping_options.find(
|
||||
(o) => o.id === shippingOptionId
|
||||
)
|
||||
|
||||
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,
|
||||
items: Object.entries(data.quantity)
|
||||
.filter(
|
||||
([id, value]) =>
|
||||
!!value && itemShippingProfileMap[id] === selectedShippingProfileId
|
||||
)
|
||||
.map(([id, quantity]) => ({
|
||||
id,
|
||||
quantity,
|
||||
})),
|
||||
}
|
||||
|
||||
try {
|
||||
await createOrderFulfillment(payload)
|
||||
|
||||
toast.success(t("orders.fulfillment.toast.created"))
|
||||
handleSuccess(`/orders/${order.id}`)
|
||||
@@ -89,15 +148,28 @@ export function OrderCreateFulfillmentForm({
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (stock_locations?.length) {
|
||||
form.setValue("location_id", stock_locations[0].id)
|
||||
}
|
||||
}, [stock_locations?.length])
|
||||
if (stock_locations?.length && shipping_options?.length) {
|
||||
const initialShippingOptionId =
|
||||
order.shipping_methods?.[0]?.shipping_option_id
|
||||
|
||||
const selectedLocationId = useWatch({
|
||||
name: "location_id",
|
||||
control: form.control,
|
||||
})
|
||||
if (initialShippingOptionId) {
|
||||
const shippingOption = shipping_options.find(
|
||||
(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(
|
||||
(item) =>
|
||||
@@ -124,14 +196,21 @@ export function OrderCreateFulfillmentForm({
|
||||
})
|
||||
}
|
||||
|
||||
const quantityMap = itemsToFulfill.reduce((acc, item) => {
|
||||
acc[item.id] = getFulfillableQuantity(item as OrderLineItemDTO)
|
||||
return acc
|
||||
}, {} as Record<string, number>)
|
||||
const quantityMap = itemsToFulfill.reduce(
|
||||
(acc, item) => {
|
||||
acc[item.id] = getFulfillableQuantity(item as OrderLineItemDTO)
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, number>
|
||||
)
|
||||
|
||||
form.setValue("quantity", quantityMap)
|
||||
}, [...fulfilledQuantityArray, requiresShipping])
|
||||
|
||||
const differentOptionSelected =
|
||||
shippingOptionId &&
|
||||
order.shipping_methods?.[0]?.shipping_option_id !== shippingOptionId
|
||||
|
||||
return (
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<KeyboundForm
|
||||
@@ -185,48 +264,69 @@ export function OrderCreateFulfillmentForm({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* <div className="py-8">*/}
|
||||
{/* <Form.Field*/}
|
||||
{/* control={form.control}*/}
|
||||
{/* name="shipping_option_id"*/}
|
||||
{/* render={({ field: { onChange, ref, ...field } }) => {*/}
|
||||
{/* return (*/}
|
||||
{/* <Form.Item>*/}
|
||||
{/* <div className="flex flex-col gap-2 xl:flex-row xl:items-center">*/}
|
||||
{/* <div className="flex-1">*/}
|
||||
{/* <Form.Label>*/}
|
||||
{/* {t("fields.shippingMethod")}*/}
|
||||
{/* </Form.Label>*/}
|
||||
{/* <Form.Hint>*/}
|
||||
{/* {t("orders.fulfillment.methodDescription")}*/}
|
||||
{/* </Form.Hint>*/}
|
||||
{/* </div>*/}
|
||||
{/* <div className="flex-1">*/}
|
||||
{/* <Form.Control>*/}
|
||||
{/* <Select onValueChange={onChange} {...field}>*/}
|
||||
{/* <Select.Trigger*/}
|
||||
{/* className="bg-ui-bg-base"*/}
|
||||
{/* ref={ref}*/}
|
||||
{/* >*/}
|
||||
{/* <Select.Value />*/}
|
||||
{/* </Select.Trigger>*/}
|
||||
{/* <Select.Content>*/}
|
||||
{/* {shipping_options.map((o) => (*/}
|
||||
{/* <Select.Item key={o.id} value={o.id}>*/}
|
||||
{/* {o.name}*/}
|
||||
{/* </Select.Item>*/}
|
||||
{/* ))}*/}
|
||||
{/* </Select.Content>*/}
|
||||
{/* </Select>*/}
|
||||
{/* </Form.Control>*/}
|
||||
{/* </div>*/}
|
||||
{/* </div>*/}
|
||||
{/* <Form.ErrorMessage />*/}
|
||||
{/* </Form.Item>*/}
|
||||
{/* )*/}
|
||||
{/* }}*/}
|
||||
{/* />*/}
|
||||
{/* </div>*/}
|
||||
<div className="py-8">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="shipping_option_id"
|
||||
render={({ field: { onChange, ref, ...field } }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<div className="flex flex-col gap-2 xl:flex-row xl:items-center">
|
||||
<div className="flex-1">
|
||||
<Form.Label>
|
||||
{t("fields.shippingMethod")}
|
||||
</Form.Label>
|
||||
<Form.Hint>
|
||||
{t("orders.fulfillment.methodDescription")}
|
||||
</Form.Hint>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Form.Control>
|
||||
<Select
|
||||
onValueChange={onChange}
|
||||
{...field}
|
||||
disabled={!selectedLocationId}
|
||||
>
|
||||
<Select.Trigger
|
||||
className="bg-ui-bg-base"
|
||||
ref={ref}
|
||||
>
|
||||
{isShippingOptionsLoading ? (
|
||||
<span className="text-right">
|
||||
{t("labels.loading")}...
|
||||
</span>
|
||||
) : (
|
||||
<Select.Value />
|
||||
)}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{shipping_options.map((o) => (
|
||||
<Select.Item key={o.id} value={o.id}>
|
||||
{o.name}
|
||||
</Select.Item>
|
||||
))}
|
||||
</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>
|
||||
<Form.Item className="mt-8">
|
||||
<Form.Label>
|
||||
@@ -238,12 +338,19 @@ export function OrderCreateFulfillmentForm({
|
||||
|
||||
<div className="flex flex-col gap-y-1">
|
||||
{fulfillableItems.map((item) => {
|
||||
const isShippingProfileMatching =
|
||||
shipping_options.find(
|
||||
(o) => o.id === shippingOptionId
|
||||
)?.shipping_profile_id ===
|
||||
item.variant?.product?.shipping_profile?.id
|
||||
|
||||
return (
|
||||
<OrderCreateFulfillmentItem
|
||||
key={item.id}
|
||||
form={form}
|
||||
item={item}
|
||||
locationId={selectedLocationId}
|
||||
disabled={isShippingProfileMatching}
|
||||
itemReservedQuantitiesMap={
|
||||
itemReservedQuantitiesMap
|
||||
}
|
||||
@@ -305,7 +412,12 @@ export function OrderCreateFulfillmentForm({
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</RouteFocusModal.Close>
|
||||
<Button size="small" type="submit" isLoading={isMutating}>
|
||||
<Button
|
||||
size="small"
|
||||
type="submit"
|
||||
isLoading={isMutating}
|
||||
disabled={!shippingOptionId}
|
||||
>
|
||||
{t("orders.fulfillment.create")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
+101
-80
@@ -1,7 +1,7 @@
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
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 { HttpTypes } from "@medusajs/types"
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Thumbnail } from "../../../../../components/common/thumbnail/index"
|
||||
import { useProductVariant } from "../../../../../hooks/api/products"
|
||||
import { getFulfillableQuantity } from "../../../../../lib/order-item"
|
||||
import { CreateFulfillmentSchema } from "./constants"
|
||||
import { InformationCircleSolid } from "@medusajs/icons"
|
||||
|
||||
type OrderEditItemProps = {
|
||||
item: HttpTypes.AdminOrderLineItem
|
||||
@@ -18,6 +19,7 @@ type OrderEditItemProps = {
|
||||
onItemRemove: (itemId: string) => void
|
||||
itemReservedQuantitiesMap: Map<string, number>
|
||||
form: UseFormReturn<zod.infer<typeof CreateFulfillmentSchema>>
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
export function OrderCreateFulfillmentItem({
|
||||
@@ -25,6 +27,7 @@ export function OrderCreateFulfillmentItem({
|
||||
form,
|
||||
locationId,
|
||||
itemReservedQuantitiesMap,
|
||||
disabled,
|
||||
}: OrderEditItemProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -70,102 +73,120 @@ export function OrderCreateFulfillmentItem({
|
||||
)
|
||||
|
||||
return (
|
||||
<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-1 items-center gap-x-3">
|
||||
<Thumbnail src={item.thumbnail} />
|
||||
<div className="flex flex-col">
|
||||
<div>
|
||||
<Text className="txt-small" as="span" weight="plus">
|
||||
{item.title}
|
||||
</Text>
|
||||
{item.variant_sku && <span>({item.variant_sku})</span>}
|
||||
</div>
|
||||
<Text as="div" className="text-ui-fg-subtle txt-small">
|
||||
{item.variant_title}
|
||||
</Text>
|
||||
<div className="bg-ui-bg-subtle shadow-elevation-card-rest my-2 rounded-xl">
|
||||
<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>
|
||||
)}
|
||||
|
||||
<div className="flex flex-1 items-center gap-x-1">
|
||||
<div className="mr-2 block h-[16px] w-[2px] bg-gray-200" />
|
||||
|
||||
<div className="text-small flex flex-1 flex-col">
|
||||
<span className="text-ui-fg-subtle font-medium">
|
||||
{t("orders.fulfillment.available")}
|
||||
</span>
|
||||
<span className="text-ui-fg-subtle">
|
||||
{availableQuantity || "N/A"}
|
||||
</span>
|
||||
<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">
|
||||
<Thumbnail src={item.thumbnail} />
|
||||
<div className="flex flex-col">
|
||||
<div>
|
||||
<Text className="txt-small" as="span" weight="plus">
|
||||
{item.title}
|
||||
</Text>
|
||||
{item.variant_sku && <span>({item.variant_sku})</span>}
|
||||
</div>
|
||||
<Text as="div" className="text-ui-fg-subtle txt-small">
|
||||
{item.variant_title}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 items-center gap-x-1">
|
||||
<div className="mr-2 block h-[16px] w-[2px] bg-gray-200" />
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="text-small flex flex-1 flex-col">
|
||||
<span className="text-ui-fg-subtle font-medium">
|
||||
{t("orders.fulfillment.inStock")}
|
||||
{t("orders.fulfillment.available")}
|
||||
</span>
|
||||
<span className="text-ui-fg-subtle">
|
||||
{inStockQuantity || "N/A"}{" "}
|
||||
{inStockQuantity && (
|
||||
<span className="font-medium text-red-500">
|
||||
-{form.getValues(`quantity.${item.id}`)}
|
||||
</span>
|
||||
)}
|
||||
{availableQuantity || "N/A"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 items-center gap-1">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name={`quantity.${item.id}`}
|
||||
rules={{ required: true, min: minValue, max: maxValue }}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Control>
|
||||
<Input
|
||||
className="bg-ui-bg-base txt-small w-[50px] rounded-lg text-right [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
|
||||
type="number"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
const val =
|
||||
e.target.value === ""
|
||||
? null
|
||||
: Number(e.target.value)
|
||||
<div className="flex flex-1 items-center gap-x-1">
|
||||
<div className="mr-2 block h-[16px] w-[2px] bg-gray-200" />
|
||||
|
||||
field.onChange(val)
|
||||
<div className="flex flex-col">
|
||||
<span className="text-ui-fg-subtle font-medium">
|
||||
{t("orders.fulfillment.inStock")}
|
||||
</span>
|
||||
<span className="text-ui-fg-subtle">
|
||||
{inStockQuantity || "N/A"}{" "}
|
||||
{inStockQuantity && (
|
||||
<span className="font-medium text-red-500">
|
||||
-{form.getValues(`quantity.${item.id}`)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
if (!isNaN(val)) {
|
||||
if (val < minValue || val > maxValue) {
|
||||
form.setError(`quantity.${item.id}`, {
|
||||
type: "manual",
|
||||
message: t(
|
||||
"orders.fulfillment.error.wrongQuantity",
|
||||
{
|
||||
count: maxValue,
|
||||
number: maxValue,
|
||||
}
|
||||
),
|
||||
})
|
||||
} else {
|
||||
form.clearErrors(`quantity.${item.id}`)
|
||||
<div className="flex flex-1 items-center gap-1">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name={`quantity.${item.id}`}
|
||||
rules={{ required: true, min: minValue, max: maxValue }}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Control>
|
||||
<Input
|
||||
className="bg-ui-bg-base txt-small w-[50px] rounded-lg text-right [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
|
||||
type="number"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
const val =
|
||||
e.target.value === ""
|
||||
? null
|
||||
: Number(e.target.value)
|
||||
|
||||
field.onChange(val)
|
||||
|
||||
if (!isNaN(val)) {
|
||||
if (val < minValue || val > maxValue) {
|
||||
form.setError(`quantity.${item.id}`, {
|
||||
type: "manual",
|
||||
message: t(
|
||||
"orders.fulfillment.error.wrongQuantity",
|
||||
{
|
||||
count: maxValue,
|
||||
number: maxValue,
|
||||
}
|
||||
),
|
||||
})
|
||||
} else {
|
||||
form.clearErrors(`quantity.${item.id}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Form.Control>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
}}
|
||||
/>
|
||||
</Form.Control>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
||||
<span className="text-ui-fg-subtle">
|
||||
/ {item.quantity} {t("fields.qty")}
|
||||
</span>
|
||||
<span className="text-ui-fg-subtle">
|
||||
/ {item.quantity} {t("fields.qty")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+2
-1
@@ -10,7 +10,8 @@ export function OrderCreateFulfillment() {
|
||||
const requiresShipping = searchParams.get("requires_shipping") === "true"
|
||||
|
||||
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) {
|
||||
|
||||
+18
-5
@@ -79,10 +79,13 @@ export const ProductCreateForm = ({
|
||||
return {}
|
||||
}
|
||||
|
||||
return regions.reduce((acc, reg) => {
|
||||
acc[reg.id] = reg.currency_code
|
||||
return acc
|
||||
}, {} as Record<string, string>)
|
||||
return regions.reduce(
|
||||
(acc, reg) => {
|
||||
acc[reg.id] = reg.currency_code
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, string>
|
||||
)
|
||||
}, [regions])
|
||||
|
||||
/**
|
||||
@@ -178,6 +181,15 @@ export const ProductCreateForm = ({
|
||||
}
|
||||
|
||||
if (currentTab === Tab.ORGANIZE) {
|
||||
// TODO: this is temp until we add partial validation per tab
|
||||
if (!form.getValues("shipping_profile_id")) {
|
||||
form.setError("shipping_profile_id", {
|
||||
type: "required",
|
||||
message: t("products.shippingProfile.create.errors.required"),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
setTab(Tab.VARIANTS)
|
||||
}
|
||||
|
||||
@@ -208,7 +220,8 @@ export const ProductCreateForm = ({
|
||||
}
|
||||
|
||||
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 (
|
||||
<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({
|
||||
control: form.control,
|
||||
name: "sales_channels",
|
||||
@@ -161,6 +171,34 @@ export const ProductCreateOrganizationSection = ({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<Form.Label>{t("products.fields.shipping_profile.label")}</Form.Label>
|
||||
<Form.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">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
|
||||
@@ -64,6 +64,7 @@ export const ProductCreateSchema = z
|
||||
discountable: z.boolean(),
|
||||
type_id: z.string().optional(),
|
||||
collection_id: z.string().optional(),
|
||||
shipping_profile_id: z.string(), // TODO: require min(1) when partial validation per tab is added
|
||||
categories: z.array(z.string()),
|
||||
tags: z.array(z.string()).optional(),
|
||||
sales_channels: z
|
||||
@@ -145,6 +146,7 @@ export const PRODUCT_CREATE_FORM_DEFAULTS: Partial<
|
||||
media: [],
|
||||
categories: [],
|
||||
collection_id: "",
|
||||
shipping_profile_id: "",
|
||||
description: "",
|
||||
handle: "",
|
||||
height: "",
|
||||
|
||||
@@ -24,6 +24,7 @@ export const normalizeProductFormValues = (
|
||||
: undefined,
|
||||
images,
|
||||
collection_id: values.collection_id || undefined,
|
||||
shipping_profile_id: values.shipping_profile_id,
|
||||
categories: values.categories.map((id) => ({ id })),
|
||||
type_id: values.type_id || 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(
|
||||
"product",
|
||||
"*categories,-variants"
|
||||
"*categories,*shipping_profile,-variants"
|
||||
)
|
||||
|
||||
@@ -14,6 +14,7 @@ import { PRODUCT_DETAIL_FIELDS } from "./constants"
|
||||
import { productLoader } from "./loader"
|
||||
|
||||
import { useDashboardExtension } from "../../../extensions"
|
||||
import { ProductShippingProfileSection } from "./components/product-shipping-profile-section"
|
||||
|
||||
export const ProductDetail = () => {
|
||||
const initialData = useLoaderData() as Awaited<
|
||||
@@ -71,6 +72,7 @@ export const ProductDetail = () => {
|
||||
</TwoColumnPage.Main>
|
||||
<TwoColumnPage.Sidebar>
|
||||
<ProductSalesChannelSection product={product} />
|
||||
<ProductShippingProfileSection product={product} />
|
||||
<ProductOrganizationSection product={product} />
|
||||
<ProductAttributeSection product={product} />
|
||||
</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>
|
||||
)
|
||||
}
|
||||
@@ -58,14 +58,14 @@ export type CreateFulfillmentValidateOrderStepInput = {
|
||||
* This step validates that a fulfillment can be created for an order. If the order
|
||||
* is canceled, the items don't exist in the order, or the items aren't grouped by
|
||||
* shipping requirement, the step throws an error.
|
||||
*
|
||||
*
|
||||
* :::note
|
||||
*
|
||||
*
|
||||
* You can retrieve an order's details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
|
||||
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
|
||||
*
|
||||
*
|
||||
* :::
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const data = createFulfillmentValidateOrder({
|
||||
* order: {
|
||||
@@ -82,10 +82,7 @@ export type CreateFulfillmentValidateOrderStepInput = {
|
||||
*/
|
||||
export const createFulfillmentValidateOrder = createStep(
|
||||
"create-fulfillment-validate-order",
|
||||
({
|
||||
order,
|
||||
inputItems,
|
||||
}: CreateFulfillmentValidateOrderStepInput) => {
|
||||
({ order, inputItems }: CreateFulfillmentValidateOrderStepInput) => {
|
||||
throwIfOrderIsCancelled({ order })
|
||||
throwIfItemsDoesNotExistsInOrder({ order, inputItems })
|
||||
throwIfItemsAreNotGroupedByShippingRequirement({ order, inputItems })
|
||||
@@ -128,6 +125,7 @@ function prepareFulfillmentData({
|
||||
id: string
|
||||
provider_id: string
|
||||
service_zone: { fulfillment_set: { location?: { id: string } } }
|
||||
shipping_profile_id: string
|
||||
}
|
||||
shippingMethod: { data?: Record<string, unknown> | null }
|
||||
reservations: ReservationItemDTO[]
|
||||
@@ -156,6 +154,18 @@ function prepareFulfillmentData({
|
||||
const orderItem = orderItemsMap.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 {
|
||||
line_item_id: i.id,
|
||||
inventory_item_id: reservation?.inventory_item_id,
|
||||
@@ -273,17 +283,18 @@ function prepareInventoryUpdate({
|
||||
/**
|
||||
* 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"
|
||||
/**
|
||||
* This workflow creates a fulfillment for an order. It's used by the [Create Order Fulfillment Admin API Route](https://docs.medusajs.com/api/admin#orders_postordersidfulfillments).
|
||||
*
|
||||
* This workflow has a hook that allows you to perform custom actions on the created fulfillment. For example, you can pass under `additional_data` custom data that
|
||||
*
|
||||
* This workflow has a hook that allows you to perform custom actions on the created fulfillment. For example, you can pass under `additional_data` custom data that
|
||||
* allows you to create custom data models linked to the fulfillment.
|
||||
*
|
||||
*
|
||||
* You can also use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around creating a fulfillment.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const { result } = await createOrderFulfillmentWorkflow(container)
|
||||
* .run({
|
||||
@@ -300,18 +311,16 @@ export const createOrderFulfillmentWorkflowId = "create-order-fulfillment"
|
||||
* }
|
||||
* }
|
||||
* })
|
||||
*
|
||||
*
|
||||
* @summary
|
||||
*
|
||||
*
|
||||
* Creates a fulfillment for an order.
|
||||
*
|
||||
*
|
||||
* @property hooks.fulfillmentCreated - This hook is executed after the fulfillment is created. You can consume this hook to perform custom actions on the created fulfillment.
|
||||
*/
|
||||
export const createOrderFulfillmentWorkflow = createWorkflow(
|
||||
createOrderFulfillmentWorkflowId,
|
||||
(
|
||||
input: WorkflowData<CreateOrderFulfillmentWorkflowInput>
|
||||
) => {
|
||||
(input: WorkflowData<CreateOrderFulfillmentWorkflowInput>) => {
|
||||
const order: OrderDTO = useRemoteQueryStep({
|
||||
entry_point: "orders",
|
||||
fields: [
|
||||
@@ -323,12 +332,14 @@ export const createOrderFulfillmentWorkflow = createWorkflow(
|
||||
"items.variant.manage_inventory",
|
||||
"items.variant.allow_backorder",
|
||||
"items.variant.product.id",
|
||||
"items.variant.product.shipping_profile.id",
|
||||
"items.variant.weight",
|
||||
"items.variant.length",
|
||||
"items.variant.height",
|
||||
"items.variant.width",
|
||||
"items.variant.material",
|
||||
"shipping_address.*",
|
||||
"shipping_methods.id",
|
||||
"shipping_methods.shipping_option_id",
|
||||
"shipping_methods.data",
|
||||
],
|
||||
@@ -346,17 +357,29 @@ export const createOrderFulfillmentWorkflow = createWorkflow(
|
||||
}, {})
|
||||
})
|
||||
|
||||
const shippingMethod = transform(order, (data) => {
|
||||
return { data: data.shipping_methods?.[0]?.data }
|
||||
const shippingOptionId = transform({ order, input }, (data) => {
|
||||
return (
|
||||
data.input.shipping_option_id ??
|
||||
data.order.shipping_methods?.[0]?.shipping_option_id
|
||||
)
|
||||
})
|
||||
|
||||
const shippingOptionId = transform(order, (data) => {
|
||||
return data.shipping_methods?.[0]?.shipping_option_id
|
||||
const shippingMethod = transform({ order, shippingOptionId }, (data) => {
|
||||
return {
|
||||
data: data.order.shipping_methods?.find(
|
||||
(sm) => sm.shipping_option_id === data.shippingOptionId
|
||||
)?.data,
|
||||
}
|
||||
})
|
||||
|
||||
const shippingOption = useRemoteQueryStep({
|
||||
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: {
|
||||
id: shippingOptionId,
|
||||
},
|
||||
|
||||
@@ -136,6 +136,11 @@ const normalizeProductForImport = (
|
||||
return
|
||||
}
|
||||
|
||||
if (normalizedKey.startsWith("shipping_profile_id")) {
|
||||
response["shipping_profile_id"] = normalizedValue
|
||||
return
|
||||
}
|
||||
|
||||
if (normalizedKey.startsWith("product_category_")) {
|
||||
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"
|
||||
|
||||
const basicFieldsToOmit = [
|
||||
@@ -32,6 +36,7 @@ export const normalizeV1Products = (
|
||||
productTypes: ProductTypes.ProductTypeDTO[]
|
||||
productCollections: ProductTypes.ProductCollectionDTO[]
|
||||
salesChannels: SalesChannelTypes.SalesChannelDTO[]
|
||||
shippingProfiles: ShippingProfileDTO[]
|
||||
}
|
||||
): object[] => {
|
||||
const productTypesMap = new Map(
|
||||
@@ -43,6 +48,9 @@ export const normalizeV1Products = (
|
||||
const salesChannelsMap = new Map(
|
||||
supportingData.salesChannels.map((sc) => [sc.name, sc.id])
|
||||
)
|
||||
const shippingProfilesIds = new Set(
|
||||
supportingData.shippingProfiles.map((sp) => sp.id)
|
||||
)
|
||||
|
||||
return rawProducts.map((product) => {
|
||||
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 (
|
||||
key.startsWith("Product Category") &&
|
||||
(key.endsWith("Handle") ||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
IFulfillmentModuleService,
|
||||
IProductModuleService,
|
||||
IRegionModuleService,
|
||||
ISalesChannelModuleService,
|
||||
@@ -16,9 +17,9 @@ export type ParseProductCsvStepInput = string
|
||||
|
||||
export const parseProductCsvStepId = "parse-product-csv"
|
||||
/**
|
||||
* This step parses a CSV file holding products to import, returning the products as
|
||||
* This step parses a CSV file holding products to import, returning the products as
|
||||
* objects that can be imported.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const data = parseProductCsvStep("products.csv")
|
||||
*/
|
||||
@@ -35,20 +36,25 @@ export const parseProductCsvStep = createStep(
|
||||
Modules.SALES_CHANNEL
|
||||
)
|
||||
|
||||
const fulfillmentService = container.resolve<IFulfillmentModuleService>(
|
||||
Modules.FULFILLMENT
|
||||
)
|
||||
|
||||
const csvProducts = convertCsvToJson(fileContent)
|
||||
|
||||
const [productTypes, productCollections, salesChannels] = await Promise.all(
|
||||
[
|
||||
const [productTypes, productCollections, salesChannels, shippingProfiles] =
|
||||
await Promise.all([
|
||||
productService.listProductTypes({}, {}),
|
||||
productService.listProductCollections({}, {}),
|
||||
salesChannelService.listSalesChannels({}, {}),
|
||||
]
|
||||
)
|
||||
fulfillmentService.listShippingProfiles({}, {}),
|
||||
])
|
||||
|
||||
const v1Normalized = normalizeV1Products(csvProducts, {
|
||||
productTypes,
|
||||
productCollections,
|
||||
salesChannels,
|
||||
shippingProfiles,
|
||||
})
|
||||
|
||||
// We use the handle to group products and variants correctly.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
AdditionalData,
|
||||
CreateProductWorkflowInputDTO,
|
||||
LinkDefinition,
|
||||
PricingTypes,
|
||||
ProductTypes,
|
||||
} from "@medusajs/framework/types"
|
||||
@@ -8,6 +9,7 @@ import {
|
||||
ProductWorkflowEvents,
|
||||
isPresent,
|
||||
MedusaError,
|
||||
Modules,
|
||||
} from "@medusajs/framework/utils"
|
||||
import {
|
||||
WorkflowData,
|
||||
@@ -17,7 +19,11 @@ import {
|
||||
transform,
|
||||
createStep,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { emitEventStep } from "../../common"
|
||||
import {
|
||||
createRemoteLinkStep,
|
||||
emitEventStep,
|
||||
useQueryGraphStep,
|
||||
} from "../../common"
|
||||
import { associateProductsWithSalesChannelsStep } from "../../sales-channel"
|
||||
import { createProductsStep } from "../steps/create-products"
|
||||
import { createProductVariantsWorkflow } from "./create-product-variants"
|
||||
@@ -29,14 +35,19 @@ export interface ValidateProductInputStepInput {
|
||||
/**
|
||||
* The products to validate.
|
||||
*/
|
||||
products: CreateProductWorkflowInputDTO[]
|
||||
products: Omit<CreateProductWorkflowInputDTO, "sales_channels">[]
|
||||
|
||||
/**
|
||||
* The shipping profiles to validate.
|
||||
*/
|
||||
shippingProfiles: { id: string }[]
|
||||
}
|
||||
|
||||
const validateProductInputStepId = "validate-product-input"
|
||||
/**
|
||||
* This step validates that all provided products have options.
|
||||
* If a product is missing options, an error is thrown.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const data = validateProductInputStep({
|
||||
* products: [
|
||||
@@ -71,7 +82,7 @@ const validateProductInputStepId = "validate-product-input"
|
||||
export const validateProductInputStep = createStep(
|
||||
validateProductInputStepId,
|
||||
async (data: ValidateProductInputStepInput) => {
|
||||
const { products } = data
|
||||
const { products, shippingProfiles } = data
|
||||
|
||||
const missingOptionsProductTitles = products
|
||||
.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(
|
||||
", "
|
||||
)}].`
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -102,11 +132,11 @@ export const createProductsWorkflowId = "create-products"
|
||||
/**
|
||||
* This workflow creates one or more products. It's used by the [Create Product Admin API Route](https://docs.medusajs.com/api/admin#products_postproducts).
|
||||
* It can also be useful to you when creating [seed scripts](https://docs.medusajs.com/learn/fundamentals/custom-cli-scripts/seed-data), for example.
|
||||
*
|
||||
*
|
||||
* This workflow has a hook that allows you to perform custom actions on the created products. You can see an example in [this guide](https://docs.medusajs.com/resources/commerce-modules/product/extend).
|
||||
*
|
||||
*
|
||||
* You can also use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around product creation.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const { result } = await createProductsWorkflow(container)
|
||||
* .run({
|
||||
@@ -143,26 +173,45 @@ export const createProductsWorkflowId = "create-products"
|
||||
* }
|
||||
* }
|
||||
* })
|
||||
*
|
||||
*
|
||||
* @summary
|
||||
*
|
||||
*
|
||||
* Create one or more products with options and variants.
|
||||
*
|
||||
*
|
||||
* @property hooks.productCreated - This hook is executed after the products are created. You can consume this hook to perform custom actions on the created products.
|
||||
*/
|
||||
export const createProductsWorkflow = createWorkflow(
|
||||
createProductsWorkflowId,
|
||||
(input: WorkflowData<CreateProductsWorkflowInput>) => {
|
||||
// Passing prices to the product module will fail, we want to keep them for after the product is created.
|
||||
const productWithoutExternalRelations = transform({ input }, (data) =>
|
||||
data.input.products.map((p) => ({
|
||||
...p,
|
||||
sales_channels: undefined,
|
||||
variants: undefined,
|
||||
}))
|
||||
)
|
||||
const { products: productWithoutExternalRelations, shippingPorfileIds } =
|
||||
transform({ input }, (data) => {
|
||||
const shippingPorfileIds: string[] = []
|
||||
const productsData = data.input.products.map((p) => {
|
||||
if (p.shipping_profile_id) {
|
||||
shippingPorfileIds.push(p.shipping_profile_id)
|
||||
}
|
||||
|
||||
validateProductInputStep({ products: productWithoutExternalRelations })
|
||||
return {
|
||||
...p,
|
||||
sales_channels: undefined,
|
||||
shipping_profile_id: undefined,
|
||||
variants: undefined,
|
||||
}
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
@@ -182,6 +231,24 @@ export const createProductsWorkflow = createWorkflow(
|
||||
|
||||
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) => {
|
||||
// TODO: Move this to a unified place for all product workflow types
|
||||
const productVariants: (ProductTypes.CreateProductVariantDTO & {
|
||||
|
||||
@@ -47,6 +47,10 @@ export type UpdateProductsWorkflowInputSelector = {
|
||||
* The variants to update.
|
||||
*/
|
||||
variants?: UpdateProductVariantWorkflowInputDTO[]
|
||||
/**
|
||||
* The shipping profile to set.
|
||||
*/
|
||||
shipping_profile_id?: string
|
||||
}
|
||||
} & AdditionalData
|
||||
|
||||
@@ -66,6 +70,10 @@ export type UpdateProductsWorkflowInputProducts = {
|
||||
* The variants to update.
|
||||
*/
|
||||
variants?: UpdateProductVariantWorkflowInputDTO[]
|
||||
/**
|
||||
* The shipping profile to set.
|
||||
*/
|
||||
shipping_profile_id?: string
|
||||
})[]
|
||||
} & AdditionalData
|
||||
|
||||
@@ -90,6 +98,7 @@ function prepareUpdateProductInput({
|
||||
products: input.products.map((p) => ({
|
||||
...p,
|
||||
sales_channels: undefined,
|
||||
shipping_profile_id: undefined,
|
||||
variants: p.variants?.map((v) => ({
|
||||
...v,
|
||||
prices: undefined,
|
||||
@@ -103,6 +112,7 @@ function prepareUpdateProductInput({
|
||||
update: {
|
||||
...input.update,
|
||||
sales_channels: undefined,
|
||||
shipping_profile_id: undefined,
|
||||
variants: input.update?.variants?.map((v) => ({
|
||||
...v,
|
||||
prices: undefined,
|
||||
@@ -173,6 +183,44 @@ function prepareSalesChannelLinks({
|
||||
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({
|
||||
input,
|
||||
updatedProducts,
|
||||
@@ -243,18 +291,42 @@ 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"
|
||||
/**
|
||||
* 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 has a hook that allows you to perform custom actions on the updated products. For example, you can pass under `additional_data` custom data that
|
||||
*
|
||||
* This workflow has a hook that allows you to perform custom actions on the updated products. For example, you can pass under `additional_data` custom data that
|
||||
* allows you to update custom data models linked to the products.
|
||||
*
|
||||
*
|
||||
* You can also use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around product update.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* To update products by their IDs:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* const { result } = await updateProductsWorkflow(container)
|
||||
* .run({
|
||||
@@ -282,9 +354,9 @@ export const updateProductsWorkflowId = "update-products"
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* You can also update products by a selector:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* const { result } = await updateProductsWorkflow(container)
|
||||
* .run({
|
||||
@@ -301,11 +373,11 @@ export const updateProductsWorkflowId = "update-products"
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* @summary
|
||||
*
|
||||
*
|
||||
* Update one or more products with options and variants.
|
||||
*
|
||||
*
|
||||
* @property hooks.productsUpdated - This hook is executed after the products are updated. You can consume this hook to perform custom actions on the updated products.
|
||||
*/
|
||||
export const updateProductsWorkflow = createWorkflow(
|
||||
@@ -345,11 +417,20 @@ export const updateProductsWorkflow = createWorkflow(
|
||||
const toUpdateInput = transform({ input }, prepareUpdateProductInput)
|
||||
const updatedProducts = updateProductsStep(toUpdateInput)
|
||||
|
||||
const updatedPorductIds = transform({ updatedProducts }, (data) => {
|
||||
return data.updatedProducts.map((p) => p.id)
|
||||
})
|
||||
|
||||
const salesChannelLinks = transform(
|
||||
{ input, updatedProducts },
|
||||
prepareSalesChannelLinks
|
||||
)
|
||||
|
||||
const shippingProfileLinks = transform(
|
||||
{ input, updatedProducts },
|
||||
prepareShippingProfileLinks
|
||||
)
|
||||
|
||||
const variantPrices = transform(
|
||||
{ input, updatedProducts },
|
||||
prepareVariantPrices
|
||||
@@ -366,16 +447,33 @@ export const updateProductsWorkflow = createWorkflow(
|
||||
variables: { filters: { product_id: productsWithSalesChannels } },
|
||||
}).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(
|
||||
{ currentSalesChannelLinks },
|
||||
prepareToDeleteSalesChannelLinks
|
||||
)
|
||||
|
||||
const toDeleteShippingProfileLinks = transform(
|
||||
{ currentShippingProfileLinks },
|
||||
prepareToDeleteShippingProfileLinks
|
||||
)
|
||||
|
||||
upsertVariantPricesWorkflow.runAsStep({
|
||||
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(
|
||||
{ updatedProducts },
|
||||
@@ -387,7 +485,12 @@ export const updateProductsWorkflow = createWorkflow(
|
||||
)
|
||||
|
||||
parallelize(
|
||||
createRemoteLinkStep(salesChannelLinks),
|
||||
createRemoteLinkStep(salesChannelLinks).config({
|
||||
name: "create-sales-channel-links-step",
|
||||
}),
|
||||
createRemoteLinkStep(shippingProfileLinks).config({
|
||||
name: "create-shipping-profile-links-step",
|
||||
}),
|
||||
emitEventStep({
|
||||
eventName: ProductWorkflowEvents.UPDATED,
|
||||
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 { removeRemoteLinkStep } from "../../common"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { removeRemoteLinkStep, useQueryGraphStep } from "../../common"
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -19,10 +42,11 @@ export const deleteShippingProfileWorkflowId =
|
||||
/**
|
||||
* 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).
|
||||
*
|
||||
* 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
|
||||
* delete shipping profiles within your custom flows.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const { result } = await deleteShippingProfileWorkflow(container)
|
||||
* .run({
|
||||
@@ -30,14 +54,24 @@ export const deleteShippingProfileWorkflowId =
|
||||
* ids: ["sp_123"]
|
||||
* }
|
||||
* })
|
||||
*
|
||||
*
|
||||
* @summary
|
||||
*
|
||||
*
|
||||
* Delete shipping profiles.
|
||||
*/
|
||||
export const deleteShippingProfileWorkflow = createWorkflow(
|
||||
deleteShippingProfileWorkflowId,
|
||||
(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)
|
||||
|
||||
removeRemoteLinkStep({
|
||||
|
||||
@@ -32,6 +32,11 @@ export interface AdminCreateOrderFulfillment {
|
||||
* to fulfill the items from.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -5,7 +5,7 @@ export interface AdminExportProductRequest {}
|
||||
export interface AdminImportProductRequest {
|
||||
/**
|
||||
* The CSV file to import the products from.
|
||||
*
|
||||
*
|
||||
* It's an uploaded file of type [File](https://developer.mozilla.org/en-US/docs/Web/API/File).
|
||||
*/
|
||||
file: File
|
||||
@@ -35,7 +35,7 @@ export interface AdminBatchProductVariantInventoryItemRequest
|
||||
export interface AdminCreateProductVariantPrice {
|
||||
/**
|
||||
* The price's currency code.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* usd
|
||||
*/
|
||||
@@ -54,11 +54,11 @@ export interface AdminCreateProductVariantPrice {
|
||||
max_quantity?: number | null
|
||||
/**
|
||||
* The price's rules.
|
||||
*
|
||||
*
|
||||
* @privateRemarks
|
||||
* Note: Although the BE is generic, we only use region_id for price rules for now, so it's better to keep the typings stricter.
|
||||
*/
|
||||
rules?: {
|
||||
rules?: {
|
||||
/**
|
||||
* The ID of the region that the price applies in.
|
||||
*/
|
||||
@@ -180,11 +180,11 @@ export interface AdminCreateProduct {
|
||||
/**
|
||||
* The product's images.
|
||||
*/
|
||||
images?: {
|
||||
images?: {
|
||||
/**
|
||||
* The image's URL.
|
||||
*/
|
||||
url: string
|
||||
url: string
|
||||
}[]
|
||||
/**
|
||||
* The product's thumbnail URL.
|
||||
@@ -210,10 +210,14 @@ export interface AdminCreateProduct {
|
||||
* The ID of the product's collection.
|
||||
*/
|
||||
collection_id?: string
|
||||
/**
|
||||
* The ID of the product's shipping profile.
|
||||
*/
|
||||
shipping_profile_id: string
|
||||
/**
|
||||
* The product's categories.
|
||||
*/
|
||||
categories?: {
|
||||
categories?: {
|
||||
/**
|
||||
* The ID of a product category that the product belongs to.
|
||||
*/
|
||||
@@ -222,7 +226,7 @@ export interface AdminCreateProduct {
|
||||
/**
|
||||
* The product's tags.
|
||||
*/
|
||||
tags?: {
|
||||
tags?: {
|
||||
/**
|
||||
* The ID of the associated product tag.
|
||||
*/
|
||||
@@ -239,7 +243,7 @@ export interface AdminCreateProduct {
|
||||
/**
|
||||
* The sales channels that the product is available in.
|
||||
*/
|
||||
sales_channels?: {
|
||||
sales_channels?: {
|
||||
/**
|
||||
* The ID of a sales channel that the product is available in.
|
||||
*/
|
||||
@@ -363,7 +367,8 @@ export interface AdminUpdateProductVariant {
|
||||
options?: Record<string, string>
|
||||
}
|
||||
|
||||
export interface AdminBatchUpdateProductVariant extends AdminUpdateProductVariant {
|
||||
export interface AdminBatchUpdateProductVariant
|
||||
extends AdminUpdateProductVariant {
|
||||
/**
|
||||
* The ID of the variant to update.
|
||||
*/
|
||||
@@ -394,7 +399,7 @@ export interface AdminUpdateProduct {
|
||||
/**
|
||||
* The product's images.
|
||||
*/
|
||||
images?: {
|
||||
images?: {
|
||||
/**
|
||||
* The image's URL.
|
||||
*/
|
||||
@@ -427,20 +432,20 @@ export interface AdminUpdateProduct {
|
||||
/**
|
||||
* The product's categories.
|
||||
*/
|
||||
categories?: {
|
||||
categories?: {
|
||||
/**
|
||||
* The ID of the category that the product belongs to.
|
||||
*/
|
||||
id: string
|
||||
id: string
|
||||
}[]
|
||||
/**
|
||||
* The product's tags.
|
||||
*/
|
||||
tags?: {
|
||||
tags?: {
|
||||
/**
|
||||
* The ID of a tag that the product is associated with.
|
||||
*/
|
||||
id: string
|
||||
id: string
|
||||
}[]
|
||||
/**
|
||||
* The product's options.
|
||||
@@ -453,12 +458,16 @@ export interface AdminUpdateProduct {
|
||||
/**
|
||||
* The sales channels that the product is available in.
|
||||
*/
|
||||
sales_channels?: {
|
||||
sales_channels?: {
|
||||
/**
|
||||
* The ID of a sales channel that the product is available in.
|
||||
*/
|
||||
id: string
|
||||
id: string
|
||||
}[]
|
||||
/**
|
||||
* The ID of the product's shipping profile.
|
||||
*/
|
||||
shipping_profile_id?: string
|
||||
/**
|
||||
* The product's weight.
|
||||
*/
|
||||
|
||||
@@ -62,6 +62,11 @@ export interface CreateOrderFulfillmentWorkflowInput {
|
||||
*/
|
||||
metadata?: Record<string, any> | null
|
||||
|
||||
/**
|
||||
* Shipping option to be used for the fulfillment.
|
||||
*/
|
||||
shipping_option_id?: string
|
||||
|
||||
/**
|
||||
* Whether the fulfillment should be shipped.
|
||||
*/
|
||||
|
||||
@@ -33,6 +33,10 @@ export type CreateProductWorkflowInputDTO = Omit<
|
||||
* The sales channels that the product is available in.
|
||||
*/
|
||||
sales_channels?: { id: string }[]
|
||||
/**
|
||||
* The product's shipping profile.
|
||||
*/
|
||||
shipping_profile_id: string
|
||||
/**
|
||||
* The product's variants.
|
||||
*/
|
||||
@@ -41,4 +45,5 @@ export type CreateProductWorkflowInputDTO = Omit<
|
||||
|
||||
export type UpdateProductWorkflowInputDTO = ProductTypes.UpsertProductDTO & {
|
||||
sales_channels?: { id: string }[]
|
||||
shipping_profile_id?: string
|
||||
}
|
||||
|
||||
@@ -116,4 +116,10 @@ export const LINKS = {
|
||||
Modules.FULFILLMENT,
|
||||
"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({
|
||||
items: z.array(Item),
|
||||
location_id: z.string().nullish(),
|
||||
shipping_option_id: z.string().optional(),
|
||||
no_notification: z.boolean().optional(),
|
||||
metadata: z.record(z.unknown()).nullish(),
|
||||
})
|
||||
|
||||
@@ -232,6 +232,7 @@ export const CreateProduct = z
|
||||
options: z.array(CreateProductOption).optional(),
|
||||
variants: z.array(CreateProductVariant).optional(),
|
||||
sales_channels: z.array(z.object({ id: z.string() })).optional(),
|
||||
shipping_profile_id: z.string(),
|
||||
weight: z.number().nullish(),
|
||||
length: z.number().nullish(),
|
||||
height: z.number().nullish(),
|
||||
@@ -266,6 +267,7 @@ export const UpdateProduct = z
|
||||
categories: z.array(IdAssociation).optional(),
|
||||
tags: z.array(IdAssociation).optional(),
|
||||
sales_channels: z.array(z.object({ id: z.string() })).optional(),
|
||||
shipping_profile_id: z.string().optional(),
|
||||
weight: z.number().nullish(),
|
||||
length: 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 "./sales-channel-location"
|
||||
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