237 lines
8.2 KiB
TypeScript
237 lines
8.2 KiB
TypeScript
import { ExecArgs } from "@medusajs/framework/types"
|
|
import {
|
|
ContainerRegistrationKeys,
|
|
Modules,
|
|
} from "@medusajs/framework/utils"
|
|
import {
|
|
createShippingOptionsWorkflow,
|
|
} from "@medusajs/medusa/core-flows"
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// All supported countries (must match seed.ts)
|
|
// ---------------------------------------------------------------------------
|
|
// prettier-ignore
|
|
const allCountries = [
|
|
// Europe
|
|
"al", "ad", "at", "be", "ba", "bg", "hr", "cy", "cz", "dk",
|
|
"ee", "fi", "fr", "de", "gr", "hu", "is", "ie", "it", "xk",
|
|
"lv", "li", "lt", "lu", "mk", "mt", "md", "mc", "me", "nl",
|
|
"no", "pl", "pt", "ro", "sm", "rs", "sk", "si", "es", "se",
|
|
"ch", "ua", "gb", "va",
|
|
// North America
|
|
"us", "ca", "mx",
|
|
// Central America & Caribbean
|
|
"bz", "cr", "sv", "gt", "hn", "ni", "pa",
|
|
"ag", "bs", "bb", "cu", "dm", "do", "gd", "ht", "jm", "kn",
|
|
"lc", "vc", "tt",
|
|
// South America
|
|
"ar", "bo", "br", "cl", "co", "ec", "gy", "py", "pe", "sr",
|
|
"uy", "ve",
|
|
// East Asia & Pacific
|
|
"au", "bn", "kh", "cn", "fj", "hk", "id", "jp", "kr", "la",
|
|
"mo", "my", "mn", "mm", "nz", "pg", "ph", "sg", "tw", "th",
|
|
"tl", "vn",
|
|
// South Asia
|
|
"af", "bd", "bt", "in", "mv", "np", "pk", "lk",
|
|
// Central Asia
|
|
"kz", "kg", "tj", "tm", "uz",
|
|
// Middle East (excluding IR)
|
|
"bh", "iq", "il", "jo", "kw", "lb", "om", "ps", "qa", "sa",
|
|
"ae", "ye",
|
|
// Africa
|
|
"dz", "ao", "bj", "bw", "bf", "bi", "cv", "cm", "cf", "td",
|
|
"km", "cd", "cg", "ci", "dj", "eg", "gq", "er", "sz", "et",
|
|
"ga", "gm", "gh", "gn", "gw", "ke", "ls", "lr", "ly", "mg",
|
|
"mw", "ml", "mr", "mu", "ma", "mz", "na", "ne", "ng", "rw",
|
|
"st", "sn", "sc", "sl", "so", "za", "ss", "sd", "tz", "tg",
|
|
"tn", "ug", "zm", "zw",
|
|
]
|
|
|
|
// Shipping zones
|
|
const ZONE_NL = ["nl"]
|
|
// prettier-ignore
|
|
const ZONE_EU1 = [
|
|
"be", "dk", "de", "fr", "it", "lu", "at", "es", "se",
|
|
]
|
|
// prettier-ignore
|
|
const ZONE_EU2 = [
|
|
"bg", "ee", "fi", "hu", "ie", "hr", "lv", "lt",
|
|
"pl", "pt", "ro", "si", "sk", "cz", "gb", "ch",
|
|
]
|
|
const ZONE_ROW = allCountries.filter(
|
|
(cc) =>
|
|
!ZONE_NL.includes(cc) &&
|
|
!ZONE_EU1.includes(cc) &&
|
|
!ZONE_EU2.includes(cc)
|
|
)
|
|
|
|
// ===========================================================================
|
|
// Migration: update existing DB shipping to 4 zones
|
|
// ===========================================================================
|
|
export default async function updateShipping({ container }: ExecArgs) {
|
|
const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
|
|
const link = container.resolve(ContainerRegistrationKeys.LINK)
|
|
const fulfillmentModule = container.resolve(Modules.FULFILLMENT)
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 1. Find existing fulfillment set
|
|
// -------------------------------------------------------------------------
|
|
const fulfillmentSets = await fulfillmentModule.listFulfillmentSets(
|
|
{},
|
|
{ relations: ["service_zones", "service_zones.shipping_options"] }
|
|
)
|
|
|
|
if (fulfillmentSets.length === 0) {
|
|
logger.error("No fulfillment sets found. Run the seed script first.")
|
|
return
|
|
}
|
|
|
|
const existingSet = fulfillmentSets[0]
|
|
logger.info(`Found fulfillment set: "${existingSet.name}" (${existingSet.id})`)
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 2. Delete existing shipping options and service zones
|
|
// -------------------------------------------------------------------------
|
|
const existingShippingOptionIds = existingSet.service_zones.flatMap(
|
|
(zone) => (zone as any).shipping_options?.map((so: any) => so.id) ?? []
|
|
)
|
|
|
|
if (existingShippingOptionIds.length > 0) {
|
|
logger.info(
|
|
`Deleting ${existingShippingOptionIds.length} existing shipping option(s)...`
|
|
)
|
|
await fulfillmentModule.deleteShippingOptions(existingShippingOptionIds)
|
|
}
|
|
|
|
const existingZoneIds = existingSet.service_zones.map((z) => z.id)
|
|
if (existingZoneIds.length > 0) {
|
|
logger.info(
|
|
`Deleting ${existingZoneIds.length} existing service zone(s)...`
|
|
)
|
|
await fulfillmentModule.deleteServiceZones(existingZoneIds)
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 3. Create new service zones on the existing fulfillment set
|
|
// -------------------------------------------------------------------------
|
|
const toGeoZones = (countries: string[]) =>
|
|
countries.map((country_code) => ({
|
|
country_code,
|
|
type: "country" as const,
|
|
}))
|
|
|
|
const newZones = await fulfillmentModule.createServiceZones([
|
|
{
|
|
name: "Netherlands",
|
|
fulfillment_set_id: existingSet.id,
|
|
geo_zones: toGeoZones(ZONE_NL),
|
|
},
|
|
{
|
|
name: "EU-1",
|
|
fulfillment_set_id: existingSet.id,
|
|
geo_zones: toGeoZones(ZONE_EU1),
|
|
},
|
|
{
|
|
name: "EU-2",
|
|
fulfillment_set_id: existingSet.id,
|
|
geo_zones: toGeoZones(ZONE_EU2),
|
|
},
|
|
{
|
|
name: "Rest of the World",
|
|
fulfillment_set_id: existingSet.id,
|
|
geo_zones: toGeoZones(ZONE_ROW),
|
|
},
|
|
])
|
|
|
|
logger.info(`Created ${newZones.length} service zones.`)
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 4. Get shipping profile and region for the new shipping options
|
|
// -------------------------------------------------------------------------
|
|
const shippingProfiles =
|
|
await fulfillmentModule.listShippingProfiles({ type: "default" })
|
|
const shippingProfile = shippingProfiles[0]
|
|
if (!shippingProfile) {
|
|
logger.error("No default shipping profile found.")
|
|
return
|
|
}
|
|
|
|
const regionModule = container.resolve(Modules.REGION)
|
|
const regions = await regionModule.listRegions({ name: "Worldwide" })
|
|
const worldwideRegion = regions[0]
|
|
if (!worldwideRegion) {
|
|
logger.error("Worldwide region not found.")
|
|
return
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 5. Create new shipping options (2 per zone)
|
|
// -------------------------------------------------------------------------
|
|
const findZone = (name: string) => {
|
|
const zone = newZones.find((z) => z.name === name)
|
|
if (!zone) throw new Error(`Service zone "${name}" not found`)
|
|
return zone
|
|
}
|
|
|
|
const SHIPPING_ZONES = [
|
|
{ zone: findZone("Netherlands"), mailboxPrice: 0, packagePrice: 0 },
|
|
{ zone: findZone("EU-1"), mailboxPrice: 700, packagePrice: 900 },
|
|
{ zone: findZone("EU-2"), mailboxPrice: 800, packagePrice: 1200 },
|
|
{ zone: findZone("Rest of the World"), mailboxPrice: 1000, packagePrice: 2200 },
|
|
]
|
|
|
|
const shippingOptionInputs = SHIPPING_ZONES.flatMap(
|
|
({ zone, mailboxPrice, packagePrice }) => [
|
|
{
|
|
name: `Mailbox Package — ${zone.name}`,
|
|
price_type: "flat" as const,
|
|
provider_id: "manual_manual",
|
|
service_zone_id: zone.id,
|
|
shipping_profile_id: shippingProfile.id,
|
|
type: {
|
|
label: "Mailbox Package",
|
|
description:
|
|
"For orders with CDs/SACDs only (up to 4 items). Fits through the mailbox.",
|
|
code: "mailbox-package",
|
|
},
|
|
prices: [
|
|
{ currency_code: "eur", amount: mailboxPrice },
|
|
{ region_id: worldwideRegion.id, amount: mailboxPrice },
|
|
],
|
|
rules: [
|
|
{ attribute: "enabled_in_store", value: "true", operator: "eq" as const },
|
|
{ attribute: "is_return", value: "false", operator: "eq" as const },
|
|
],
|
|
},
|
|
{
|
|
name: `Package — ${zone.name}`,
|
|
price_type: "flat" as const,
|
|
provider_id: "manual_manual",
|
|
service_zone_id: zone.id,
|
|
shipping_profile_id: shippingProfile.id,
|
|
type: {
|
|
label: "Package",
|
|
description:
|
|
"For orders with LPs, 5+ CDs/SACDs, or mixed physical formats.",
|
|
code: "package",
|
|
},
|
|
prices: [
|
|
{ currency_code: "eur", amount: packagePrice },
|
|
{ region_id: worldwideRegion.id, amount: packagePrice },
|
|
],
|
|
rules: [
|
|
{ attribute: "enabled_in_store", value: "true", operator: "eq" as const },
|
|
{ attribute: "is_return", value: "false", operator: "eq" as const },
|
|
],
|
|
},
|
|
]
|
|
)
|
|
|
|
await createShippingOptionsWorkflow(container).run({
|
|
input: shippingOptionInputs,
|
|
})
|
|
|
|
logger.info(
|
|
`Shipping migration complete: 4 zones, ${shippingOptionInputs.length} shipping options created.`
|
|
)
|
|
}
|