fix(medusa) : Improperly referenced product id in product import strategy (#2351)
* What - fixing PR #2350 * Why - In the ProductImportStrategy class, the getImportInstructions improperly checks the product.id for each row. With it setup this way, products will always be considered new because the product.id column is not read here. So products will never be updated. [Link to the code line](https://github.com/medusajs/medusa/blob/bd941309161bcee4fbfc7918d310c426ba8d8f15/packages/medusa/src/strategies/batch-jobs/product/import.ts#L166) * How - Replacing `row["product.product.id"]` to `row["product.id"]` Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
1 parent
3d255302b0
commit
d8a5942d3d
9 files changed
+110
-67
No files matched your search
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix wrongly referenced product id in ProductImportStrategy
|
||||||
|
Fix assigning nested objects properly
|
||||||
@@ -118,17 +118,16 @@ describe("Product import - Sales Channel", () => {
|
|||||||
|
|
||||||
const productsResponse = await api.get("/admin/products", adminReqConfig)
|
const productsResponse = await api.get("/admin/products", adminReqConfig)
|
||||||
|
|
||||||
expect(productsResponse.data.count).toBe(2)
|
expect(productsResponse.data.count).toBe(1)
|
||||||
expect(productsResponse.data.products).toEqual([
|
expect(productsResponse.data.products).toEqual([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: "O6S1YQ6mKm",
|
|
||||||
title: "Test product",
|
title: "Test product",
|
||||||
description: "test-product-description-1",
|
description:
|
||||||
|
"Hopper Stripes Bedding, available as duvet cover, pillow sham and sheet.\\n100% organic cotton, soft and crisp to the touch. Made in Portugal.",
|
||||||
handle: "test-product-product-1",
|
handle: "test-product-product-1",
|
||||||
variants: [
|
variants: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
title: "Test variant",
|
title: "Test variant",
|
||||||
product_id: "O6S1YQ6mKm",
|
|
||||||
sku: "test-sku-1",
|
sku: "test-sku-1",
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
@@ -143,25 +142,6 @@ describe("Product import - Sales Channel", () => {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
|
||||||
id: "5VxiEkmnPV",
|
|
||||||
title: "Test product",
|
|
||||||
description: "test-product-description",
|
|
||||||
handle: "test-product-product-2",
|
|
||||||
variants: [
|
|
||||||
expect.objectContaining({
|
|
||||||
title: "Test variant",
|
|
||||||
product_id: "5VxiEkmnPV",
|
|
||||||
sku: "test-sku-2",
|
|
||||||
}),
|
|
||||||
expect.objectContaining({
|
|
||||||
title: "Test variant",
|
|
||||||
product_id: "5VxiEkmnPV",
|
|
||||||
sku: "test-sku-3",
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
sales_channels: [],
|
|
||||||
}),
|
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -8,6 +8,7 @@ const { initDb, useDb } = require("../../../../helpers/use-db")
|
|||||||
const adminSeeder = require("../../../helpers/admin-seeder")
|
const adminSeeder = require("../../../helpers/admin-seeder")
|
||||||
const batchJobSeeder = require("../../../helpers/batch-job-seeder")
|
const batchJobSeeder = require("../../../helpers/batch-job-seeder")
|
||||||
const userSeeder = require("../../../helpers/user-seeder")
|
const userSeeder = require("../../../helpers/user-seeder")
|
||||||
|
const { simpleProductFactory } = require("../../../factories")
|
||||||
|
|
||||||
const adminReqConfig = {
|
const adminReqConfig = {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -71,6 +72,29 @@ describe("Product import batch job", () => {
|
|||||||
jest.setTimeout(1000000)
|
jest.setTimeout(1000000)
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
|
const existingProductToBeUpdated = await simpleProductFactory(
|
||||||
|
dbConnection,
|
||||||
|
{
|
||||||
|
id: "existing-product-id",
|
||||||
|
title: "Test product",
|
||||||
|
options: [{ id: "opt-1-id", title: "Size" }],
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: "existing-variant-id",
|
||||||
|
title: "Initial tile",
|
||||||
|
sku: "test-sku-4",
|
||||||
|
barde: "test-barcode-4",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
option_id: "opt-1-id",
|
||||||
|
value: "Large",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
"/admin/batch-jobs",
|
"/admin/batch-jobs",
|
||||||
{
|
{
|
||||||
@@ -113,8 +137,8 @@ describe("Product import batch job", () => {
|
|||||||
expect(productsResponse.data.count).toBe(2)
|
expect(productsResponse.data.count).toBe(2)
|
||||||
expect(productsResponse.data.products).toEqual(
|
expect(productsResponse.data.products).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
|
// NEW PRODUCT
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: "O6S1YQ6mKm",
|
|
||||||
title: "Test product",
|
title: "Test product",
|
||||||
description:
|
description:
|
||||||
"Hopper Stripes Bedding, available as duvet cover, pillow sham and sheet.\\n100% organic cotton, soft and crisp to the touch. Made in Portugal.",
|
"Hopper Stripes Bedding, available as duvet cover, pillow sham and sheet.\\n100% organic cotton, soft and crisp to the touch. Made in Portugal.",
|
||||||
@@ -123,9 +147,9 @@ describe("Product import batch job", () => {
|
|||||||
status: "draft",
|
status: "draft",
|
||||||
thumbnail: "test-image.png",
|
thumbnail: "test-image.png",
|
||||||
variants: [
|
variants: [
|
||||||
|
// NEW VARIANT
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
title: "Test variant",
|
title: "Test variant",
|
||||||
product_id: "O6S1YQ6mKm",
|
|
||||||
sku: "test-sku-1",
|
sku: "test-sku-1",
|
||||||
barcode: "test-barcode-1",
|
barcode: "test-barcode-1",
|
||||||
ean: null,
|
ean: null,
|
||||||
@@ -147,14 +171,14 @@ describe("Product import batch job", () => {
|
|||||||
region_id: "region-product-import-1",
|
region_id: "region-product-import-1",
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
options: [
|
options: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
value: "option 1 value red",
|
value: "option 1 value red",
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
value: "option 2 value 1",
|
value: "option 2 value 1",
|
||||||
}),
|
}),
|
||||||
],
|
]),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
images: [
|
images: [
|
||||||
@@ -165,11 +189,9 @@ describe("Product import batch job", () => {
|
|||||||
options: [
|
options: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
title: "test-option-1",
|
title: "test-option-1",
|
||||||
product_id: "O6S1YQ6mKm",
|
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
title: "test-option-2",
|
title: "test-option-2",
|
||||||
product_id: "O6S1YQ6mKm",
|
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
tags: [
|
tags: [
|
||||||
@@ -178,8 +200,9 @@ describe("Product import batch job", () => {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
// UPDATED PRODUCT
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: "5VxiEkmnPV",
|
id: existingProductToBeUpdated.id,
|
||||||
title: "Test product",
|
title: "Test product",
|
||||||
description: "test-product-description",
|
description: "test-product-description",
|
||||||
handle: "test-product-product-2",
|
handle: "test-product-product-2",
|
||||||
@@ -187,10 +210,24 @@ describe("Product import batch job", () => {
|
|||||||
status: "draft",
|
status: "draft",
|
||||||
thumbnail: "test-image.png",
|
thumbnail: "test-image.png",
|
||||||
profile_id: expect.any(String),
|
profile_id: expect.any(String),
|
||||||
variants: [
|
variants: expect.arrayContaining([
|
||||||
|
// UPDATED VARIANT
|
||||||
|
expect.objectContaining({
|
||||||
|
id: "existing-variant-id",
|
||||||
|
title: "Test variant changed",
|
||||||
|
sku: "test-sku-4",
|
||||||
|
barcode: "test-barcode-4",
|
||||||
|
options: [
|
||||||
|
expect.objectContaining({
|
||||||
|
value: "Large",
|
||||||
|
option_id: "opt-1-id",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
// CREATED VARIANT
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
title: "Test variant",
|
title: "Test variant",
|
||||||
product_id: "5VxiEkmnPV",
|
product_id: existingProductToBeUpdated.id,
|
||||||
sku: "test-sku-2",
|
sku: "test-sku-2",
|
||||||
barcode: "test-barcode-2",
|
barcode: "test-barcode-2",
|
||||||
ean: null,
|
ean: null,
|
||||||
@@ -207,13 +244,15 @@ describe("Product import batch job", () => {
|
|||||||
],
|
],
|
||||||
options: [
|
options: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
value: "Option 1 value 1",
|
value: "Small",
|
||||||
|
option_id: "opt-1-id",
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
// CREATED VARIANT
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
title: "Test variant",
|
title: "Test variant",
|
||||||
product_id: "5VxiEkmnPV",
|
product_id: existingProductToBeUpdated.id,
|
||||||
sku: "test-sku-3",
|
sku: "test-sku-3",
|
||||||
barcode: "test-barcode-3",
|
barcode: "test-barcode-3",
|
||||||
ean: null,
|
ean: null,
|
||||||
@@ -230,11 +269,12 @@ describe("Product import batch job", () => {
|
|||||||
],
|
],
|
||||||
options: [
|
options: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
value: "Option 1 Value blue",
|
value: "Medium",
|
||||||
|
option_id: "opt-1-id",
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
],
|
]),
|
||||||
images: [
|
images: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
url: "test-image.png",
|
url: "test-image.png",
|
||||||
@@ -242,8 +282,9 @@ describe("Product import batch job", () => {
|
|||||||
],
|
],
|
||||||
options: [
|
options: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
title: "test-option",
|
product_id: existingProductToBeUpdated.id,
|
||||||
product_id: "5VxiEkmnPV",
|
id: "opt-1-id",
|
||||||
|
title: "Size",
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
tags: [
|
tags: [
|
||||||
|
|||||||
@@ -1,4 +1,2 @@
|
|||||||
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,Product Profile Name,Product Profile Type,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 ImportLand [EUR],Price USD,Price denmark [DKK],Price Denmark [DKK],Option 1 Name,Option 1 Value,Option 2 Name,Option 2 Value,Image 1 Url,Sales Channel 1 Name,Sales Channel 2 Name,Sales Channel 1 Id,Sales Channel 2 Id
|
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 ImportLand [EUR],Price USD,Price denmark [DKK],Price Denmark [DKK],Option 1 Name,Option 1 Value,Option 2 Name,Option 2 Value,Image 1 Url,Sales Channel 1 Name,Sales Channel 2 Name,Sales Channel 1 Id,Sales Channel 2 Id
|
||||||
O6S1YQ6mKm,test-product-product-1,Test product,,test-product-description-1,draft,,,,,,,,,,Test collection 1,test-collection1,test-type-1,123_1,TRUE,,profile_1,profile_type_1,,Test variant,test-sku-1,test-barcode-1,10,FALSE,TRUE,,,,,,,,,1.00,1.10,1.30,,test-option-1,option 1 value red,test-option-2,option 2 value 1,test-image.png,Import Sales Channel 1,Import Sales Channel 2,,
|
,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,test-type-1,123_1,TRUE,,,Test variant,test-sku-1,test-barcode-1,10,FALSE,TRUE,,,,,,,,,1.00,1.10,1.30,,test-option-1,option 1 value red,test-option-2,option 2 value 1,test-image.png,Import Sales Channel 1,Import Sales Channel 2,,
|
||||||
5VxiEkmnPV,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,profile_2,profile_type_2,,Test variant,test-sku-2,test-barcode-2,10,FALSE,TRUE,,,,,,,,,,,,1.10,test-option,Option 1 value 1,,,test-image.png,,,,
|
|
||||||
5VxiEkmnPV,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,profile_2,profile_type_2,,Test variant,test-sku-3,test-barcode-3,10,FALSE,TRUE,,,,,,,,,,1.20,,,test-option,Option 1 Value blue,,,test-image.png,,,,
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
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,Product Profile Name,Product Profile Type,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 ImportLand [EUR],Price USD,Price denmark [DKK],Price Denmark [DKK],Option 1 Name,Option 1 Value,Option 2 Name,Option 2 Value,Image 1 Url
|
Product id,Product Handle,Product Title,Product Subtitle,Product Description,Product Status,Product Thumbnail,Product Weight,Product Length,Product Width,Product Height,Product HS Code,Product Origin Country,Product MID Code,Product Material,Product Collection Title,Product Collection Handle,Product Type,Product Tags,Product Discountable,Product External ID,Variant id,Variant Title,Variant SKU,Variant Barcode,Variant Inventory Quantity,Variant Allow backorder,Variant Manage inventory,Variant Weight,Variant Length,Variant Width,Variant Height,Variant HS Code,Variant Origin Country,Variant MID Code,Variant Material,Price ImportLand [EUR],Price USD,Price denmark [DKK],Price Denmark [DKK],Option 1 Name,Option 1 Value,Option 2 Name,Option 2 Value,Image 1 Url
|
||||||
O6S1YQ6mKm,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,test-type-1,123_1,TRUE,,profile_1,profile_type_1,,Test variant,test-sku-1,test-barcode-1,10,FALSE,TRUE,,,,,,,,,1.00,1.10,1.30,,test-option-1,option 1 value red,test-option-2,option 2 value 1,test-image.png
|
,test-product-product-1,Test product,,"Hopper Stripes Bedding, available as duvet cover, pillow sham and sheet.\n100% organic cotton, soft and crisp to the touch. Made in Portugal.",draft,,,,,,,,,,Test collection 1,test-collection1,test-type-1,123_1,TRUE,,,Test variant,test-sku-1,test-barcode-1,10,FALSE,TRUE,,,,,,,,,1.00,1.10,1.30,,test-option-1,option 1 value red,test-option-2,option 2 value 1,test-image.png
|
||||||
5VxiEkmnPV,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,profile_2,profile_type_2,,Test variant,test-sku-2,test-barcode-2,10,FALSE,TRUE,,,,,,,,,,,,1.10,test-option,Option 1 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
|
||||||
5VxiEkmnPV,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,profile_2,profile_type_2,,Test variant,test-sku-3,test-barcode-3,10,FALSE,TRUE,,,,,,,,,,1.20,,,test-option,Option 1 Value blue,,,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
|
||||||
|
@@ -1,12 +1,10 @@
|
|||||||
import { Readable, PassThrough } from "stream"
|
import { PassThrough, Readable } from "stream"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
|
|
||||||
import { FileService } from "medusa-interfaces"
|
import { FileService } from "medusa-interfaces"
|
||||||
import { IdMap, MockManager } from "medusa-test-utils"
|
import { IdMap, MockManager } from "medusa-test-utils"
|
||||||
|
|
||||||
import { User } from "../../../../models"
|
import { User } from "../../../../models"
|
||||||
import { BatchJobStatus } from "../../../../types/batch-job"
|
|
||||||
import ProductImportStrategy from "../../../batch-jobs/product/import"
|
|
||||||
import {
|
import {
|
||||||
BatchJobService,
|
BatchJobService,
|
||||||
ProductService,
|
ProductService,
|
||||||
@@ -14,8 +12,10 @@ import {
|
|||||||
RegionService,
|
RegionService,
|
||||||
ShippingProfileService,
|
ShippingProfileService,
|
||||||
} from "../../../../services"
|
} from "../../../../services"
|
||||||
import { InjectedProps } from "../../../batch-jobs/product/types"
|
import { BatchJobStatus } from "../../../../types/batch-job"
|
||||||
import { FlagRouter } from "../../../../utils/flag-router"
|
import { FlagRouter } from "../../../../utils/flag-router"
|
||||||
|
import ProductImportStrategy from "../../../batch-jobs/product/import"
|
||||||
|
import { InjectedProps } from "../../../batch-jobs/product/types"
|
||||||
|
|
||||||
let fakeJob = {
|
let fakeJob = {
|
||||||
id: IdMap.getId("product-import-job"),
|
id: IdMap.getId("product-import-job"),
|
||||||
@@ -32,10 +32,10 @@ let fakeJob = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function* generateCSVDataForStream() {
|
async function* generateCSVDataForStream() {
|
||||||
yield "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,Product Profile Name,Product Profile Type,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 france [USD],Price USD,Price denmark [DKK],Price Denmark [DKK],Option 1 Name,Option 1 Value,Option 2 Name,Option 2 Value,Image 1 Url\n"
|
yield "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 france [USD],Price USD,Price denmark [DKK],Price Denmark [DKK],Option 1 Name,Option 1 Value,Option 2 Name,Option 2 Value,Image 1 Url\n"
|
||||||
yield "O6S1YQ6mKm,test-product-product-1,Test product,,test-product-description-1,draft,,,,,,,,,,Test collection 1,test-collection1,test-type-1,123_1,TRUE,,profile_1,profile_type_1,SebniWTDeC,Test variant,test-sku-1,test-barcode-1,10,FALSE,TRUE,,,,,,,,,100,110,130,,test-option-1,option 1 value red,test-option-2,option 2 value 1,test-image.png\n"
|
yield ",test-product-product-1,Test product,,test-product-description-1,draft,,,,,,,,,,Test collection 1,test-collection1,test-type-1,123_1,TRUE,,SebniWTDeC,Test variant,test-sku-1,test-barcode-1,10,FALSE,TRUE,,,,,,,,,100,110,130,,test-option-1,option 1 value red,test-option-2,option 2 value 1,test-image.png\n"
|
||||||
yield "5VxiEkmnPV,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,profile_2,profile_type_2,CaBp7amx3r,Test variant,test-sku-2,test-barcode-2,10,FALSE,TRUE,,,,,,,,,,,,110,test-option,Option 1 value 1,,,test-image.png\n"
|
yield "5VxiEkmnPV,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,,Test variant,test-sku-2,test-barcode-2,10,FALSE,TRUE,,,,,,,,,,,,110,test-option,Option 1 value 1,,,test-image.png\n"
|
||||||
yield "5VxiEkmnPV,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,profile_2,profile_type_2,3SS1MHGDEJ,Test variant,test-sku-3,test-barcode-3,10,FALSE,TRUE,,,,,,,,,,120,,,test-option,Option 1 Value blue,,,test-image.png\n"
|
yield "5VxiEkmnPV,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,3SS1MHGDEJ,Test variant,test-sku-3,test-barcode-3,10,FALSE,TRUE,,,,,,,,,,120,,,test-option,Option 1 Value blue,,,test-image.png\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ******************** SERVICES MOCK ******************** */
|
/* ******************** SERVICES MOCK ******************** */
|
||||||
@@ -159,7 +159,7 @@ describe("Product import strategy", () => {
|
|||||||
|
|
||||||
expect(getImportInstructionsSpy).toBeCalledTimes(1)
|
expect(getImportInstructionsSpy).toBeCalledTimes(1)
|
||||||
|
|
||||||
expect(fileServiceMock.getUploadStreamDescriptor).toBeCalledTimes(2)
|
expect(fileServiceMock.getUploadStreamDescriptor).toBeCalledTimes(4)
|
||||||
|
|
||||||
expect(fileServiceMock.getUploadStreamDescriptor).toHaveBeenNthCalledWith(
|
expect(fileServiceMock.getUploadStreamDescriptor).toHaveBeenNthCalledWith(
|
||||||
1,
|
1,
|
||||||
@@ -170,6 +170,20 @@ describe("Product import strategy", () => {
|
|||||||
)
|
)
|
||||||
expect(fileServiceMock.getUploadStreamDescriptor).toHaveBeenNthCalledWith(
|
expect(fileServiceMock.getUploadStreamDescriptor).toHaveBeenNthCalledWith(
|
||||||
2,
|
2,
|
||||||
|
{
|
||||||
|
ext: "json",
|
||||||
|
name: `imports/products/ops/${fakeJob.id}-VARIANT_CREATE`,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(fileServiceMock.getUploadStreamDescriptor).toHaveBeenNthCalledWith(
|
||||||
|
3,
|
||||||
|
{
|
||||||
|
ext: "json",
|
||||||
|
name: `imports/products/ops/${fakeJob.id}-PRODUCT_UPDATE`,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(fileServiceMock.getUploadStreamDescriptor).toHaveBeenNthCalledWith(
|
||||||
|
4,
|
||||||
{
|
{
|
||||||
ext: "json",
|
ext: "json",
|
||||||
name: `imports/products/ops/${fakeJob.id}-VARIANT_UPDATE`, // because row data has variant.id
|
name: `imports/products/ops/${fakeJob.id}-VARIANT_UPDATE`, // because row data has variant.id
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import {
|
|||||||
TBuiltProductImportLine,
|
TBuiltProductImportLine,
|
||||||
TParsedProductImportRowData,
|
TParsedProductImportRowData,
|
||||||
} from "./types"
|
} from "./types"
|
||||||
import { BatchJob, SalesChannel } from "../../../models"
|
import { BatchJob, Product, SalesChannel } from "../../../models"
|
||||||
import { FlagRouter } from "../../../utils/flag-router"
|
import { FlagRouter } from "../../../utils/flag-router"
|
||||||
import { transformProductData, transformVariantData } from "./utils"
|
import { transformProductData, transformVariantData } from "./utils"
|
||||||
import SalesChannelFeatureFlag from "../../../loaders/feature-flags/sales-channels"
|
import SalesChannelFeatureFlag from "../../../loaders/feature-flags/sales-channels"
|
||||||
@@ -163,7 +163,7 @@ class ProductImportStrategy extends AbstractBatchJobStrategy {
|
|||||||
// save only first occurrence
|
// save only first occurrence
|
||||||
if (!seenProducts[row["product.handle"] as string]) {
|
if (!seenProducts[row["product.handle"] as string]) {
|
||||||
row["product.profile_id"] = shippingProfile!.id
|
row["product.profile_id"] = shippingProfile!.id
|
||||||
if (row["product.product.id"]) {
|
if (row["product.id"]) {
|
||||||
productsUpdate.push(row)
|
productsUpdate.push(row)
|
||||||
} else {
|
} else {
|
||||||
productsCreate.push(row)
|
productsCreate.push(row)
|
||||||
@@ -306,7 +306,6 @@ class ProductImportStrategy extends AbstractBatchJobStrategy {
|
|||||||
await this.updateProducts(batchJob)
|
await this.updateProducts(batchJob)
|
||||||
await this.createVariants(batchJob)
|
await this.createVariants(batchJob)
|
||||||
await this.updateVariants(batchJob)
|
await this.updateVariants(batchJob)
|
||||||
|
|
||||||
await this.finalize(batchJob)
|
await this.finalize(batchJob)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -443,6 +442,8 @@ class ProductImportStrategy extends AbstractBatchJobStrategy {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete productData.options // for now not supported in the update method
|
||||||
|
|
||||||
await productServiceTx.update(
|
await productServiceTx.update(
|
||||||
productOp["product.id"] as string,
|
productOp["product.id"] as string,
|
||||||
productData
|
productData
|
||||||
@@ -489,7 +490,7 @@ class ProductImportStrategy extends AbstractBatchJobStrategy {
|
|||||||
product!.options.find(
|
product!.options.find(
|
||||||
(createdProductOption) =>
|
(createdProductOption) =>
|
||||||
createdProductOption.title === variantOption.title
|
createdProductOption.title === variantOption.title
|
||||||
)!.id
|
)?.id
|
||||||
) || []
|
) || []
|
||||||
|
|
||||||
variant.options =
|
variant.options =
|
||||||
@@ -498,6 +499,9 @@ class ProductImportStrategy extends AbstractBatchJobStrategy {
|
|||||||
option_id: optionIds[index],
|
option_id: optionIds[index],
|
||||||
})) || []
|
})) || []
|
||||||
|
|
||||||
|
delete variant.id
|
||||||
|
delete variant.product
|
||||||
|
|
||||||
await this.productVariantService_
|
await this.productVariantService_
|
||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.create(product!, variant as unknown as CreateProductVariantInput)
|
.create(product!, variant as unknown as CreateProductVariantInput)
|
||||||
@@ -700,7 +704,6 @@ class ProductImportStrategy extends AbstractBatchJobStrategy {
|
|||||||
private async updateProgress(batchJobId: string): Promise<void> {
|
private async updateProgress(batchJobId: string): Promise<void> {
|
||||||
const newCount = (this.processedCounter[batchJobId] || 0) + 1
|
const newCount = (this.processedCounter[batchJobId] || 0) + 1
|
||||||
this.processedCounter[batchJobId] = newCount
|
this.processedCounter[batchJobId] = newCount
|
||||||
|
|
||||||
if (newCount % BATCH_SIZE !== 0) {
|
if (newCount % BATCH_SIZE !== 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -769,9 +772,7 @@ const CSVSchema: ProductImportCsvSchema = {
|
|||||||
//
|
//
|
||||||
{ name: "Product Discountable", mapTo: "product.discountable" },
|
{ name: "Product Discountable", mapTo: "product.discountable" },
|
||||||
{ name: "Product External ID", mapTo: "product.external_id" },
|
{ name: "Product External ID", mapTo: "product.external_id" },
|
||||||
// PRODUCT-SHIPPING_PROFILE
|
|
||||||
{ name: "Product Profile Name", mapTo: "product.profile.name" },
|
|
||||||
{ name: "Product Profile Type", mapTo: "product.profile.type" },
|
|
||||||
// VARIANTS
|
// VARIANTS
|
||||||
{
|
{
|
||||||
name: "Variant id",
|
name: "Variant id",
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export type InjectedProps = {
|
|||||||
*/
|
*/
|
||||||
export type TParsedProductImportRowData = Record<
|
export type TParsedProductImportRowData = Record<
|
||||||
string,
|
string,
|
||||||
string | number | (string | number | object)[]
|
string | number | object | (string | number | object)[]
|
||||||
>
|
>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import set from "lodash/set"
|
||||||
|
|
||||||
import { TParsedProductImportRowData } from "./types"
|
import { TParsedProductImportRowData } from "./types"
|
||||||
import { csvRevertCellContentFormatter } from "../../../utils"
|
import { csvRevertCellContentFormatter } from "../../../utils"
|
||||||
|
|
||||||
@@ -37,7 +39,7 @@ export function transformProductData(
|
|||||||
|
|
||||||
Object.keys(productData).forEach((k) => {
|
Object.keys(productData).forEach((k) => {
|
||||||
const key = k.split("product.")[1]
|
const key = k.split("product.")[1]
|
||||||
ret[key] = productData[k]
|
set(ret, key, productData[k])
|
||||||
})
|
})
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
@@ -54,12 +56,12 @@ export function transformVariantData(
|
|||||||
|
|
||||||
Object.keys(productData).forEach((k) => {
|
Object.keys(productData).forEach((k) => {
|
||||||
const key = k.split("variant.")[1]
|
const key = k.split("variant.")[1]
|
||||||
ret[key] = productData[k]
|
set(ret, key, productData[k])
|
||||||
})
|
})
|
||||||
|
|
||||||
// include product handle to keep track of associated product
|
// include product handle to keep track of associated product
|
||||||
ret["product.handle"] = data["product.handle"]
|
ret["product.handle"] = data["product.handle"]
|
||||||
ret["product.options"] = data["product.options"]
|
set(ret, "product.options", data["product.options"])
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user