fix: Use region name in product pricing exports (#8373)
* fix:Bug fixes to product import * fix:Add an export failed notification if an export fails * fix: Use region name in product export prices
This commit is contained in:
@@ -1,10 +1,19 @@
|
||||
import { BigNumberInput, HttpTypes, PricingTypes } from "@medusajs/types"
|
||||
import { upperCaseFirst } from "@medusajs/utils"
|
||||
import {
|
||||
RegionTypes,
|
||||
BigNumberInput,
|
||||
HttpTypes,
|
||||
PricingTypes,
|
||||
} from "@medusajs/types"
|
||||
import { MedusaError, upperCaseFirst } from "@medusajs/utils"
|
||||
|
||||
// We want to have one row per variant, so we need to normalize the data
|
||||
export const normalizeForExport = (
|
||||
product: HttpTypes.AdminProduct[]
|
||||
product: HttpTypes.AdminProduct[],
|
||||
{ regions }: { regions: RegionTypes.RegionDTO[] }
|
||||
): object[] => {
|
||||
// Currently region names are treated as case-insensitive.
|
||||
const regionsMap = new Map(regions.map((r) => [r.id, r]))
|
||||
|
||||
const res = product.reduce((acc: object[], product) => {
|
||||
const variants = product.variants ?? []
|
||||
if (!variants.length) {
|
||||
@@ -15,7 +24,7 @@ export const normalizeForExport = (
|
||||
variants.forEach((v) => {
|
||||
const toPush = {
|
||||
...normalizeProductForExport(product),
|
||||
...normalizeVariantForExport(v),
|
||||
...normalizeVariantForExport(v, regionsMap),
|
||||
} as any
|
||||
delete toPush["Product Variants"]
|
||||
|
||||
@@ -81,16 +90,31 @@ const normalizeProductForExport = (product: HttpTypes.AdminProduct): object => {
|
||||
const normalizeVariantForExport = (
|
||||
variant: HttpTypes.AdminProductVariant & {
|
||||
price_set?: PricingTypes.PriceSetDTO
|
||||
}
|
||||
},
|
||||
regionsMap: Map<string, RegionTypes.RegionDTO>
|
||||
): object => {
|
||||
const flattenedPrices = variant.price_set?.prices
|
||||
?.sort((a, b) => b.currency_code!.localeCompare(a.currency_code!))
|
||||
.reduce((acc: Record<string, BigNumberInput>, price) => {
|
||||
const regionRule = price.price_rules?.find(
|
||||
(r) => r.attribute === "region"
|
||||
(r) => r.attribute === "region_id"
|
||||
)
|
||||
|
||||
if (regionRule) {
|
||||
acc[beautifyKey(`variant_price_${regionRule.value}`)] = price.amount!
|
||||
const region = regionsMap.get(regionRule?.value!)
|
||||
if (!region) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Region with id ${regionRule?.value} not found`
|
||||
)
|
||||
}
|
||||
|
||||
const regionKey = `variant_price_${region.name
|
||||
.toLowerCase()
|
||||
.split(" ")
|
||||
.join("_")}_[${region.currency_code.toUpperCase()}]`
|
||||
|
||||
acc[beautifyKey(regionKey)] = price.amount!
|
||||
} else if (!price.price_rules?.length) {
|
||||
acc[
|
||||
beautifyKey(`variant_price_${price.currency_code!.toUpperCase()}`)
|
||||
|
||||
@@ -70,6 +70,7 @@ const variantFieldsToOmit = new Map([["variant_product_id", true]])
|
||||
// These fields can have a numeric value, but they are stored as string in the DB so we need to normalize them
|
||||
const stringFields = [
|
||||
"product_tag_",
|
||||
"variant_option_",
|
||||
"variant_barcode",
|
||||
"variant_sku",
|
||||
"variant_ean",
|
||||
@@ -78,6 +79,12 @@ const stringFields = [
|
||||
"variant_mid_code",
|
||||
]
|
||||
|
||||
const booleanFields = [
|
||||
"product_discountable",
|
||||
"variant_manage_inventory",
|
||||
"variant_allow_backorder",
|
||||
]
|
||||
|
||||
const normalizeProductForImport = (
|
||||
rawProduct: object
|
||||
): HttpTypes.AdminCreateProduct => {
|
||||
@@ -221,9 +228,20 @@ const normalizeVariantForImport = (
|
||||
}
|
||||
|
||||
const getNormalizedValue = (key: string, value: any): any => {
|
||||
return stringFields.some((field) => key.startsWith(field))
|
||||
let res = stringFields.some((field) => key.startsWith(field))
|
||||
? value?.toString()
|
||||
: value
|
||||
|
||||
if (booleanFields.some((field) => key.startsWith(field))) {
|
||||
if (value === "TRUE") {
|
||||
res = true
|
||||
}
|
||||
if (value === "FALSE") {
|
||||
res = false
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
const snakecaseKey = (key: string): string => {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { IFileModuleService, HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
IFileModuleService,
|
||||
HttpTypes,
|
||||
IRegionModuleService,
|
||||
} from "@medusajs/types"
|
||||
import { ModuleRegistrationName, convertJsonToCsv } from "@medusajs/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
import { normalizeForExport } from "../helpers/normalize-for-export"
|
||||
@@ -7,7 +11,16 @@ export const generateProductCsvStepId = "generate-product-csv"
|
||||
export const generateProductCsvStep = createStep(
|
||||
generateProductCsvStepId,
|
||||
async (products: HttpTypes.AdminProduct[], { container }) => {
|
||||
const normalizedData = normalizeForExport(products)
|
||||
const regionService = container.resolve<IRegionModuleService>(
|
||||
ModuleRegistrationName.REGION
|
||||
)
|
||||
|
||||
const regions = await regionService.listRegions(
|
||||
{},
|
||||
{ select: ["id", "name", "currency_code"], take: null }
|
||||
)
|
||||
|
||||
const normalizedData = normalizeForExport(products, { regions })
|
||||
const csvContent = convertJsonToCsv(normalizedData)
|
||||
|
||||
const fileModule: IFileModuleService = container.resolve(
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
import { WorkflowTypes } from "@medusajs/types"
|
||||
import { generateProductCsvStep, getAllProductsStep } from "../steps"
|
||||
import { useRemoteQueryStep } from "../../common"
|
||||
import { sendNotificationsStep } from "../../notification"
|
||||
import { notifyOnFailureStep, sendNotificationsStep } from "../../notification"
|
||||
|
||||
export const exportProductsWorkflowId = "export-products"
|
||||
export const exportProductsWorkflow = createWorkflow(
|
||||
@@ -19,6 +19,22 @@ export const exportProductsWorkflow = createWorkflow(
|
||||
backgroundExecution: true,
|
||||
})
|
||||
|
||||
const failureNotification = transform({ input }, (data) => {
|
||||
return [
|
||||
{
|
||||
// We don't need the recipient here for now, but if we want to push feed notifications to a specific user we could add it.
|
||||
to: "",
|
||||
channel: "feed",
|
||||
template: "admin-ui",
|
||||
data: {
|
||||
title: "Product export",
|
||||
description: `Failed to export products, please try again later.`,
|
||||
},
|
||||
},
|
||||
]
|
||||
})
|
||||
notifyOnFailureStep(failureNotification)
|
||||
|
||||
const file = generateProductCsvStep(products)
|
||||
const fileDetails = useRemoteQueryStep({
|
||||
fields: ["id", "url"],
|
||||
|
||||
Reference in New Issue
Block a user