import { CreateInventoryLevelInput, ExecArgs } from "@medusajs/framework/types" import { ContainerRegistrationKeys, Modules, ProductStatus, } from "@medusajs/framework/utils" import { createWorkflow, transform, WorkflowResponse, } from "@medusajs/framework/workflows-sdk" import { createApiKeysWorkflow, createInventoryLevelsWorkflow, createProductsWorkflow, createRegionsWorkflow, createSalesChannelsWorkflow, createShippingOptionsWorkflow, createShippingProfilesWorkflow, createStockLocationsWorkflow, createTaxRegionsWorkflow, linkSalesChannelsToApiKeyWorkflow, linkSalesChannelsToStockLocationWorkflow, updateStoresStep, updateStoresWorkflow, } from "@medusajs/medusa/core-flows" import { ApiKey } from "../../.medusa/types/query-entry-points" import { ALL_FORMATS, buildVariant } from "./trptk-formats" // --------------------------------------------------------------------------- // Workflow: update store currencies // --------------------------------------------------------------------------- const updateStoreCurrencies = createWorkflow( "update-store-currencies", (input: { supported_currencies: { currency_code: string is_default?: boolean }[] store_id: string }) => { const normalizedInput = transform({ input }, (data) => { return { selector: { id: data.input.store_id }, update: { supported_currencies: data.input.supported_currencies.map( (currency) => ({ currency_code: currency.currency_code, is_default: currency.is_default ?? false, }) ), }, } }) const stores = updateStoresStep(normalizedInput) return new WorkflowResponse(stores) } ) // --------------------------------------------------------------------------- // VAT rates for countries where we collect tax (standard rates as of 2025) // Countries not listed here get 0% tax automatically. // --------------------------------------------------------------------------- const VAT_RATES: Record = { // EU member states nl: 21, de: 19, fr: 20, it: 22, es: 21, be: 21, at: 20, pl: 23, cz: 21, dk: 25, se: 25, fi: 25.5, ie: 23, pt: 23, bg: 20, hr: 21, cy: 19, ee: 22, gr: 24, hu: 27, lv: 21, lt: 21, lu: 17, mt: 18, ro: 19, sk: 23, si: 22, // Non-EU European gb: 20, ch: 8.1, no: 25, } // --------------------------------------------------------------------------- // All supported countries (worldwide except RU and IR) // --------------------------------------------------------------------------- // 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 — country codes grouped by shipping cost tier // --------------------------------------------------------------------------- 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) ) // =========================================================================== // Main seed function // =========================================================================== export default async function seedTRPTKData({ container }: ExecArgs) { const logger = container.resolve(ContainerRegistrationKeys.LOGGER) const link = container.resolve(ContainerRegistrationKeys.LINK) const query = container.resolve(ContainerRegistrationKeys.QUERY) const fulfillmentModuleService = container.resolve(Modules.FULFILLMENT) const salesChannelModuleService = container.resolve(Modules.SALES_CHANNEL) const storeModuleService = container.resolve(Modules.STORE) // ----------------------------------------------------------------------- // 1. Store settings // ----------------------------------------------------------------------- logger.info("Setting up TRPTK store...") const [store] = await storeModuleService.listStores() let defaultSalesChannel = await salesChannelModuleService.listSalesChannels({ name: "Default Sales Channel", }) if (!defaultSalesChannel.length) { const { result: salesChannelResult } = await createSalesChannelsWorkflow( container ).run({ input: { salesChannelsData: [ { name: "Default Sales Channel", }, ], }, }) defaultSalesChannel = salesChannelResult } await updateStoreCurrencies(container).run({ input: { store_id: store.id, supported_currencies: [ { currency_code: "eur", is_default: true, }, ], }, }) await updateStoresWorkflow(container).run({ input: { selector: { id: store.id }, update: { name: "TRPTK", default_sales_channel_id: defaultSalesChannel[0].id, }, }, }) logger.info("Store configured: TRPTK, EUR (tax-inclusive).") // ----------------------------------------------------------------------- // 2. Region (single Worldwide region, EUR, tax-inclusive) // ----------------------------------------------------------------------- logger.info("Seeding region...") const { result: regionResult } = await createRegionsWorkflow(container).run({ input: { regions: [ { name: "Worldwide", currency_code: "eur", countries: allCountries, is_tax_inclusive: true, payment_providers: ["pp_system_default"], }, ], }, }) const worldwideRegion = regionResult[0] logger.info("Region created: Worldwide.") // ----------------------------------------------------------------------- // 3. Tax regions with VAT rates (only for countries that collect tax) // ----------------------------------------------------------------------- logger.info("Seeding tax regions...") const vatCountries = Object.keys(VAT_RATES) await createTaxRegionsWorkflow(container).run({ input: vatCountries.map((country_code) => ({ country_code, provider_id: "tp_system", })), }) // Set default tax rates per country const taxModule = container.resolve(Modules.TAX) for (const [countryCode, rate] of Object.entries(VAT_RATES)) { // Find the tax region for this country const taxRegions = await taxModule.listTaxRegions({ country_code: countryCode, }) if (taxRegions.length > 0) { await taxModule.createTaxRates({ tax_region_id: taxRegions[0].id, name: `VAT ${countryCode.toUpperCase()} ${rate}%`, code: `vat-${countryCode}`, rate, is_default: true, }) } } logger.info("Tax regions and rates created.") // ----------------------------------------------------------------------- // 4. Stock location // ----------------------------------------------------------------------- logger.info("Seeding stock location...") const { result: stockLocationResult } = await createStockLocationsWorkflow( container ).run({ input: { locations: [ { name: "TRPTK Warehouse", address: { city: "Amsterdam", country_code: "NL", address_1: "", }, }, ], }, }) const stockLocation = stockLocationResult[0] await updateStoresWorkflow(container).run({ input: { selector: { id: store.id }, update: { default_location_id: stockLocation.id, }, }, }) await link.create({ [Modules.STOCK_LOCATION]: { stock_location_id: stockLocation.id, }, [Modules.FULFILLMENT]: { fulfillment_provider_id: "manual_manual", }, }) logger.info("Stock location created: TRPTK Warehouse.") // ----------------------------------------------------------------------- // 5. Fulfillment & shipping // ----------------------------------------------------------------------- logger.info("Seeding fulfillment...") const shippingProfiles = await fulfillmentModuleService.listShippingProfiles({ type: "default" }) let shippingProfile = shippingProfiles.length ? shippingProfiles[0] : null if (!shippingProfile) { const { result: shippingProfileResult } = await createShippingProfilesWorkflow(container).run({ input: { data: [ { name: "Default Shipping Profile", type: "default", }, ], }, }) shippingProfile = shippingProfileResult[0] } const toGeoZones = (countries: string[]) => countries.map((country_code) => ({ country_code, type: "country" as const, })) const fulfillmentSet = await fulfillmentModuleService.createFulfillmentSets({ name: "TRPTK Shipping", type: "shipping", service_zones: [ { name: "Netherlands", geo_zones: toGeoZones(ZONE_NL) }, { name: "EU-1", geo_zones: toGeoZones(ZONE_EU1) }, { name: "EU-2", geo_zones: toGeoZones(ZONE_EU2) }, { name: "Rest of the World", geo_zones: toGeoZones(ZONE_ROW) }, ], }) await link.create({ [Modules.STOCK_LOCATION]: { stock_location_id: stockLocation.id, }, [Modules.FULFILLMENT]: { fulfillment_set_id: fulfillmentSet.id, }, }) // Shipping options: 2 per zone (Mailbox Package + Package) const findZone = (name: string) => { const zone = fulfillmentSet.service_zones.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(`Fulfillment configured: 4 zones, ${shippingOptionInputs.length} shipping options.`) // Link sales channel to stock location await linkSalesChannelsToStockLocationWorkflow(container).run({ input: { id: stockLocation.id, add: [defaultSalesChannel[0].id], }, }) // ----------------------------------------------------------------------- // 6. Publishable API key // ----------------------------------------------------------------------- logger.info("Seeding API key...") let publishableApiKey: ApiKey | null = null const { data } = await query.graph({ entity: "api_key", fields: ["id"], filters: { type: "publishable", }, }) publishableApiKey = data?.[0] if (!publishableApiKey) { const { result: [publishableApiKeyResult], } = await createApiKeysWorkflow(container).run({ input: { api_keys: [ { title: "TRPTK Webshop", type: "publishable", created_by: "", }, ], }, }) publishableApiKey = publishableApiKeyResult as ApiKey } await linkSalesChannelsToApiKeyWorkflow(container).run({ input: { id: publishableApiKey.id, add: [defaultSalesChannel[0].id], }, }) logger.info("API key configured.") // ----------------------------------------------------------------------- // 7. Sample product: TTK0001 // ----------------------------------------------------------------------- logger.info("Seeding sample product...") await createProductsWorkflow(container).run({ input: { products: [ { title: "Sample Release — TTK0001", description: "A sample TRPTK release to demonstrate the product structure. Replace with real catalogue data.", handle: "ttk0001", status: ProductStatus.PUBLISHED, external_id: "TTK0001", shipping_profile_id: shippingProfile!.id, metadata: { ean: "0608917722017", catalogue_number: "TTK0001", }, options: [ { title: "Format", values: ALL_FORMATS, }, ], variants: ALL_FORMATS.map((format) => buildVariant("TTK0001", format)), sales_channels: [ { id: defaultSalesChannel[0].id, }, ], }, ], }, }) logger.info("Sample product created: TTK0001.") // ----------------------------------------------------------------------- // 8. Inventory levels (physical variants only) // ----------------------------------------------------------------------- logger.info("Seeding inventory levels...") const { data: inventoryItems } = await query.graph({ entity: "inventory_item", fields: ["id"], }) const inventoryLevels: CreateInventoryLevelInput[] = inventoryItems.map( (item) => ({ location_id: stockLocation.id, stocked_quantity: 100, inventory_item_id: item.id, }) ) if (inventoryLevels.length) { await createInventoryLevelsWorkflow(container).run({ input: { inventory_levels: inventoryLevels, }, }) } logger.info("Inventory levels set.") logger.info("TRPTK seed complete!") }