-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(medusa): PriceList import strategy (#2210)
- Loading branch information
Showing
17 changed files
with
1,129 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@medusajs/medusa": minor | ||
--- | ||
|
||
Adds a BatchJob strategy for importing prices to PriceLists |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"medusa-core-utils": minor | ||
--- | ||
|
||
Adds `computerizeAmount` utility to convert human money format into the DB format Medusa uses (integer of lowest currency unit) |
290 changes: 290 additions & 0 deletions
290
integration-tests/api/__tests__/batch-jobs/price-list/import.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,290 @@ | ||
const fs = require("fs") | ||
const path = require("path") | ||
|
||
const setupServer = require("../../../../helpers/setup-server") | ||
const { useApi } = require("../../../../helpers/use-api") | ||
const { initDb, useDb } = require("../../../../helpers/use-db") | ||
|
||
const adminSeeder = require("../../../helpers/admin-seeder") | ||
const { | ||
simpleRegionFactory, | ||
simplePriceListFactory, | ||
simpleProductFactory, | ||
} = require("../../../factories") | ||
|
||
const adminReqConfig = { | ||
headers: { | ||
Authorization: "Bearer test_token", | ||
}, | ||
} | ||
|
||
jest.setTimeout(1000000) | ||
|
||
function cleanTempData() { | ||
// cleanup tmp ops files | ||
const opsFiles = path.resolve( | ||
"__tests__", | ||
"batch-jobs", | ||
"price-list", | ||
"imports" | ||
) | ||
|
||
fs.rmSync(opsFiles, { recursive: true, force: true }) | ||
} | ||
|
||
function getImportFile() { | ||
return path.resolve( | ||
"__tests__", | ||
"batch-jobs", | ||
"price-list", | ||
"price-list-import.csv" | ||
) | ||
} | ||
|
||
function copyTemplateFile() { | ||
const csvTemplate = path.resolve( | ||
"__tests__", | ||
"batch-jobs", | ||
"price-list", | ||
"price-list-import-template.csv" | ||
) | ||
const destination = getImportFile() | ||
fs.copyFileSync(csvTemplate, destination) | ||
} | ||
|
||
describe("Price list import batch job", () => { | ||
let medusaProcess | ||
let dbConnection | ||
|
||
beforeAll(async () => { | ||
const cwd = path.resolve(path.join(__dirname, "..", "..", "..")) | ||
dbConnection = await initDb({ cwd }) | ||
|
||
cleanTempData() // cleanup if previous process didn't manage to do it | ||
|
||
medusaProcess = await setupServer({ | ||
cwd, | ||
redisUrl: "redis://127.0.0.1:6379", | ||
uploadDir: __dirname, | ||
verbose: false, | ||
}) | ||
}) | ||
|
||
afterAll(async () => { | ||
const db = useDb() | ||
await db.shutdown() | ||
|
||
cleanTempData() | ||
|
||
medusaProcess.kill() | ||
}) | ||
|
||
beforeEach(async () => { | ||
await adminSeeder(dbConnection) | ||
}) | ||
|
||
afterEach(async () => { | ||
const db = useDb() | ||
await db.teardown() | ||
}) | ||
|
||
it("should import a csv file", async () => { | ||
jest.setTimeout(1000000) | ||
const api = useApi() | ||
|
||
copyTemplateFile() | ||
|
||
const product = await simpleProductFactory(dbConnection, { | ||
variants: [ | ||
{ | ||
id: "test-pl-variant", | ||
}, | ||
{ | ||
id: "test-pl-sku-variant", | ||
sku: "pl-sku", | ||
}, | ||
], | ||
}) | ||
|
||
await simpleRegionFactory(dbConnection, { | ||
id: "test-pl-region", | ||
name: "PL Region", | ||
currency_code: "eur", | ||
}) | ||
|
||
const priceList = await simplePriceListFactory(dbConnection, { | ||
id: "pl_my_price_list", | ||
name: "Test price list", | ||
prices: [ | ||
{ | ||
variant_id: product.variants[0].id, | ||
currency_code: "usd", | ||
amount: 1000, | ||
}, | ||
{ | ||
variant_id: product.variants[0].id, | ||
currency_code: "eur", | ||
amount: 2080, | ||
}, | ||
], | ||
}) | ||
|
||
const response = await api.post( | ||
"/admin/batch-jobs", | ||
{ | ||
type: "price-list-import", | ||
context: { | ||
price_list_id: priceList.id, | ||
fileKey: "price-list-import.csv", | ||
}, | ||
}, | ||
adminReqConfig | ||
) | ||
|
||
const batchJobId = response.data.batch_job.id | ||
|
||
expect(batchJobId).toBeTruthy() | ||
|
||
// Pull to check the status until it is completed | ||
let batchJob | ||
let shouldContinuePulling = true | ||
while (shouldContinuePulling) { | ||
const res = await api.get( | ||
`/admin/batch-jobs/${batchJobId}`, | ||
adminReqConfig | ||
) | ||
|
||
await new Promise((resolve, _) => { | ||
setTimeout(resolve, 1000) | ||
}) | ||
|
||
batchJob = res.data.batch_job | ||
|
||
shouldContinuePulling = !( | ||
batchJob.status === "completed" || batchJob.status === "failed" | ||
) | ||
} | ||
|
||
expect(batchJob.status).toBe("completed") | ||
|
||
const priceListRes = await api.get( | ||
"/admin/price-lists/pl_my_price_list", | ||
adminReqConfig | ||
) | ||
|
||
// Verify that file service deleted file | ||
const importFilePath = getImportFile() | ||
expect(fs.existsSync(importFilePath)).toBe(false) | ||
|
||
expect(priceListRes.data.price_list.prices.length).toEqual(5) | ||
expect(priceListRes.data.price_list.prices).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
variant_id: "test-pl-variant", | ||
currency_code: "usd", | ||
amount: 1111, | ||
}), | ||
expect.objectContaining({ | ||
variant_id: "test-pl-variant", | ||
currency_code: "eur", | ||
region_id: "test-pl-region", | ||
amount: 2222, | ||
}), | ||
expect.objectContaining({ | ||
variant_id: "test-pl-variant", | ||
currency_code: "jpy", | ||
amount: 3333, | ||
}), | ||
expect.objectContaining({ | ||
variant_id: "test-pl-sku-variant", | ||
currency_code: "usd", | ||
amount: 4444, | ||
}), | ||
expect.objectContaining({ | ||
variant_id: "test-pl-sku-variant", | ||
currency_code: "eur", | ||
region_id: "test-pl-region", | ||
amount: 5555, | ||
}), | ||
]) | ||
) | ||
}) | ||
|
||
it("should fail with invalid import format", async () => { | ||
jest.setTimeout(1000000) | ||
const api = useApi() | ||
|
||
const product = await simpleProductFactory(dbConnection, { | ||
variants: [ | ||
{ id: "test-pl-variant" }, | ||
{ id: "test-pl-sku-variant", sku: "pl-sku" }, | ||
], | ||
}) | ||
|
||
await simpleRegionFactory(dbConnection, { | ||
id: "test-pl-region", | ||
name: "PL Region", | ||
currency_code: "eur", | ||
}) | ||
|
||
const priceList = await simplePriceListFactory(dbConnection, { | ||
id: "pl_my_price_list", | ||
name: "Test price list", | ||
prices: [ | ||
{ | ||
variant_id: product.variants[0].id, | ||
currency_code: "usd", | ||
amount: 1000, | ||
}, | ||
{ | ||
variant_id: product.variants[0].id, | ||
currency_code: "eur", | ||
amount: 2080, | ||
}, | ||
], | ||
}) | ||
|
||
const response = await api.post( | ||
"/admin/batch-jobs", | ||
{ | ||
type: "price-list-import", | ||
context: { | ||
price_list_id: priceList.id, | ||
fileKey: "invalid-format.csv", | ||
}, | ||
}, | ||
adminReqConfig | ||
) | ||
|
||
const batchJobId = response.data.batch_job.id | ||
|
||
expect(batchJobId).toBeTruthy() | ||
|
||
// Pull to check the status until it is completed | ||
let batchJob | ||
let shouldContinuePulling = true | ||
while (shouldContinuePulling) { | ||
const res = await api.get( | ||
`/admin/batch-jobs/${batchJobId}`, | ||
adminReqConfig | ||
) | ||
|
||
await new Promise((resolve, _) => { | ||
setTimeout(resolve, 1000) | ||
}) | ||
|
||
batchJob = res.data.batch_job | ||
|
||
shouldContinuePulling = !( | ||
batchJob.status === "completed" || batchJob.status === "failed" | ||
) | ||
} | ||
|
||
expect(batchJob.status).toBe("failed") | ||
expect(batchJob.result).toEqual({ | ||
errors: [ | ||
"The csv file parsing failed due to: Unable to treat column non-descript-column from the csv file. No target column found in the provided schema", | ||
], | ||
}) | ||
}) | ||
}) |
3 changes: 3 additions & 0 deletions
3
integration-tests/api/__tests__/batch-jobs/price-list/invalid-format.csv
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
non-descript-column,SKU,Price USD,Price PL Region [EUR], Price JPY | ||
test-pl-variant,,11.11,22.22,3333 | ||
,pl-sku,44.441,55.55, |
3 changes: 3 additions & 0 deletions
3
integration-tests/api/__tests__/batch-jobs/price-list/price-list-import-template.csv
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
Product Variant ID,SKU,Price USD,Price PL Region [EUR], Price JPY | ||
test-pl-variant,,11.11,22.22,3333 | ||
,pl-sku,44.441,55.55, |
6 changes: 3 additions & 3 deletions
6
integration-tests/api/__tests__/batch-jobs/product/product-import-ss.csv
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
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 | ||
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,,,,,,,,,100,110,130,,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,,,,,,,,,,,,110,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,,,,,,,,,,120,,,test-option,Option 1 Value blue,,,test-image.png,,,, | ||
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,, | ||
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,,,, |
6 changes: 3 additions & 3 deletions
6
integration-tests/api/__tests__/batch-jobs/product/product-import.csv
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
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 | ||
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,,,,,,,,,100,110,130,,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,,,,,,,,,,,,110,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,,,,,,,,,,120,,,test-option,Option 1 Value blue,,,test-image.png | ||
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 | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import zeroDecimalCurrencies from "./zero-decimal-currencies" | ||
|
||
const computerizeAmount = (amount, currency) => { | ||
let divisor = 100 | ||
|
||
if (zeroDecimalCurrencies.includes(currency.toLowerCase())) { | ||
divisor = 1 | ||
} | ||
|
||
return Math.round(amount * divisor) | ||
} | ||
|
||
export default computerizeAmount |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.