trptk-medusa/src/scripts/update-shipping.ts
2026-02-24 15:06:43 +01:00

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.`
)
}