Feat(fulfillment): service provider registration + fulfillment management (#6524)
**What** - Create the fulfillment manual package with a first iteration API - Create a new `AbstractFulfillmentProviderService` and `IFulfillmentProvider` - Modify the module service interface to add new methods to manipulate the fulfillment and the communication with the external provider - create (no bulk) - cancel (no bulk) - update (no bulk) - list - listAndCount - retrieve - Add new methods to the service provider service to include communication with the third party provider - get options - create - cancel - validate data - validate option - Update/create interfaces and DTO's - fix repository serializer to allow non entity to be passed without throwing - split module tests into multiple files to simplify navigation - Add integration tests to validate fulfillments manipulation and external provider loading + communication FIXES CORE-1729 FIXES CORE-1785 FIXES CORE-1784 FIXES CORE-1766
This commit is contained in:
1 parent
f9ef37a2f2
commit
62a7bcc30c
55 files changed
+4066
-2749
No files matched your search
@@ -0,0 +1,87 @@
|
||||
import { moduleProviderLoader } from "@medusajs/modules-sdk"
|
||||
import { LoaderOptions, ModuleProvider, ModulesSdkTypes } from "@medusajs/types"
|
||||
import { asFunction, asValue, Lifetime } from "awilix"
|
||||
import { FulfillmentIdentifiersRegistrationName } from "@types"
|
||||
import { lowerCaseFirst } from "@medusajs/utils"
|
||||
import { FulfillmentProviderService } from "@services"
|
||||
import { ContainerRegistrationKeys } from "@medusajs/utils/src"
|
||||
|
||||
const registrationFn = async (klass, container, pluginOptions) => {
|
||||
Object.entries(pluginOptions.config || []).map(([name, config]) => {
|
||||
const key = FulfillmentProviderService.getRegistrationIdentifier(
|
||||
klass,
|
||||
name
|
||||
)
|
||||
|
||||
container.register({
|
||||
["fp_" + key]: asFunction((cradle) => new klass(cradle, config), {
|
||||
lifetime: klass.LIFE_TIME || Lifetime.SINGLETON,
|
||||
}),
|
||||
})
|
||||
|
||||
container.registerAdd(FulfillmentIdentifiersRegistrationName, asValue(key))
|
||||
})
|
||||
}
|
||||
|
||||
export default async ({
|
||||
container,
|
||||
options,
|
||||
}: LoaderOptions<
|
||||
(
|
||||
| ModulesSdkTypes.ModuleServiceInitializeOptions
|
||||
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
|
||||
) & { providers: ModuleProvider[] }
|
||||
>): Promise<void> => {
|
||||
container.registerAdd(
|
||||
FulfillmentIdentifiersRegistrationName,
|
||||
asValue(undefined)
|
||||
)
|
||||
|
||||
// Local providers
|
||||
// TODO
|
||||
|
||||
await moduleProviderLoader({
|
||||
container,
|
||||
providers: options?.providers || [],
|
||||
registerServiceFn: registrationFn,
|
||||
})
|
||||
|
||||
await syncDatabaseProviders({
|
||||
container,
|
||||
})
|
||||
}
|
||||
|
||||
async function syncDatabaseProviders({ container }) {
|
||||
const providerServiceRegistrationKey = lowerCaseFirst(
|
||||
FulfillmentProviderService.name
|
||||
)
|
||||
|
||||
const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
|
||||
try {
|
||||
const providerIdentifiers: string[] = (
|
||||
container.resolve(FulfillmentIdentifiersRegistrationName) ?? []
|
||||
).filter(Boolean)
|
||||
|
||||
const providerService: ModulesSdkTypes.InternalModuleService<any> =
|
||||
container.resolve(providerServiceRegistrationKey)
|
||||
|
||||
const providers = await providerService.list({
|
||||
id: providerIdentifiers,
|
||||
})
|
||||
|
||||
const loadedProvidersMap = new Map(providers.map((p) => [p.id, p]))
|
||||
|
||||
const providersToCreate: any[] = []
|
||||
for (const identifier of providerIdentifiers) {
|
||||
if (loadedProvidersMap.has(identifier)) {
|
||||
continue
|
||||
}
|
||||
|
||||
providersToCreate.push({ id: identifier })
|
||||
}
|
||||
|
||||
await providerService.create(providersToCreate)
|
||||
} catch (error) {
|
||||
logger.error(`Error syncing providers: ${error.message}`)
|
||||
}
|
||||
}
|
||||
@@ -15,15 +15,6 @@
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"fulfillment_id": {
|
||||
"name": "fulfillment_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"company": {
|
||||
"name": "company",
|
||||
"type": "text",
|
||||
@@ -159,16 +150,6 @@
|
||||
"name": "fulfillment_address",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "IDX_fulfillment_address_fulfillment_id",
|
||||
"columnNames": [
|
||||
"fulfillment_id"
|
||||
],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_fulfillment_address_fulfillment_id\" ON \"fulfillment_address\" (fulfillment_id) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_fulfillment_address_deleted_at",
|
||||
"columnNames": [
|
||||
@@ -192,6 +173,85 @@
|
||||
"checks": [],
|
||||
"foreignKeys": {}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"metadata": {
|
||||
"name": "metadata",
|
||||
"type": "jsonb",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "json"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
}
|
||||
},
|
||||
"name": "fulfillment_provider",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "IDX_fulfillment_provider_deleted_at",
|
||||
"columnNames": [
|
||||
"deleted_at"
|
||||
],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_fulfillment_provider_deleted_at\" ON \"fulfillment_provider\" (deleted_at) WHERE deleted_at IS NOT NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "fulfillment_provider_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
"id": {
|
||||
@@ -299,85 +359,6 @@
|
||||
"checks": [],
|
||||
"foreignKeys": {}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"metadata": {
|
||||
"name": "metadata",
|
||||
"type": "jsonb",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "json"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
}
|
||||
},
|
||||
"name": "service_provider",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "IDX_service_provider_deleted_at",
|
||||
"columnNames": [
|
||||
"deleted_at"
|
||||
],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_service_provider_deleted_at\" ON \"service_provider\" (deleted_at) WHERE deleted_at IS NOT NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "service_provider_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
"id": {
|
||||
@@ -957,8 +938,8 @@
|
||||
"nullable": true,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"service_provider_id": {
|
||||
"name": "service_provider_id",
|
||||
"fulfillment_provider_id": {
|
||||
"name": "fulfillment_provider_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
@@ -1059,14 +1040,14 @@
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_shipping_option_shipping_profile_id\" ON \"shipping_option\" (shipping_profile_id) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_shipping_option_service_provider_id",
|
||||
"keyName": "IDX_shipping_option_fulfillment_provider_id",
|
||||
"columnNames": [
|
||||
"service_provider_id"
|
||||
"fulfillment_provider_id"
|
||||
],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_shipping_option_service_provider_id\" ON \"shipping_option\" (service_provider_id) WHERE deleted_at IS NULL"
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_shipping_option_fulfillment_provider_id\" ON \"shipping_option\" (fulfillment_provider_id) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_shipping_option_shipping_option_type_id",
|
||||
@@ -1125,16 +1106,16 @@
|
||||
"deleteRule": "set null",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"shipping_option_service_provider_id_foreign": {
|
||||
"constraintName": "shipping_option_service_provider_id_foreign",
|
||||
"shipping_option_fulfillment_provider_id_foreign": {
|
||||
"constraintName": "shipping_option_fulfillment_provider_id_foreign",
|
||||
"columnNames": [
|
||||
"service_provider_id"
|
||||
"fulfillment_provider_id"
|
||||
],
|
||||
"localTableName": "public.shipping_option",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedTableName": "public.service_provider",
|
||||
"referencedTableName": "public.fulfillment_provider",
|
||||
"deleteRule": "set null",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
@@ -1397,15 +1378,6 @@
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"items_id": {
|
||||
"name": "items_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
@@ -1442,6 +1414,15 @@
|
||||
"name": "fulfillment",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": [
|
||||
"delivery_address_id"
|
||||
],
|
||||
"composite": false,
|
||||
"keyName": "fulfillment_delivery_address_id_unique",
|
||||
"primary": false,
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_fulfillment_location_id",
|
||||
"columnNames": [
|
||||
@@ -1494,6 +1475,18 @@
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {
|
||||
"fulfillment_provider_id_foreign": {
|
||||
"constraintName": "fulfillment_provider_id_foreign",
|
||||
"columnNames": [
|
||||
"provider_id"
|
||||
],
|
||||
"localTableName": "public.fulfillment",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedTableName": "public.fulfillment_provider",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"fulfillment_shipping_option_id_foreign": {
|
||||
"constraintName": "fulfillment_shipping_option_id_foreign",
|
||||
"columnNames": [
|
||||
@@ -1507,18 +1500,6 @@
|
||||
"deleteRule": "set null",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"fulfillment_provider_id_foreign": {
|
||||
"constraintName": "fulfillment_provider_id_foreign",
|
||||
"columnNames": [
|
||||
"provider_id"
|
||||
],
|
||||
"localTableName": "public.fulfillment",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedTableName": "public.service_provider",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"fulfillment_delivery_address_id_foreign": {
|
||||
"constraintName": "fulfillment_delivery_address_id_foreign",
|
||||
"columnNames": [
|
||||
@@ -1530,18 +1511,6 @@
|
||||
],
|
||||
"referencedTableName": "public.fulfillment_address",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"fulfillment_items_id_foreign": {
|
||||
"constraintName": "fulfillment_items_id_foreign",
|
||||
"columnNames": [
|
||||
"items_id"
|
||||
],
|
||||
"localTableName": "public.fulfillment",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedTableName": "public.fulfillment_item",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1670,6 +1639,7 @@
|
||||
"id"
|
||||
],
|
||||
"referencedTableName": "public.fulfillment",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
}
|
||||
@@ -1721,6 +1691,15 @@
|
||||
"nullable": false,
|
||||
"mappedType": "decimal"
|
||||
},
|
||||
"raw_quantity": {
|
||||
"name": "raw_quantity",
|
||||
"type": "jsonb",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "json"
|
||||
},
|
||||
"line_item_id": {
|
||||
"name": "line_item_id",
|
||||
"type": "text",
|
||||
@@ -1846,6 +1825,7 @@
|
||||
"id"
|
||||
],
|
||||
"referencedTableName": "public.fulfillment",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
}
|
||||
|
||||
+14
-15
@@ -1,19 +1,18 @@
|
||||
import { Migration } from '@mikro-orm/migrations';
|
||||
|
||||
export class Migration20240221164918_InitialSetupMigration extends Migration {
|
||||
export class Migration20240305095931_InitialSetupMigration extends Migration {
|
||||
|
||||
async up(): Promise<void> {
|
||||
this.addSql('create table if not exists "fulfillment_address" ("id" text not null, "fulfillment_id" text null, "company" text null, "first_name" text null, "last_name" text null, "address_1" text null, "address_2" text null, "city" text null, "country_code" text null, "province" text null, "postal_code" text null, "phone" text null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_address_pkey" primary key ("id"));');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_address_fulfillment_id" ON "fulfillment_address" (fulfillment_id) WHERE deleted_at IS NULL;');
|
||||
this.addSql('create table if not exists "fulfillment_address" ("id" text not null, "company" text null, "first_name" text null, "last_name" text null, "address_1" text null, "address_2" text null, "city" text null, "country_code" text null, "province" text null, "postal_code" text null, "phone" text null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_address_pkey" primary key ("id"));');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_address_deleted_at" ON "fulfillment_address" (deleted_at) WHERE deleted_at IS NOT NULL;');
|
||||
|
||||
this.addSql('create table if not exists "fulfillment_provider" ("id" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_provider_pkey" primary key ("id"));');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_provider_deleted_at" ON "fulfillment_provider" (deleted_at) WHERE deleted_at IS NOT NULL;');
|
||||
|
||||
this.addSql('create table if not exists "fulfillment_set" ("id" text not null, "name" text not null, "type" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_set_pkey" primary key ("id"));');
|
||||
this.addSql('CREATE UNIQUE INDEX IF NOT EXISTS "IDX_fulfillment_set_name_unique" ON "fulfillment_set" (name) WHERE deleted_at IS NULL;');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_set_deleted_at" ON "fulfillment_set" (deleted_at) WHERE deleted_at IS NOT NULL;');
|
||||
|
||||
this.addSql('create table if not exists "service_provider" ("id" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "service_provider_pkey" primary key ("id"));');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_service_provider_deleted_at" ON "service_provider" (deleted_at) WHERE deleted_at IS NOT NULL;');
|
||||
|
||||
this.addSql('create table if not exists "service_zone" ("id" text not null, "name" text not null, "metadata" jsonb null, "fulfillment_set_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "service_zone_pkey" primary key ("id"));');
|
||||
this.addSql('CREATE UNIQUE INDEX IF NOT EXISTS "IDX_service_zone_name_unique" ON "service_zone" (name) WHERE deleted_at IS NULL;');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_service_zone_fulfillment_set_id" ON "service_zone" (fulfillment_set_id) WHERE deleted_at IS NULL;');
|
||||
@@ -33,11 +32,11 @@ export class Migration20240221164918_InitialSetupMigration extends Migration {
|
||||
this.addSql('CREATE UNIQUE INDEX IF NOT EXISTS "IDX_shipping_profile_name_unique" ON "shipping_profile" (name) WHERE deleted_at IS NULL;');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_profile_deleted_at" ON "shipping_profile" (deleted_at) WHERE deleted_at IS NOT NULL;');
|
||||
|
||||
this.addSql('create table if not exists "shipping_option" ("id" text not null, "name" text not null, "price_type" text check ("price_type" in (\'calculated\', \'flat\')) not null default \'calculated\', "service_zone_id" text not null, "shipping_profile_id" text null, "service_provider_id" text null, "data" jsonb null, "metadata" jsonb null, "shipping_option_type_id" text null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_pkey" primary key ("id"));');
|
||||
this.addSql('create table if not exists "shipping_option" ("id" text not null, "name" text not null, "price_type" text check ("price_type" in (\'calculated\', \'flat\')) not null default \'calculated\', "service_zone_id" text not null, "shipping_profile_id" text null, "fulfillment_provider_id" text null, "data" jsonb null, "metadata" jsonb null, "shipping_option_type_id" text null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_pkey" primary key ("id"));');
|
||||
this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_shipping_option_type_id_unique" unique ("shipping_option_type_id");');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_service_zone_id" ON "shipping_option" (service_zone_id) WHERE deleted_at IS NULL;');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_shipping_profile_id" ON "shipping_option" (shipping_profile_id) WHERE deleted_at IS NULL;');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_service_provider_id" ON "shipping_option" (service_provider_id) WHERE deleted_at IS NULL;');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_fulfillment_provider_id" ON "shipping_option" (fulfillment_provider_id) WHERE deleted_at IS NULL;');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_shipping_option_type_id" ON "shipping_option" (shipping_option_type_id) WHERE deleted_at IS NULL;');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_deleted_at" ON "shipping_option" (deleted_at) WHERE deleted_at IS NOT NULL;');
|
||||
|
||||
@@ -45,7 +44,8 @@ export class Migration20240221164918_InitialSetupMigration extends Migration {
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_rule_shipping_option_id" ON "shipping_option_rule" (shipping_option_id) WHERE deleted_at IS NULL;');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_rule_deleted_at" ON "shipping_option_rule" (deleted_at) WHERE deleted_at IS NOT NULL;');
|
||||
|
||||
this.addSql('create table if not exists "fulfillment" ("id" text not null, "location_id" text not null, "packed_at" timestamptz null, "shipped_at" timestamptz null, "delivered_at" timestamptz null, "canceled_at" timestamptz null, "data" jsonb null, "provider_id" text not null, "shipping_option_id" text null, "metadata" jsonb null, "delivery_address_id" text not null, "items_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_pkey" primary key ("id"));');
|
||||
this.addSql('create table if not exists "fulfillment" ("id" text not null, "location_id" text not null, "packed_at" timestamptz null, "shipped_at" timestamptz null, "delivered_at" timestamptz null, "canceled_at" timestamptz null, "data" jsonb null, "provider_id" text not null, "shipping_option_id" text null, "metadata" jsonb null, "delivery_address_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_pkey" primary key ("id"));');
|
||||
this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_delivery_address_id_unique" unique ("delivery_address_id");');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_location_id" ON "fulfillment" (location_id) WHERE deleted_at IS NULL;');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_provider_id" ON "fulfillment" (provider_id) WHERE deleted_at IS NULL;');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_shipping_option_id" ON "fulfillment" (shipping_option_id) WHERE deleted_at IS NULL;');
|
||||
@@ -55,7 +55,7 @@ export class Migration20240221164918_InitialSetupMigration extends Migration {
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_label_fulfillment_id" ON "fulfillment_label" (fulfillment_id) WHERE deleted_at IS NULL;');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_label_deleted_at" ON "fulfillment_label" (deleted_at) WHERE deleted_at IS NOT NULL;');
|
||||
|
||||
this.addSql('create table if not exists "fulfillment_item" ("id" text not null, "title" text not null, "sku" text not null, "barcode" text not null, "quantity" numeric not null, "line_item_id" text null, "inventory_item_id" text null, "fulfillment_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_item_pkey" primary key ("id"));');
|
||||
this.addSql('create table if not exists "fulfillment_item" ("id" text not null, "title" text not null, "sku" text not null, "barcode" text not null, "quantity" numeric not null, "raw_quantity" jsonb not null, "line_item_id" text null, "inventory_item_id" text null, "fulfillment_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_item_pkey" primary key ("id"));');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_line_item_id" ON "fulfillment_item" (line_item_id) WHERE deleted_at IS NULL;');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_inventory_item_id" ON "fulfillment_item" (inventory_item_id) WHERE deleted_at IS NULL;');
|
||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_fulfillment_id" ON "fulfillment_item" (fulfillment_id) WHERE deleted_at IS NULL;');
|
||||
@@ -67,19 +67,18 @@ export class Migration20240221164918_InitialSetupMigration extends Migration {
|
||||
|
||||
this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_service_zone_id_foreign" foreign key ("service_zone_id") references "service_zone" ("id") on update cascade;');
|
||||
this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_shipping_profile_id_foreign" foreign key ("shipping_profile_id") references "shipping_profile" ("id") on update cascade on delete set null;');
|
||||
this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_service_provider_id_foreign" foreign key ("service_provider_id") references "service_provider" ("id") on update cascade on delete set null;');
|
||||
this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_fulfillment_provider_id_foreign" foreign key ("fulfillment_provider_id") references "fulfillment_provider" ("id") on update cascade on delete set null;');
|
||||
this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_shipping_option_type_id_foreign" foreign key ("shipping_option_type_id") references "shipping_option_type" ("id") on update cascade on delete cascade;');
|
||||
|
||||
this.addSql('alter table if exists "shipping_option_rule" add constraint "shipping_option_rule_shipping_option_id_foreign" foreign key ("shipping_option_id") references "shipping_option" ("id") on update cascade;');
|
||||
|
||||
this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_provider_id_foreign" foreign key ("provider_id") references "fulfillment_provider" ("id") on update cascade;');
|
||||
this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_shipping_option_id_foreign" foreign key ("shipping_option_id") references "shipping_option" ("id") on update cascade on delete set null;');
|
||||
this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_provider_id_foreign" foreign key ("provider_id") references "service_provider" ("id") on update cascade;');
|
||||
this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_delivery_address_id_foreign" foreign key ("delivery_address_id") references "fulfillment_address" ("id") on update cascade;');
|
||||
this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_items_id_foreign" foreign key ("items_id") references "fulfillment_item" ("id") on update cascade;');
|
||||
|
||||
this.addSql('alter table if exists "fulfillment_label" add constraint "fulfillment_label_fulfillment_id_foreign" foreign key ("fulfillment_id") references "fulfillment" ("id") on update cascade;');
|
||||
this.addSql('alter table if exists "fulfillment_label" add constraint "fulfillment_label_fulfillment_id_foreign" foreign key ("fulfillment_id") references "fulfillment" ("id") on update cascade on delete cascade;');
|
||||
|
||||
this.addSql('alter table if exists "fulfillment_item" add constraint "fulfillment_item_fulfillment_id_foreign" foreign key ("fulfillment_id") references "fulfillment" ("id") on update cascade;');
|
||||
this.addSql('alter table if exists "fulfillment_item" add constraint "fulfillment_item_fulfillment_id_foreign" foreign key ("fulfillment_id") references "fulfillment" ("id") on update cascade on delete cascade;');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,12 +14,6 @@ import {
|
||||
|
||||
type OptionalAddressProps = DAL.SoftDeletableEntityDateColumns
|
||||
|
||||
const FulfillmentIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "fulfillment_address",
|
||||
columns: "fulfillment_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
const FulfillmentDeletedAtIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "fulfillment_address",
|
||||
columns: "deleted_at",
|
||||
@@ -33,10 +27,6 @@ export default class Address {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id!: string
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
@FulfillmentIdIndex.MikroORMIndex()
|
||||
fulfillment_id: string | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
company: string | null = null
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import {
|
||||
BigNumber,
|
||||
createPsqlIndexStatementHelper,
|
||||
DALUtils,
|
||||
generateEntityId,
|
||||
MikroOrmBigNumberProperty,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import { DAL } from "@medusajs/types"
|
||||
import { BigNumberRawValue, DAL } from "@medusajs/types"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
@@ -60,8 +62,11 @@ export default class FulfillmentItem {
|
||||
@Property({ columnType: "text" })
|
||||
barcode: string
|
||||
|
||||
@Property({ columnType: "numeric", serializer: Number })
|
||||
quantity: number // TODO: probably allow big numbers here
|
||||
@MikroOrmBigNumberProperty()
|
||||
quantity: BigNumber | number
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_quantity: BigNumberRawValue
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
@LineItemIdIndex.MikroORMIndex()
|
||||
@@ -71,11 +76,16 @@ export default class FulfillmentItem {
|
||||
@InventoryItemIdIndex.MikroORMIndex()
|
||||
inventory_item_id: string | null = null
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
@ManyToOne(() => Fulfillment, {
|
||||
columnType: "text",
|
||||
mapToPk: true,
|
||||
fieldName: "fulfillment_id",
|
||||
onDelete: "cascade",
|
||||
})
|
||||
@FulfillmentIdIndex.MikroORMIndex()
|
||||
fulfillment_id: string
|
||||
|
||||
@ManyToOne(() => Fulfillment)
|
||||
@ManyToOne(() => Fulfillment, { persist: false })
|
||||
fulfillment: Fulfillment
|
||||
|
||||
@Property({
|
||||
@@ -100,10 +110,12 @@ export default class FulfillmentItem {
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "fulit")
|
||||
this.fulfillment_id ??= this.fulfillment.id
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "fulit")
|
||||
this.fulfillment_id ??= this.fulfillment.id
|
||||
}
|
||||
}
|
||||
@@ -48,11 +48,16 @@ export default class FulfillmentLabel {
|
||||
@Property({ columnType: "text" })
|
||||
label_url: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
@ManyToOne(() => Fulfillment, {
|
||||
columnType: "text",
|
||||
mapToPk: true,
|
||||
fieldName: "fulfillment_id",
|
||||
onDelete: "cascade",
|
||||
})
|
||||
@FulfillmentIdIndex.MikroORMIndex()
|
||||
fulfillment_id: string
|
||||
|
||||
@ManyToOne(() => Fulfillment)
|
||||
@ManyToOne(() => Fulfillment, { persist: false })
|
||||
fulfillment: Fulfillment
|
||||
|
||||
@Property({
|
||||
@@ -77,10 +82,12 @@ export default class FulfillmentLabel {
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "fulla")
|
||||
this.fulfillment_id ??= this.fulfillment.id
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "fulla")
|
||||
this.fulfillment_id ??= this.fulfillment.id
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -18,18 +18,18 @@ import {
|
||||
} from "@mikro-orm/core"
|
||||
import ShippingOption from "./shipping-option"
|
||||
|
||||
type ServiceProviderOptionalProps = DAL.SoftDeletableEntityDateColumns
|
||||
type FulfillmentProviderOptionalProps = DAL.SoftDeletableEntityDateColumns
|
||||
|
||||
const DeletedAtIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "service_provider",
|
||||
tableName: "fulfillment_provider",
|
||||
columns: "deleted_at",
|
||||
where: "deleted_at IS NOT NULL",
|
||||
})
|
||||
|
||||
@Entity()
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class ServiceProvider {
|
||||
[OptionalProps]?: ServiceProviderOptionalProps
|
||||
export default class FulfillmentProvider {
|
||||
[OptionalProps]?: FulfillmentProviderOptionalProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
@@ -39,7 +39,7 @@ export default class ServiceProvider {
|
||||
|
||||
@OneToMany(
|
||||
() => ShippingOption,
|
||||
(shippingOption) => shippingOption.service_provider
|
||||
(shippingOption) => shippingOption.fulfillment_provider
|
||||
)
|
||||
shipping_options = new Collection<ShippingOption>(this)
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
Filter,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
@@ -20,7 +21,7 @@ import {
|
||||
import Address from "./address"
|
||||
import FulfillmentItem from "./fulfillment-item"
|
||||
import FulfillmentLabel from "./fulfillment-label"
|
||||
import ServiceProvider from "./service-provider"
|
||||
import FulfillmentProvider from "./fulfillment-provider"
|
||||
import ShippingOption from "./shipping-option"
|
||||
|
||||
type FulfillmentOptionalProps = DAL.SoftDeletableEntityDateColumns
|
||||
@@ -88,28 +89,37 @@ export default class Fulfillment {
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
data: Record<string, unknown> | null = null
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
@ManyToOne(() => FulfillmentProvider, {
|
||||
columnType: "text",
|
||||
fieldName: "provider_id",
|
||||
mapToPk: true,
|
||||
})
|
||||
@FulfillmentProviderIdIndex.MikroORMIndex()
|
||||
provider_id: string
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
@ManyToOne(() => ShippingOption, {
|
||||
columnType: "text",
|
||||
fieldName: "shipping_option_id",
|
||||
nullable: true,
|
||||
mapToPk: true,
|
||||
})
|
||||
@FulfillmentShippingOptionIdIndex.MikroORMIndex()
|
||||
shipping_option_id: string | null = null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@ManyToOne(() => ShippingOption, { nullable: true })
|
||||
@ManyToOne(() => ShippingOption, { persist: false })
|
||||
shipping_option: ShippingOption | null
|
||||
|
||||
@ManyToOne(() => ServiceProvider)
|
||||
provider: ServiceProvider
|
||||
@ManyToOne(() => FulfillmentProvider, { persist: false })
|
||||
provider: FulfillmentProvider
|
||||
|
||||
@ManyToOne(() => Address)
|
||||
delivery_address: Address
|
||||
@OneToOne()
|
||||
delivery_address!: Address
|
||||
|
||||
@ManyToOne(() => FulfillmentItem)
|
||||
items: FulfillmentItem
|
||||
@OneToMany(() => FulfillmentItem, (item) => item.fulfillment)
|
||||
items = new Collection<FulfillmentItem>(this)
|
||||
|
||||
@OneToMany(() => FulfillmentLabel, (label) => label.fulfillment)
|
||||
labels = new Collection<FulfillmentLabel>(this)
|
||||
@@ -136,10 +146,12 @@ export default class Fulfillment {
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "ful")
|
||||
this.provider_id ??= this.provider.id
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "ful")
|
||||
this.provider_id ??= this.provider.id
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ export { default as GeoZone } from "./geo-zone"
|
||||
export { default as ServiceZone } from "./service-zone"
|
||||
export { default as FulfillmentItem } from "./fulfillment-item"
|
||||
export { default as FulfillmentLabel } from "./fulfillment-label"
|
||||
export { default as ServiceProvider } from "./service-provider"
|
||||
export { default as FulfillmentProvider } from "./fulfillment-provider"
|
||||
export { default as ShippingOption } from "./shipping-option"
|
||||
export { default as ShippingOptionType } from "./shipping-option-type"
|
||||
export { default as ShippingOptionRule } from "./shipping-option-rule"
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import Fulfillment from "./fulfillment"
|
||||
import ServiceProvider from "./service-provider"
|
||||
import FulfillmentProvider from "./fulfillment-provider"
|
||||
import ServiceZone from "./service-zone"
|
||||
import ShippingOptionRule from "./shipping-option-rule"
|
||||
import ShippingOptionType from "./shipping-option-type"
|
||||
@@ -48,9 +48,9 @@ const ShippingProfileIdIndex = createPsqlIndexStatementHelper({
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
const ServiceProviderIdIndex = createPsqlIndexStatementHelper({
|
||||
const FulfillmentProviderIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "shipping_option",
|
||||
columns: "service_provider_id",
|
||||
columns: "fulfillment_provider_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
@@ -94,14 +94,14 @@ export default class ShippingOption {
|
||||
@ShippingProfileIdIndex.MikroORMIndex()
|
||||
shipping_profile_id: string | null
|
||||
|
||||
@ManyToOne(() => ServiceProvider, {
|
||||
@ManyToOne(() => FulfillmentProvider, {
|
||||
type: "text",
|
||||
fieldName: "service_provider_id",
|
||||
fieldName: "fulfillment_provider_id",
|
||||
mapToPk: true,
|
||||
nullable: true,
|
||||
})
|
||||
@ServiceProviderIdIndex.MikroORMIndex()
|
||||
service_provider_id: string
|
||||
@FulfillmentProviderIdIndex.MikroORMIndex()
|
||||
fulfillment_provider_id: string
|
||||
|
||||
@Property({ columnType: "text", persist: false })
|
||||
@ShippingOptionTypeIdIndex.MikroORMIndex()
|
||||
@@ -121,10 +121,10 @@ export default class ShippingOption {
|
||||
})
|
||||
shipping_profile: ShippingProfile | null
|
||||
|
||||
@ManyToOne(() => ServiceProvider, {
|
||||
@ManyToOne(() => FulfillmentProvider, {
|
||||
persist: false,
|
||||
})
|
||||
service_provider: ServiceProvider | null
|
||||
fulfillment_provider: FulfillmentProvider | null
|
||||
|
||||
@OneToOne(() => ShippingOptionType, (so) => so.shipping_option, {
|
||||
owner: true,
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as Models from "@models"
|
||||
import * as ModuleModels from "@models"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import * as ModuleRepositories from "@repositories"
|
||||
import loadProviders from "./loaders/providers"
|
||||
|
||||
const migrationScriptOptions = {
|
||||
moduleName: Modules.FULFILLMENT,
|
||||
@@ -34,7 +35,7 @@ const connectionLoader = ModulesSdkUtils.mikroOrmConnectionLoaderFactory({
|
||||
})
|
||||
|
||||
const service = FulfillmentModuleService
|
||||
const loaders = [containerLoader, connectionLoader]
|
||||
const loaders = [containerLoader, connectionLoader, loadProviders]
|
||||
|
||||
export const moduleDefinition: ModuleExports = {
|
||||
service,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import {
|
||||
Context,
|
||||
DAL,
|
||||
FilterableFulfillmentSetProps,
|
||||
FilterableShippingOptionProps,
|
||||
FilterQuery,
|
||||
FindConfig,
|
||||
FulfillmentDTO,
|
||||
FulfillmentTypes,
|
||||
IFulfillmentModuleService,
|
||||
InternalModuleDeclaration,
|
||||
@@ -25,6 +27,7 @@ import {
|
||||
|
||||
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
||||
import {
|
||||
Fulfillment,
|
||||
FulfillmentSet,
|
||||
GeoZone,
|
||||
ServiceZone,
|
||||
@@ -34,6 +37,7 @@ import {
|
||||
ShippingProfile,
|
||||
} from "@models"
|
||||
import { isContextValid, validateRules } from "@utils"
|
||||
import FulfillmentProviderService from "./fulfillment-provider"
|
||||
|
||||
const generateMethodForModels = [
|
||||
ServiceZone,
|
||||
@@ -42,6 +46,7 @@ const generateMethodForModels = [
|
||||
ShippingProfile,
|
||||
ShippingOptionRule,
|
||||
ShippingOptionType,
|
||||
// Not adding Fulfillment to not auto generate the methods under the hood and only provide the methods we want to expose8
|
||||
]
|
||||
|
||||
type InjectedDependencies = {
|
||||
@@ -53,6 +58,8 @@ type InjectedDependencies = {
|
||||
shippingOptionService: ModulesSdkTypes.InternalModuleService<any>
|
||||
shippingOptionRuleService: ModulesSdkTypes.InternalModuleService<any>
|
||||
shippingOptionTypeService: ModulesSdkTypes.InternalModuleService<any>
|
||||
fulfillmentProviderService: FulfillmentProviderService
|
||||
fulfillmentService: ModulesSdkTypes.InternalModuleService<any>
|
||||
}
|
||||
|
||||
export default class FulfillmentModuleService<
|
||||
@@ -62,7 +69,8 @@ export default class FulfillmentModuleService<
|
||||
TShippingProfileEntity extends ShippingProfile = ShippingProfile,
|
||||
TShippingOptionEntity extends ShippingOption = ShippingOption,
|
||||
TShippingOptionRuleEntity extends ShippingOptionRule = ShippingOptionRule,
|
||||
TSippingOptionTypeEntity extends ShippingOptionType = ShippingOptionType
|
||||
TSippingOptionTypeEntity extends ShippingOptionType = ShippingOptionType,
|
||||
TFulfillmentEntity extends Fulfillment = Fulfillment
|
||||
>
|
||||
extends ModulesSdkUtils.abstractModuleServiceFactory<
|
||||
InjectedDependencies,
|
||||
@@ -87,6 +95,8 @@ export default class FulfillmentModuleService<
|
||||
protected readonly shippingOptionService_: ModulesSdkTypes.InternalModuleService<TShippingOptionEntity>
|
||||
protected readonly shippingOptionRuleService_: ModulesSdkTypes.InternalModuleService<TShippingOptionRuleEntity>
|
||||
protected readonly shippingOptionTypeService_: ModulesSdkTypes.InternalModuleService<TSippingOptionTypeEntity>
|
||||
protected readonly fulfillmentProviderService_: FulfillmentProviderService
|
||||
protected readonly fulfillmentService_: ModulesSdkTypes.InternalModuleService<TFulfillmentEntity>
|
||||
|
||||
constructor(
|
||||
{
|
||||
@@ -98,6 +108,8 @@ export default class FulfillmentModuleService<
|
||||
shippingOptionService,
|
||||
shippingOptionRuleService,
|
||||
shippingOptionTypeService,
|
||||
fulfillmentProviderService,
|
||||
fulfillmentService,
|
||||
}: InjectedDependencies,
|
||||
protected readonly moduleDeclaration: InternalModuleDeclaration
|
||||
) {
|
||||
@@ -111,6 +123,8 @@ export default class FulfillmentModuleService<
|
||||
this.shippingOptionService_ = shippingOptionService
|
||||
this.shippingOptionRuleService_ = shippingOptionRuleService
|
||||
this.shippingOptionTypeService_ = shippingOptionTypeService
|
||||
this.fulfillmentProviderService_ = fulfillmentProviderService
|
||||
this.fulfillmentService_ = fulfillmentService
|
||||
}
|
||||
|
||||
__joinerConfig(): ModuleJoinerConfig {
|
||||
@@ -129,7 +143,7 @@ export default class FulfillmentModuleService<
|
||||
"rules",
|
||||
"type",
|
||||
"shipping_profile",
|
||||
"service_provider",
|
||||
"fulfillment_provider",
|
||||
...(normalizedConfig.relations ?? []),
|
||||
]
|
||||
// The assumption is that there won't be an infinite amount of shipping options. So if a context filtering needs to be applied we can retrieve them all.
|
||||
@@ -173,7 +187,7 @@ export default class FulfillmentModuleService<
|
||||
async listShippingOptions(
|
||||
filters: FilterableShippingOptionProps = {},
|
||||
config: FindConfig<ShippingOptionDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<FulfillmentTypes.ShippingOptionDTO[]> {
|
||||
const {
|
||||
filters: normalizedFilters,
|
||||
@@ -211,6 +225,76 @@ export default class FulfillmentModuleService<
|
||||
})
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async retrieveFulfillment(
|
||||
id: string,
|
||||
config: FindConfig<FulfillmentTypes.FulfillmentDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<FulfillmentTypes.FulfillmentDTO> {
|
||||
const fulfillment = await this.fulfillmentService_.retrieve(
|
||||
id,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.baseRepository_.serialize<FulfillmentTypes.FulfillmentDTO>(
|
||||
fulfillment,
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async retrieveFulfillmentOptions(
|
||||
providerId: string
|
||||
): Promise<Record<string, any>[]> {
|
||||
return await this.fulfillmentProviderService_.getFulfillmentOptions(
|
||||
providerId
|
||||
)
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listFulfillments(
|
||||
filters: FulfillmentTypes.FilterableFulfillmentProps = {},
|
||||
config: FindConfig<FulfillmentTypes.FulfillmentDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<FulfillmentTypes.FulfillmentDTO[]> {
|
||||
const fulfillments = await this.fulfillmentService_.list(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.baseRepository_.serialize<
|
||||
FulfillmentTypes.FulfillmentDTO[]
|
||||
>(fulfillments, {
|
||||
populate: true,
|
||||
})
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listAndCountFulfillments(
|
||||
filters?: FilterableFulfillmentSetProps,
|
||||
config?: FindConfig<FulfillmentDTO>,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[FulfillmentDTO[], number]> {
|
||||
const [fulfillments, count] = await this.fulfillmentService_.listAndCount(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return [
|
||||
await this.baseRepository_.serialize<FulfillmentTypes.FulfillmentDTO[]>(
|
||||
fulfillments,
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
),
|
||||
count,
|
||||
]
|
||||
}
|
||||
|
||||
create(
|
||||
data: FulfillmentTypes.CreateFulfillmentSetDTO[],
|
||||
sharedContext?: Context
|
||||
@@ -504,6 +588,55 @@ export default class FulfillmentModuleService<
|
||||
: createdShippingOptionRules[0]
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async createFulfillment(
|
||||
data: FulfillmentTypes.CreateFulfillmentDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<FulfillmentTypes.FulfillmentDTO> {
|
||||
const { order, ...fulfillmentDataToCreate } = data
|
||||
|
||||
const fulfillment = await this.fulfillmentService_.create(
|
||||
fulfillmentDataToCreate,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const {
|
||||
items,
|
||||
data: fulfillmentData,
|
||||
provider_id,
|
||||
...fulfillmentRest
|
||||
} = fulfillment
|
||||
|
||||
let fulfillmentThirdPartyData!: any
|
||||
try {
|
||||
fulfillmentThirdPartyData =
|
||||
await this.fulfillmentProviderService_.createFulfillment(
|
||||
provider_id,
|
||||
fulfillmentData || {},
|
||||
items.map((i) => i),
|
||||
order,
|
||||
fulfillmentRest
|
||||
)
|
||||
await this.fulfillmentService_.update(
|
||||
{
|
||||
id: fulfillment.id,
|
||||
data: fulfillmentThirdPartyData ?? {},
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
} catch (error) {
|
||||
await this.fulfillmentService_.delete(fulfillment.id, sharedContext)
|
||||
throw error
|
||||
}
|
||||
|
||||
return await this.baseRepository_.serialize<FulfillmentTypes.FulfillmentDTO>(
|
||||
fulfillment,
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
update(
|
||||
data: FulfillmentTypes.UpdateFulfillmentSetDTO[],
|
||||
sharedContext?: Context
|
||||
@@ -1074,6 +1207,88 @@ export default class FulfillmentModuleService<
|
||||
: updatedShippingOptionRules[0]
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async updateFulfillment(
|
||||
id: string,
|
||||
data: FulfillmentTypes.UpdateFulfillmentDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<FulfillmentTypes.FulfillmentDTO> {
|
||||
const fulfillment = await this.fulfillmentService_.update(
|
||||
{ id, ...data },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const serialized =
|
||||
await this.baseRepository_.serialize<FulfillmentTypes.FulfillmentDTO>(
|
||||
fulfillment,
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
)
|
||||
|
||||
return Array.isArray(serialized) ? serialized[0] : serialized
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async cancelFulfillment(
|
||||
id: string,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<FulfillmentDTO> {
|
||||
const canceledAt = new Date()
|
||||
|
||||
let fulfillment = await this.fulfillmentService_.retrieve(
|
||||
id,
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
FulfillmentModuleService.canCancelFulfillmentOrThrow(fulfillment)
|
||||
|
||||
// Make this action idempotent
|
||||
if (!fulfillment.canceled_at) {
|
||||
try {
|
||||
await this.fulfillmentProviderService_.cancelFulfillment(
|
||||
fulfillment.provider_id,
|
||||
fulfillment.data ?? {}
|
||||
)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
|
||||
fulfillment = await this.fulfillmentService_.update(
|
||||
{
|
||||
id,
|
||||
canceled_at: canceledAt,
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
const result = await this.baseRepository_.serialize(fulfillment, {
|
||||
populate: true,
|
||||
})
|
||||
|
||||
return Array.isArray(result) ? result[0] : result
|
||||
}
|
||||
|
||||
protected static canCancelFulfillmentOrThrow(fulfillment: Fulfillment) {
|
||||
if (fulfillment.shipped_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Fulfillment with id ${fulfillment.id} already shipped`
|
||||
)
|
||||
}
|
||||
|
||||
if (fulfillment.delivered_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Fulfillment with id ${fulfillment.id} already delivered`
|
||||
)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
protected static validateMissingShippingOptions_(
|
||||
shippingOptions: ShippingOption[],
|
||||
shippingOptionsData: FulfillmentTypes.UpdateShippingOptionDTO[]
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import {
|
||||
Constructor,
|
||||
DAL,
|
||||
FulfillmentTypes,
|
||||
IFulfillmentProvider,
|
||||
} from "@medusajs/types"
|
||||
import { ModulesSdkUtils, promiseAll } from "@medusajs/utils"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { FulfillmentProvider } from "@models"
|
||||
|
||||
type InjectedDependencies = {
|
||||
fulfillmentProviderRepository: DAL.RepositoryService
|
||||
[key: `fp_${string}`]: FulfillmentTypes.IFulfillmentProvider
|
||||
}
|
||||
|
||||
// TODO rework DTO's
|
||||
|
||||
export default class FulfillmentProviderService extends ModulesSdkUtils.internalModuleServiceFactory<InjectedDependencies>(
|
||||
FulfillmentProvider
|
||||
) {
|
||||
protected readonly fulfillmentProviderRepository_: DAL.RepositoryService
|
||||
|
||||
constructor(container: InjectedDependencies) {
|
||||
super(container)
|
||||
this.fulfillmentProviderRepository_ =
|
||||
container.fulfillmentProviderRepository
|
||||
}
|
||||
|
||||
static getRegistrationIdentifier(
|
||||
providerClass: Constructor<IFulfillmentProvider>,
|
||||
optionName?: string
|
||||
) {
|
||||
return `${(providerClass as any).identifier}_${optionName}`
|
||||
}
|
||||
|
||||
protected retrieveProviderRegistration(
|
||||
providerId: string
|
||||
): FulfillmentTypes.IFulfillmentProvider {
|
||||
try {
|
||||
return this.__container__[`fp_${providerId}`]
|
||||
} catch (err) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Could not find a fulfillment provider with id: ${providerId}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async listFulfillmentOptions(providerIds: string[]): Promise<any[]> {
|
||||
return await promiseAll(
|
||||
providerIds.map(async (p) => {
|
||||
const provider = this.retrieveProviderRegistration(p)
|
||||
return {
|
||||
provider_id: p,
|
||||
options: (await provider.getFulfillmentOptions()) as Record<
|
||||
string,
|
||||
unknown
|
||||
>[],
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
async getFulfillmentOptions(
|
||||
providerId: string
|
||||
): Promise<Record<string, unknown>[]> {
|
||||
const provider = this.retrieveProviderRegistration(providerId)
|
||||
return await provider.getFulfillmentOptions()
|
||||
}
|
||||
|
||||
async validateFulfillmentData(
|
||||
providerId: string,
|
||||
optionData: Record<string, unknown>,
|
||||
data: Record<string, unknown>,
|
||||
context: Record<string, unknown>
|
||||
) {
|
||||
const provider = this.retrieveProviderRegistration(providerId)
|
||||
return await provider.validateFulfillmentData(optionData, data, context)
|
||||
}
|
||||
|
||||
async validateOption(data: any) {
|
||||
const provider = this.retrieveProviderRegistration(data.provider_id)
|
||||
return await provider.validateOption(data)
|
||||
}
|
||||
|
||||
async createFulfillment(
|
||||
providerId: string,
|
||||
data: object,
|
||||
items: object[],
|
||||
order: object,
|
||||
fulfillment: Record<string, unknown>
|
||||
): Promise<Record<string, unknown>> {
|
||||
const provider = this.retrieveProviderRegistration(providerId)
|
||||
return await provider.createFulfillment(data, items, order, fulfillment)
|
||||
}
|
||||
|
||||
async cancelFulfillment(
|
||||
providerId: string,
|
||||
fulfillment: Record<string, unknown>
|
||||
): Promise<any> {
|
||||
const provider = this.retrieveProviderRegistration(providerId)
|
||||
return await provider.cancelFulfillment(fulfillment)
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export { default as FulfillmentModuleService } from "./fulfillment-module-service"
|
||||
export { default as FulfillmentProviderService } from "./fulfillment-provider"
|
||||
@@ -4,3 +4,6 @@ export type InitializeModuleInjectableDependencies = {
|
||||
logger?: Logger
|
||||
eventBusService?: IEventBusModuleService
|
||||
}
|
||||
|
||||
export const FulfillmentIdentifiersRegistrationName =
|
||||
"fulfillment_providers_identifier"
|
||||
Reference in new issue
Block a user