Skip to content

Commit

Permalink
feat(medusa): Convert FulfillmentService to TypeScript (#1962)
Browse files Browse the repository at this point in the history
  • Loading branch information
pKorsholm committed Aug 22, 2022
1 parent 8cbebef commit c97ccd3
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 117 deletions.
7 changes: 7 additions & 0 deletions .changeset/new-icons-chew.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"medusa-fulfillment-manual": patch
"medusa-interfaces": patch
"@medusajs/medusa": patch
---

Convert FulfillmentService to TypeScript
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ManualFulfillmentService extends FulfillmentService {
]
}

validateFulfillmentData(data, cart) {
validateFulfillmentData(_, data, cart) {
return data
}

Expand Down
23 changes: 17 additions & 6 deletions packages/medusa-interfaces/src/fulfillment-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,23 @@ class BaseFulfillmentService extends BaseService {
* to create shipping options in Medusa that can be chosen between by the
* customer.
*/
getFulfillmentOptions() {}
getFulfillmentOptions() {
throw Error("getFulfillmentOptions must be overridden by the child class")
}

/**
* Called before a shipping method is set on a cart to ensure that the data
* sent with the shipping method is valid. The data object may contain extra
* data about the shipment such as an id of a drop point. It is up to the
* fulfillment provider to enforce that the correct data is being sent
* through.
* @param {object} optionData - the data to validate
* @param {object} data - the data to validate
* @param {object} cart - the cart to which the shipping method will be applied
* @param {object | undefined} cart - the cart to which the shipping method will be applied
* @return {object} the data to populate `cart.shipping_methods.$.data` this
* is usually important for future actions like generating shipping labels
*/
validateFulfillmentData(data, cart) {
validateFulfillmentData(optionData, data, cart) {
throw Error("validateFulfillmentData must be overridden by the child class")
}

Expand All @@ -56,12 +59,16 @@ class BaseFulfillmentService extends BaseService {
/**
* Used to calculate a price for a given shipping option.
*/
calculatePrice(data, cart) {
calculatePrice(optionData, data, cart) {
throw Error("calculatePrice must be overridden by the child class")
}

createFulfillment() {
throw Error("createOrder must be overridden by the child class")
createFulfillment(data, items, order, fulfillment) {
throw Error("createFulfillment must be overridden by the child class")
}

cancelFulfillment(fulfillment) {
throw Error("cancelFulfillment must be overridden by the child class")
}

/**
Expand Down Expand Up @@ -94,6 +101,10 @@ class BaseFulfillmentService extends BaseService {
getShipmentDocuments(data) {
return []
}

retrieveDocuments(fulfillmentData, documentType) {
throw Error("retrieveDocuments must be overridden by the child class")
}
}

export default BaseFulfillmentService
108 changes: 0 additions & 108 deletions packages/medusa/src/services/fulfillment-provider.js

This file was deleted.

191 changes: 191 additions & 0 deletions packages/medusa/src/services/fulfillment-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { MedusaError } from "medusa-core-utils"
import BaseFulfillmentService from "medusa-interfaces/dist/fulfillment-service"
import { EntityManager } from "typeorm"
import { TransactionBaseService } from "../interfaces"
import {
Cart,
Fulfillment,
FulfillmentProvider,
LineItem,
Order,
Return,
ShippingMethod,
ShippingOption,
} from "../models"
import { FulfillmentProviderRepository } from "../repositories/fulfillment-provider"
import { CreateFulfillmentOrder } from "../types/fulfillment"
import {
CreateReturnType,
FulfillmentOptions,
} from "../types/fulfillment-provider"
import { MedusaContainer } from "../types/global"

type FulfillmentProviderKey = `fp_${string}`

type FulfillmentProviderContainer = MedusaContainer & {
fulfillmentProviderRepository: typeof FulfillmentProviderRepository
manager: EntityManager
} & {
[key in `${FulfillmentProviderKey}`]: BaseFulfillmentService
}

/**
* Helps retrive fulfillment providers
*/
class FulfillmentProviderService extends TransactionBaseService {
protected manager_: EntityManager
protected transactionManager_: EntityManager | undefined

protected readonly container_: FulfillmentProviderContainer

protected readonly fulfillmentProviderRepository_: typeof FulfillmentProviderRepository

constructor(container: FulfillmentProviderContainer) {
super(container)

const { manager, fulfillmentProviderRepository } = container

this.container_ = container
this.manager_ = manager
this.fulfillmentProviderRepository_ = fulfillmentProviderRepository
}

async registerInstalledProviders(providers: string[]): Promise<void> {
return await this.atomicPhase_(async (manager) => {
const fulfillmentProviderRepo = manager.getCustomRepository(
this.fulfillmentProviderRepository_
)
await fulfillmentProviderRepo.update({}, { is_installed: false })

for (const p of providers) {
const n = fulfillmentProviderRepo.create({ id: p, is_installed: true })
await fulfillmentProviderRepo.save(n)
}
})
}

async list(): Promise<FulfillmentProvider[]> {
const fpRepo = this.manager_.getCustomRepository(
this.fulfillmentProviderRepository_
)

return await fpRepo.find({})
}

async listFulfillmentOptions(
providerIds: string[]
): Promise<FulfillmentOptions[]> {
return await Promise.all(
providerIds.map(async (p) => {
const provider = await this.retrieveProvider(p)
return {
provider_id: p,
options:
(await provider.getFulfillmentOptions()) as unknown as Record<
string,
unknown
>[],
}
})
)
}

/**
* @param providerId - the provider id
* @return the payment fulfillment provider
*/
retrieveProvider(providerId: string): BaseFulfillmentService {
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 createFulfillment(
method: ShippingMethod,
items: LineItem[],
order: CreateFulfillmentOrder,
fulfillment: Omit<Fulfillment, "beforeInsert">
): Promise<Record<string, unknown>> {
const provider = this.retrieveProvider(method.shipping_option.provider_id)
return provider.createFulfillment(
method.data,
items,
order,
fulfillment
) as unknown as Record<string, unknown>
}

async canCalculate(option: ShippingOption): Promise<boolean> {
const provider = this.retrieveProvider(option.provider_id)
return provider.canCalculate(option.data) as unknown as boolean
}

async validateFulfillmentData(
option: ShippingOption,
data: Record<string, unknown>,
cart: Cart | Record<string, unknown>
): Promise<Record<string, unknown>> {
const provider = this.retrieveProvider(option.provider_id)
return provider.validateFulfillmentData(
option.data,
data,
cart
) as unknown as Record<string, unknown>
}

async cancelFulfillment(fulfillment: Fulfillment): Promise<Fulfillment> {
const provider = this.retrieveProvider(fulfillment.provider_id)
return provider.cancelFulfillment(
fulfillment.data
) as unknown as Fulfillment
}

async calculatePrice(
option: ShippingOption,
data: Record<string, unknown>,
cart?: Order | Cart
): Promise<number> {
const provider = this.retrieveProvider(option.provider_id)
return provider.calculatePrice(option.data, data, cart) as unknown as number
}

async validateOption(option: ShippingOption): Promise<boolean> {
const provider = this.retrieveProvider(option.provider_id)
return provider.validateOption(option.data) as unknown as boolean
}

async createReturn(
returnOrder: CreateReturnType
): Promise<Record<string, unknown>> {
const option = returnOrder.shipping_method.shipping_option
const provider = this.retrieveProvider(option.provider_id)
return provider.createReturn(returnOrder) as unknown as Record<
string,
unknown
>
}

/**
* Fetches documents from the fulfillment provider
* @param providerId - the id of the provider
* @param fulfillmentData - the data relating to the fulfillment
* @param documentType - the typ of
* @returns document to fetch
*/
// TODO: consider removal in favor of "getReturnDocuments" and "getShipmentDocuments"
async retrieveDocuments(
providerId: string,
fulfillmentData: Record<string, unknown>,
documentType: "invoice" | "label"
): Promise<any> {
const provider = this.retrieveProvider(providerId)
return provider.retrieveDocuments(fulfillmentData, documentType)
}
}

export default FulfillmentProviderService
4 changes: 2 additions & 2 deletions packages/medusa/src/services/shipping-option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ class ShippingOptionService extends TransactionBaseService {
*/
async createShippingMethod(
optionId: string,
data: object,
data: Record<string, unknown>,
config: CreateShippingMethodDto
): Promise<ShippingMethod> {
return await this.atomicPhase_(async (manager) => {
Expand Down Expand Up @@ -678,7 +678,7 @@ class ShippingOptionService extends TransactionBaseService {
*/
async getPrice_(
option: ShippingOption,
data: object,
data: Record<string, unknown>,
cart: Cart | Order | undefined
): Promise<number> {
if (option.price_type === "calculated") {
Expand Down

0 comments on commit c97ccd3

Please sign in to comment.