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:
Adrien de Peretti
2024-03-05 11:11:14 +00:00
committed by GitHub
parent f9ef37a2f2
commit 62a7bcc30c
55 changed files with 4066 additions and 2749 deletions
@@ -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
}
}
@@ -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)
+22 -10
View File
@@ -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
}
}
+1 -1
View File
@@ -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,