chore(): Reorganize modules (#7210)
**What** Move all modules to the modules directory
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
/dist
|
||||
node_modules
|
||||
.DS_store
|
||||
.env*
|
||||
.env
|
||||
*.sql
|
||||
@@ -0,0 +1,23 @@
|
||||
# @medusajs/fulfillment
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#7175](https://github.com/medusajs/medusa/pull/7175) [`e26cda4b6a`](https://github.com/medusajs/medusa/commit/e26cda4b6afb7fb25f0b0a7a7ce20b7f914d35db) Thanks [@adrien2p](https://github.com/adrien2p)! - feat(medusa, core-flows, types): Allow to update the rules from a shipping options
|
||||
|
||||
- Updated dependencies [[`0c0b425de7`](https://github.com/medusajs/medusa/commit/0c0b425de7b154b80b712ab17b16215cf62d1e83), [`8d356217bd`](https://github.com/medusajs/medusa/commit/8d356217bd31c97a196e861ee243822a4d924df7), [`1eeb1e9de3`](https://github.com/medusajs/medusa/commit/1eeb1e9de3e0b571735437b00968ee96e4aabad5), [`20e8df914e`](https://github.com/medusajs/medusa/commit/20e8df914ec5fdf8d562d4fa84f72c58c7056195), [`27f4f0d724`](https://github.com/medusajs/medusa/commit/27f4f0d7243367c2dfc6012bf1f6b7400a77ec7b), [`e0b02a1012`](https://github.com/medusajs/medusa/commit/e0b02a1012981c29830d7779f59ebe805bbfd137), [`e944a627f0`](https://github.com/medusajs/medusa/commit/e944a627f074fb39a56f4bc7b3d6d315736ebf7c), [`1a48fe0282`](https://github.com/medusajs/medusa/commit/1a48fe0282a8bc1f8548a4736255e457d173da09), [`86f499de2f`](https://github.com/medusajs/medusa/commit/86f499de2f31356ab36ad5e93f27345443b3e5f6), [`09a2220569`](https://github.com/medusajs/medusa/commit/09a22205693da62fbf8fd450535d5024cb9c01d1), [`78f603e4f1`](https://github.com/medusajs/medusa/commit/78f603e4f18c9d16f4b58a2189c959026453d8b2), [`cc557c8752`](https://github.com/medusajs/medusa/commit/cc557c8752fd0554f5a1b58522d9a88dc43a8509), [`dd35a4dbff`](https://github.com/medusajs/medusa/commit/dd35a4dbff10c86ea3c5f7f817c18b6e60d599e3), [`58c68f6715`](https://github.com/medusajs/medusa/commit/58c68f67156e993255fbc25d91db15ae23bc95c0), [`1bcb13f892`](https://github.com/medusajs/medusa/commit/1bcb13f892bc61db21b3fc6bdbce85f747aeec4c), [`82a176e30e`](https://github.com/medusajs/medusa/commit/82a176e30e47a7d11caaf31c3023bd8db588b465), [`11517f0faf`](https://github.com/medusajs/medusa/commit/11517f0fafdf00af256240448b58d149d8b6f600), [`62b9dcc6c1`](https://github.com/medusajs/medusa/commit/62b9dcc6c1ce46aadb7944215006c12da3c9f619), [`5d9aea053c`](https://github.com/medusajs/medusa/commit/5d9aea053ce6e04f242f86fb9053c13dec515d5b), [`e26cda4b6a`](https://github.com/medusajs/medusa/commit/e26cda4b6afb7fb25f0b0a7a7ce20b7f914d35db), [`bc06ad2db4`](https://github.com/medusajs/medusa/commit/bc06ad2db48c999023ab823fefc1375196976e9b), [`18f3aacee6`](https://github.com/medusajs/medusa/commit/18f3aacee6752854d377faa806f4cc67bc71456b), [`232322d035`](https://github.com/medusajs/medusa/commit/232322d03515f81e56867ff8c765b8409399ee68), [`38c971f111`](https://github.com/medusajs/medusa/commit/38c971f111af69f176e7e9892eb59f5bae831fa7), [`45c49e89f2`](https://github.com/medusajs/medusa/commit/45c49e89f28123ef622fc1c07253bae94fd74875), [`528ef4ca90`](https://github.com/medusajs/medusa/commit/528ef4ca90bb2cf6173dccc9fd6a9f9932ff9b76), [`65794f4bb5`](https://github.com/medusajs/medusa/commit/65794f4bb56e4fd3f0ccb7656a948f856f05324e), [`93ef94cad3`](https://github.com/medusajs/medusa/commit/93ef94cad3ddc5b6973b4e48e422b0aa0e6ddbbe), [`4cf71af07d`](https://github.com/medusajs/medusa/commit/4cf71af07d1807c83df3889c1774f82cbd1b9a6f), [`4b57c5d286`](https://github.com/medusajs/medusa/commit/4b57c5d286f9dc6e2098c67e9fecb0d93175b5a1), [`c78915c7c5`](https://github.com/medusajs/medusa/commit/c78915c7c5e91a99c1b1bae932656c8d86b17daf), [`18f3aacee6`](https://github.com/medusajs/medusa/commit/18f3aacee6752854d377faa806f4cc67bc71456b), [`667c8609cc`](https://github.com/medusajs/medusa/commit/667c8609ccf3850f5df8cf784723a95bd0d6d2a6), [`f175cac4af`](https://github.com/medusajs/medusa/commit/f175cac4af63b71066a8398ecf9beaa6f28b20cc), [`0a9b9b073d`](https://github.com/medusajs/medusa/commit/0a9b9b073dd2d3f4aa5e5cb1c16e2221a7200e0d), [`a6562d2a41`](https://github.com/medusajs/medusa/commit/a6562d2a41453cbe7aa43be352c4924e3e4c79d5), [`00e6b21bb5`](https://github.com/medusajs/medusa/commit/00e6b21bb50dbc886bc37ad052a1c40ce865294e), [`8fd1488938`](https://github.com/medusajs/medusa/commit/8fd148893850eb66c5eae00c4ca9391a80ea2eb9), [`1c6ba4468e`](https://github.com/medusajs/medusa/commit/1c6ba4468eab1440931c88929affd5b4c593f377)]:
|
||||
- @medusajs/types@1.11.16
|
||||
- @medusajs/modules-sdk@1.12.11
|
||||
- @medusajs/utils@1.11.9
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#6700](https://github.com/medusajs/medusa/pull/6700) [`8f8a4f9b13`](https://github.com/medusajs/medusa/commit/8f8a4f9b1353087d98f6cc75346d43a7f49901a8) Thanks [@olivermrbl](https://github.com/olivermrbl)! - chore: Version all modules to allow for initial testing
|
||||
|
||||
- Updated dependencies [[`1fd0457c15`](https://github.com/medusajs/medusa/commit/1fd0457c153b2ef7657c052878d8e5364e1b324a), [`9288f53327`](https://github.com/medusajs/medusa/commit/9288f53327b8ce617af92ed8d14d9459cbfeb13c), [`d4b921f3db`](https://github.com/medusajs/medusa/commit/d4b921f3dbe0a38f1565a8de759996c70798d58e), [`ac86362e81`](https://github.com/medusajs/medusa/commit/ac86362e81d8523cb8e3dfad026fc94658513018), [`e4acde1aa2`](https://github.com/medusajs/medusa/commit/e4acde1aa2eb57f07e6692fe8b61f728948b9a96), [`1a661adf3e`](https://github.com/medusajs/medusa/commit/1a661adf3ef4991aa6e237dd894b6a5c47cd4aca), [`56cbf88115`](https://github.com/medusajs/medusa/commit/56cbf88115994adea7037c3f2814f0c96af3cfc0), [`36a61658f9`](https://github.com/medusajs/medusa/commit/36a61658f969a7b19c84a1e621ad1464927cafb1), [`04a532e5ef`](https://github.com/medusajs/medusa/commit/04a532e5efabbf75b1e4155520b1da175b686ffc), [`c319edb8e0`](https://github.com/medusajs/medusa/commit/c319edb8e0ecd13d086652147667916e5abab2d8), [`0b9fcb6324`](https://github.com/medusajs/medusa/commit/0b9fcb6324eee9f2556c7e6317775fae93b12a47), [`586df9da25`](https://github.com/medusajs/medusa/commit/586df9da250e492442769f5bac2f8b3de1d46f05), [`b3d826497b`](https://github.com/medusajs/medusa/commit/b3d826497b3dae5e1b26b7924706c24fd5e87ca5), [`a86c87fe14`](https://github.com/medusajs/medusa/commit/a86c87fe1442afce9285e39255914e01012b4449), [`640eccd5dd`](https://github.com/medusajs/medusa/commit/640eccd5ddbb163e0f987ce6c772f1129c2e2632), [`8ea37d03c9`](https://github.com/medusajs/medusa/commit/8ea37d03c914a5004a3e42770668b2d1f7f8f564), [`339a946f38`](https://github.com/medusajs/medusa/commit/339a946f389033c21e05338f9dbf07d88e140533), [`ac829fc67f`](https://github.com/medusajs/medusa/commit/ac829fc67f7495b08f28e55923c59f0fd6320311), [`d9d5afc3cf`](https://github.com/medusajs/medusa/commit/d9d5afc3cfc29221d0e65bff7b78474a8fb8f31f), [`c3c4f49fc2`](https://github.com/medusajs/medusa/commit/c3c4f49fc2126f950e69e291ca939ca88a15afd3), [`9288f53327`](https://github.com/medusajs/medusa/commit/9288f53327b8ce617af92ed8d14d9459cbfeb13c), [`0d46abf0ff`](https://github.com/medusajs/medusa/commit/0d46abf0ffa4c5e03bf7d2a9cdf1db828a76bea8), [`fafde4f54d`](https://github.com/medusajs/medusa/commit/fafde4f54d3ef75a7d382e6cbf94e38b3deae99b), [`8dad2b51a2`](https://github.com/medusajs/medusa/commit/8dad2b51a26c4c3c14a6c95f70424c8bef2ad63e), [`0c705d7bd4`](https://github.com/medusajs/medusa/commit/0c705d7bd41a768c48017ae95b3c8414d96c6acb), [`a6d7070dd6`](https://github.com/medusajs/medusa/commit/a6d7070dd669c21ea19d70434d42c2f8167dc309), [`1d91b7429b`](https://github.com/medusajs/medusa/commit/1d91b7429beebd6f09d5027f7f7e1fe74ce3a8ff), [`168f02f138`](https://github.com/medusajs/medusa/commit/168f02f138ad101e1013f2c8c3f8dc19de12accf), [`1ed5f918c3`](https://github.com/medusajs/medusa/commit/1ed5f918c31794a70aca4a4e4cd83cf456593baa), [`c20eb15cd9`](https://github.com/medusajs/medusa/commit/c20eb15cd9b1bd90c8d01f68eca6f0f181cd902d), [`e5945479e0`](https://github.com/medusajs/medusa/commit/e5945479e091d9560ae3e7240306a31031ef4584), [`f5c2256286`](https://github.com/medusajs/medusa/commit/f5c22562867f412040f8bc6c55ab5de3a3735e62), [`000eb61e33`](https://github.com/medusajs/medusa/commit/000eb61e33e0302db95ee6ad1656ea9b430ed471), [`d550be3685`](https://github.com/medusajs/medusa/commit/d550be3685423218d47a20c57a5e06758f4a961a), [`62a7bcc30c`](https://github.com/medusajs/medusa/commit/62a7bcc30cbc7b234b2b51d7858439951a84edeb), [`8f8a4f9b13`](https://github.com/medusajs/medusa/commit/8f8a4f9b1353087d98f6cc75346d43a7f49901a8), [`6500f18b9b`](https://github.com/medusajs/medusa/commit/6500f18b9b80c5c9c473489e7e740d55dca74303), [`ce39b9b66e`](https://github.com/medusajs/medusa/commit/ce39b9b66e8c277ec0691ea6d0a950003be09cc1), [`a6a4b3f01a`](https://github.com/medusajs/medusa/commit/a6a4b3f01a6d2bd97b1580c59134279a1b033a5d), [`4d51f095b3`](https://github.com/medusajs/medusa/commit/4d51f095b3f98f468cefb760512563f7b77bb9cf), [`4625bd1241`](https://github.com/medusajs/medusa/commit/4625bd12416275b09c22cde4a09cb0f68df5d7c1), [`56b0b45304`](https://github.com/medusajs/medusa/commit/56b0b4530401a6ec5aa155874d371e45bb388fe2), [`cc1b66842c`](https://github.com/medusajs/medusa/commit/cc1b66842cbb37c6eab84e2d8b74844c214f38d7), [`24fb102a56`](https://github.com/medusajs/medusa/commit/24fb102a564b1253d1f8b039bb1e435cc5312fbb), [`e85463b2a7`](https://github.com/medusajs/medusa/commit/e85463b2a717751de2e21c39a4c745449b31affe)]:
|
||||
- @medusajs/types@1.11.14
|
||||
- @medusajs/utils@1.11.7
|
||||
- @medusajs/modules-sdk@1.12.9
|
||||
@@ -0,0 +1 @@
|
||||
# Fulfillment Module
|
||||
@@ -0,0 +1,24 @@
|
||||
import { EventBusTypes } from "@medusajs/types"
|
||||
|
||||
export function buildExpectedEventMessageShape(options: {
|
||||
eventName: string
|
||||
action: string
|
||||
object: string
|
||||
eventGroupId?: string
|
||||
data: any
|
||||
options?: Record<string, unknown>
|
||||
}): EventBusTypes.Message {
|
||||
return {
|
||||
eventName: options.eventName,
|
||||
body: {
|
||||
metadata: {
|
||||
action: options.action,
|
||||
eventGroupId: options.eventGroupId,
|
||||
service: "fulfillment",
|
||||
object: options.object,
|
||||
},
|
||||
data: options.data,
|
||||
},
|
||||
options: options.options,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { CreateFulfillmentDTO } from "@medusajs/types"
|
||||
|
||||
export function generateCreateFulfillmentData(
|
||||
data: Partial<CreateFulfillmentDTO> & {
|
||||
provider_id: string
|
||||
shipping_option_id: string
|
||||
}
|
||||
) {
|
||||
const randomString = Math.random().toString(36).substring(7)
|
||||
|
||||
return {
|
||||
location_id: "test-location",
|
||||
packed_at: null,
|
||||
shipped_at: null,
|
||||
delivered_at: null,
|
||||
canceled_at: null,
|
||||
data: null,
|
||||
provider_id: data.provider_id,
|
||||
shipping_option_id: data.shipping_option_id,
|
||||
metadata: data.metadata ?? null,
|
||||
delivery_address: data.delivery_address ?? {
|
||||
address_1: "test-address_" + randomString,
|
||||
address_2: "test-address_" + randomString,
|
||||
city: "test-city_" + randomString,
|
||||
postal_code: "test-postal-code_" + randomString,
|
||||
country_code: "test-country-code_" + randomString,
|
||||
province: "test-province_" + randomString,
|
||||
phone: "test-phone_" + randomString,
|
||||
full_name: "test-full-name_" + randomString,
|
||||
},
|
||||
items: data.items ?? [
|
||||
{
|
||||
title: "test-title_" + randomString,
|
||||
sku: "test-sku_" + randomString,
|
||||
quantity: 1,
|
||||
barcode: "test-barcode_" + randomString,
|
||||
},
|
||||
],
|
||||
labels: data.labels ?? [
|
||||
{
|
||||
tracking_number: "test-tracking-number_" + randomString,
|
||||
tracking_url: "test-tracking-url_" + randomString,
|
||||
label_url: "test-label-url_" + randomString,
|
||||
},
|
||||
],
|
||||
order: data.order ?? {},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { generateCreateShippingOptionsData } from "./shipping-options"
|
||||
import { generateCreateFulfillmentData } from "./fulfillment"
|
||||
import { IFulfillmentModuleService } from "@medusajs/types"
|
||||
|
||||
export * from "./shipping-options"
|
||||
export * from "./fulfillment"
|
||||
export * from "./events"
|
||||
|
||||
export async function createFullDataStructure(
|
||||
service: IFulfillmentModuleService,
|
||||
{
|
||||
providerId,
|
||||
}: {
|
||||
providerId: string
|
||||
}
|
||||
) {
|
||||
const randomString = Math.random().toString(36).substring(7)
|
||||
|
||||
const shippingProfile = await service.createShippingProfiles({
|
||||
// generate random string
|
||||
name: "test_" + randomString,
|
||||
type: "default",
|
||||
})
|
||||
const fulfillmentSet = await service.create({
|
||||
name: "test_" + randomString,
|
||||
type: "test-type",
|
||||
})
|
||||
const serviceZone = await service.createServiceZones({
|
||||
name: "test_" + randomString,
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
geo_zones: [
|
||||
{
|
||||
type: "country",
|
||||
country_code: "US_" + randomString,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const shippingOption = await service.createShippingOptions(
|
||||
generateCreateShippingOptionsData({
|
||||
provider_id: providerId,
|
||||
service_zone_id: serviceZone.id,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
})
|
||||
)
|
||||
|
||||
await service.createFulfillment(
|
||||
generateCreateFulfillmentData({
|
||||
provider_id: providerId,
|
||||
shipping_option_id: shippingOption.id,
|
||||
})
|
||||
)
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { AbstractFulfillmentProviderService } from "@medusajs/utils/src"
|
||||
|
||||
export class FulfillmentProviderServiceFixtures extends AbstractFulfillmentProviderService {
|
||||
static identifier = "fixtures-fulfillment-provider"
|
||||
|
||||
async createFulfillment(data, items, order, fulfillment): Promise<any> {
|
||||
return {}
|
||||
}
|
||||
|
||||
async cancelFulfillment(fulfillment): Promise<any> {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getFulfillmentOptions(): Promise<any> {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export const services = [FulfillmentProviderServiceFixtures]
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./default-provider"
|
||||
@@ -0,0 +1,41 @@
|
||||
import { CreateShippingOptionDTO } from "@medusajs/types"
|
||||
|
||||
export function generateCreateShippingOptionsData({
|
||||
name,
|
||||
service_zone_id,
|
||||
shipping_profile_id,
|
||||
provider_id,
|
||||
price_type,
|
||||
rules,
|
||||
type,
|
||||
data,
|
||||
}: Omit<CreateShippingOptionDTO, "name" | "price_type" | "type"> & {
|
||||
price_type?: CreateShippingOptionDTO["price_type"]
|
||||
name?: string
|
||||
type?: CreateShippingOptionDTO["type"]
|
||||
}): Required<CreateShippingOptionDTO> {
|
||||
const randomString = Math.random().toString(36).substring(7)
|
||||
|
||||
return {
|
||||
service_zone_id: service_zone_id,
|
||||
shipping_profile_id: shipping_profile_id,
|
||||
provider_id: provider_id,
|
||||
type: type ?? {
|
||||
code: "test-type_" + randomString,
|
||||
description: "test-description_" + randomString,
|
||||
label: "test-label_" + randomString,
|
||||
},
|
||||
data: data ?? {
|
||||
amount: 1000,
|
||||
},
|
||||
name: name ?? Math.random().toString(36).substring(7),
|
||||
price_type: price_type ?? "flat",
|
||||
rules: rules ?? [
|
||||
{
|
||||
attribute: "weight",
|
||||
operator: "eq",
|
||||
value: "test",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
+1064
File diff suppressed because it is too large
Load Diff
+218
@@ -0,0 +1,218 @@
|
||||
import { resolve } from "path"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { IFulfillmentModuleService } from "@medusajs/types"
|
||||
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
|
||||
import {
|
||||
generateCreateFulfillmentData,
|
||||
generateCreateShippingOptionsData,
|
||||
} from "../../__fixtures__"
|
||||
|
||||
jest.setTimeout(100000)
|
||||
|
||||
const moduleOptions = {
|
||||
providers: [
|
||||
{
|
||||
resolve: resolve(
|
||||
process.cwd() +
|
||||
"/integration-tests/__fixtures__/providers/default-provider"
|
||||
),
|
||||
options: {
|
||||
config: {
|
||||
"test-provider": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const providerId = "fixtures-fulfillment-provider_test-provider"
|
||||
|
||||
moduleIntegrationTestRunner({
|
||||
moduleName: Modules.FULFILLMENT,
|
||||
moduleOptions: moduleOptions,
|
||||
testSuite: ({ service }: SuiteOptions<IFulfillmentModuleService>) => {
|
||||
describe("Fulfillment Module Service", () => {
|
||||
describe("read", () => {
|
||||
it("should list fulfillment", async () => {
|
||||
const shippingProfile = await service.createShippingProfiles({
|
||||
name: "test",
|
||||
type: "default",
|
||||
})
|
||||
const fulfillmentSet = await service.create({
|
||||
name: "test",
|
||||
type: "test-type",
|
||||
})
|
||||
const serviceZone = await service.createServiceZones({
|
||||
name: "test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
})
|
||||
|
||||
const shippingOption = await service.createShippingOptions(
|
||||
generateCreateShippingOptionsData({
|
||||
provider_id: providerId,
|
||||
service_zone_id: serviceZone.id,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
})
|
||||
)
|
||||
|
||||
const fulfillment = await service.createFulfillment(
|
||||
generateCreateFulfillmentData({
|
||||
provider_id: providerId,
|
||||
shipping_option_id: shippingOption.id,
|
||||
})
|
||||
)
|
||||
|
||||
const result = await service.listFulfillments({
|
||||
shipping_option_id: shippingOption.id,
|
||||
})
|
||||
|
||||
expect(result.length).toEqual(1)
|
||||
expect(result[0].id).toEqual(fulfillment.id)
|
||||
})
|
||||
|
||||
it("should retrieve the fulfillment options", async () => {
|
||||
const fulfillmentOptions = await service.retrieveFulfillmentOptions(
|
||||
providerId
|
||||
)
|
||||
|
||||
expect(fulfillmentOptions).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
describe("mutations", () => {
|
||||
describe("on create", () => {
|
||||
it("should create a fulfillment", async () => {
|
||||
const shippingProfile = await service.createShippingProfiles({
|
||||
name: "test",
|
||||
type: "default",
|
||||
})
|
||||
const fulfillmentSet = await service.create({
|
||||
name: "test",
|
||||
type: "test-type",
|
||||
})
|
||||
const serviceZone = await service.createServiceZones({
|
||||
name: "test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
})
|
||||
|
||||
const shippingOption = await service.createShippingOptions(
|
||||
generateCreateShippingOptionsData({
|
||||
provider_id: providerId,
|
||||
service_zone_id: serviceZone.id,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
})
|
||||
)
|
||||
|
||||
const fulfillment = await service.createFulfillment(
|
||||
generateCreateFulfillmentData({
|
||||
provider_id: providerId,
|
||||
shipping_option_id: shippingOption.id,
|
||||
})
|
||||
)
|
||||
|
||||
expect(fulfillment).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
packed_at: null,
|
||||
shipped_at: null,
|
||||
delivered_at: null,
|
||||
canceled_at: null,
|
||||
data: null,
|
||||
provider_id: providerId,
|
||||
shipping_option_id: shippingOption.id,
|
||||
metadata: null,
|
||||
delivery_address: expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
}),
|
||||
items: [
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
}),
|
||||
],
|
||||
labels: [
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("on cancel", () => {
|
||||
let fulfillment
|
||||
|
||||
beforeEach(async () => {
|
||||
const shippingProfile = await service.createShippingProfiles({
|
||||
name: "test",
|
||||
type: "default",
|
||||
})
|
||||
const fulfillmentSet = await service.create({
|
||||
name: "test",
|
||||
type: "test-type",
|
||||
})
|
||||
const serviceZone = await service.createServiceZones({
|
||||
name: "test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
})
|
||||
|
||||
const shippingOption = await service.createShippingOptions(
|
||||
generateCreateShippingOptionsData({
|
||||
provider_id: providerId,
|
||||
service_zone_id: serviceZone.id,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
})
|
||||
)
|
||||
|
||||
fulfillment = await service.createFulfillment(
|
||||
generateCreateFulfillmentData({
|
||||
provider_id: providerId,
|
||||
shipping_option_id: shippingOption.id,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should cancel a fulfillment successfully", async () => {
|
||||
const result = await service.cancelFulfillment(fulfillment.id)
|
||||
// should be idempotent
|
||||
const idempotentResult = await service.cancelFulfillment(
|
||||
fulfillment.id
|
||||
)
|
||||
|
||||
expect(result.canceled_at).not.toBeNull()
|
||||
expect(idempotentResult.canceled_at).not.toBeNull()
|
||||
expect(idempotentResult.canceled_at).toEqual(result.canceled_at)
|
||||
})
|
||||
|
||||
it("should fail to cancel a fulfillment that is already shipped", async () => {
|
||||
await service.updateFulfillment(fulfillment.id, {
|
||||
shipped_at: new Date(),
|
||||
})
|
||||
|
||||
const err = await service
|
||||
.cancelFulfillment(fulfillment.id)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(err.message).toEqual(
|
||||
`Fulfillment with id ${fulfillment.id} already shipped`
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail to cancel a fulfillment that is already delivered", async () => {
|
||||
await service.updateFulfillment(fulfillment.id, {
|
||||
delivered_at: new Date(),
|
||||
})
|
||||
|
||||
const err = await service
|
||||
.cancelFulfillment(fulfillment.id)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(err.message).toEqual(
|
||||
`Fulfillment with id ${fulfillment.id} already delivered`
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
CreateGeoZoneDTO,
|
||||
IFulfillmentModuleService,
|
||||
UpdateGeoZoneDTO,
|
||||
} from "@medusajs/types"
|
||||
import { GeoZoneType } from "@medusajs/utils"
|
||||
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
|
||||
|
||||
jest.setTimeout(100000)
|
||||
|
||||
moduleIntegrationTestRunner({
|
||||
moduleName: Modules.FULFILLMENT,
|
||||
testSuite: ({ service }: SuiteOptions<IFulfillmentModuleService>) => {
|
||||
describe("Fulfillment Module Service", () => {
|
||||
describe("read", () => {
|
||||
it("should list geo zones with a filter", async function () {
|
||||
const fulfillmentSet = await service.create({
|
||||
name: "test",
|
||||
type: "test-type",
|
||||
})
|
||||
const serviceZone = await service.createServiceZones({
|
||||
name: "test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
})
|
||||
|
||||
const createdZone1 = await service.createGeoZones({
|
||||
service_zone_id: serviceZone.id,
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "fr",
|
||||
})
|
||||
const createdZone2 = await service.createGeoZones({
|
||||
service_zone_id: serviceZone.id,
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "us",
|
||||
})
|
||||
|
||||
let listedZones = await service.listGeoZones({
|
||||
type: createdZone1.type,
|
||||
})
|
||||
|
||||
expect(listedZones).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ id: createdZone1.id }),
|
||||
expect.objectContaining({ id: createdZone2.id }),
|
||||
])
|
||||
)
|
||||
|
||||
listedZones = await service.listGeoZones({
|
||||
country_code: createdZone2.country_code,
|
||||
})
|
||||
|
||||
expect(listedZones).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ id: createdZone2.id }),
|
||||
])
|
||||
)
|
||||
expect(listedZones).not.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ id: createdZone1.id }),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("mutations", () => {
|
||||
describe("on create", () => {
|
||||
it("should create a new geo zone", async function () {
|
||||
const fulfillmentSet = await service.create({
|
||||
name: "test",
|
||||
type: "test-type",
|
||||
})
|
||||
const serviceZone = await service.createServiceZones({
|
||||
name: "test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
})
|
||||
|
||||
const data: CreateGeoZoneDTO = {
|
||||
service_zone_id: serviceZone.id,
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "fr",
|
||||
}
|
||||
|
||||
const geoZone = await service.createGeoZones(data)
|
||||
|
||||
expect(geoZone).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
type: data.type,
|
||||
country_code: data.country_code,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should create a collection of geo zones", async function () {
|
||||
const fulfillmentSet = await service.create({
|
||||
name: "test",
|
||||
type: "test-type",
|
||||
})
|
||||
const serviceZone = await service.createServiceZones({
|
||||
name: "test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
})
|
||||
|
||||
const data: CreateGeoZoneDTO[] = [
|
||||
{
|
||||
service_zone_id: serviceZone.id,
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "fr",
|
||||
},
|
||||
{
|
||||
service_zone_id: serviceZone.id,
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "us",
|
||||
},
|
||||
]
|
||||
|
||||
const geoZones = await service.createGeoZones(data)
|
||||
|
||||
expect(geoZones).toHaveLength(2)
|
||||
|
||||
let i = 0
|
||||
for (const data_ of data) {
|
||||
expect(geoZones[i]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
type: data_.type,
|
||||
country_code: data_.country_code,
|
||||
})
|
||||
)
|
||||
++i
|
||||
}
|
||||
})
|
||||
|
||||
it("should fail to create new geo zones that are not valid", async function () {
|
||||
const fulfillmentSet = await service.create({
|
||||
name: "test",
|
||||
type: "test-type",
|
||||
})
|
||||
const serviceZone = await service.createServiceZones({
|
||||
name: "test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
})
|
||||
|
||||
let data: CreateGeoZoneDTO = {
|
||||
service_zone_id: serviceZone.id,
|
||||
type: GeoZoneType.PROVINCE,
|
||||
country_code: "fr",
|
||||
} as any
|
||||
|
||||
let err = await service.createGeoZones(data).catch((e) => e)
|
||||
expect(err.message).toBe(
|
||||
"Missing required property province_code for geo zone type province"
|
||||
)
|
||||
|
||||
data = {
|
||||
service_zone_id: serviceZone.id,
|
||||
type: GeoZoneType.CITY,
|
||||
country_code: "fr",
|
||||
province_code: "test",
|
||||
} as any
|
||||
|
||||
err = await service.createGeoZones(data).catch((e) => e)
|
||||
expect(err.message).toBe(
|
||||
"Missing required property city for geo zone type city"
|
||||
)
|
||||
|
||||
data = {
|
||||
service_zone_id: serviceZone.id,
|
||||
type: GeoZoneType.ZIP,
|
||||
postal_expression: "test",
|
||||
} as any
|
||||
|
||||
err = await service.createGeoZones(data).catch((e) => e)
|
||||
expect(err.message).toBe(
|
||||
"Missing required property country_code for geo zone type zip"
|
||||
)
|
||||
|
||||
data = {
|
||||
service_zone_id: serviceZone.id,
|
||||
type: "unknown",
|
||||
postal_expression: "test",
|
||||
} as any
|
||||
|
||||
err = await service.createGeoZones(data).catch((e) => e)
|
||||
expect(err.message).toBe(`Invalid geo zone type: unknown`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("on update", () => {
|
||||
it("should update an existing geo zone", async function () {
|
||||
const fulfillmentSet = await service.create({
|
||||
name: "test",
|
||||
type: "test-type",
|
||||
})
|
||||
|
||||
const serviceZone = await service.createServiceZones({
|
||||
name: "test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
})
|
||||
|
||||
const createData: CreateGeoZoneDTO = {
|
||||
service_zone_id: serviceZone.id,
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "fr",
|
||||
}
|
||||
|
||||
const createdGeoZone = await service.createGeoZones(createData)
|
||||
|
||||
const updateData: UpdateGeoZoneDTO = {
|
||||
id: createdGeoZone.id,
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "us",
|
||||
}
|
||||
|
||||
const updatedGeoZone = await service.updateGeoZones(updateData)
|
||||
|
||||
expect(updatedGeoZone).toEqual(
|
||||
expect.objectContaining({
|
||||
id: updateData.id,
|
||||
type: updateData.type,
|
||||
country_code: updateData.country_code,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should update a collection of geo zones", async function () {
|
||||
const fulfillmentSet = await service.create({
|
||||
name: "test",
|
||||
type: "test-type",
|
||||
})
|
||||
|
||||
const serviceZone = await service.createServiceZones({
|
||||
name: "test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
})
|
||||
|
||||
const createData: CreateGeoZoneDTO[] = [
|
||||
{
|
||||
service_zone_id: serviceZone.id,
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "fr",
|
||||
},
|
||||
{
|
||||
service_zone_id: serviceZone.id,
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "us",
|
||||
},
|
||||
]
|
||||
|
||||
const createdGeoZones = await service.createGeoZones(createData)
|
||||
|
||||
const updateData: UpdateGeoZoneDTO[] = createdGeoZones.map(
|
||||
(geoZone, index) => ({
|
||||
id: geoZone.id,
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: index % 2 === 0 ? "us" : "fr",
|
||||
})
|
||||
)
|
||||
|
||||
const updatedGeoZones = await service.updateGeoZones(updateData)
|
||||
|
||||
expect(updatedGeoZones).toHaveLength(2)
|
||||
|
||||
for (const data_ of updateData) {
|
||||
const expectedGeoZone = updatedGeoZones.find(
|
||||
(geoZone) => geoZone.id === data_.id
|
||||
)
|
||||
expect(expectedGeoZone).toEqual(
|
||||
expect.objectContaining({
|
||||
id: data_.id,
|
||||
type: data_.type,
|
||||
country_code: data_.country_code,
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
import { Modules, ModulesDefinition } from "@medusajs/modules-sdk"
|
||||
import { FulfillmentSetDTO, IFulfillmentModuleService } from "@medusajs/types"
|
||||
import {
|
||||
initModules,
|
||||
moduleIntegrationTestRunner,
|
||||
SuiteOptions,
|
||||
} from "medusa-test-utils/dist"
|
||||
import { resolve } from "path"
|
||||
import { createFullDataStructure } from "../../__fixtures__"
|
||||
import { FulfillmentProviderService } from "@services"
|
||||
import { FulfillmentProviderServiceFixtures } from "../../__fixtures__/providers"
|
||||
|
||||
let moduleOptions = {
|
||||
providers: [
|
||||
{
|
||||
resolve: resolve(
|
||||
process.cwd() +
|
||||
"/integration-tests/__fixtures__/providers/default-provider"
|
||||
),
|
||||
options: {
|
||||
config: {
|
||||
"test-provider": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let providerId = "fixtures-fulfillment-provider_test-provider"
|
||||
|
||||
async function list(
|
||||
service: IFulfillmentModuleService,
|
||||
...args: Parameters<IFulfillmentModuleService["list"]>
|
||||
) {
|
||||
const [filters = {}, config = {}] = args
|
||||
|
||||
const finalConfig = {
|
||||
relations: [
|
||||
"service_zones.geo_zones",
|
||||
"service_zones.shipping_options.shipping_profile",
|
||||
"service_zones.shipping_options.provider",
|
||||
"service_zones.shipping_options.type",
|
||||
"service_zones.shipping_options.rules",
|
||||
"service_zones.shipping_options.fulfillments.labels",
|
||||
"service_zones.shipping_options.fulfillments.items",
|
||||
"service_zones.shipping_options.fulfillments.delivery_address",
|
||||
],
|
||||
...config,
|
||||
}
|
||||
|
||||
return await service.list(filters, finalConfig)
|
||||
}
|
||||
|
||||
function expectSoftDeleted(
|
||||
fulfillmentSets: FulfillmentSetDTO[],
|
||||
{ softDeleted = false } = {}
|
||||
) {
|
||||
expect(fulfillmentSets).toHaveLength(1)
|
||||
|
||||
let fulfillmentSet = fulfillmentSets[0]
|
||||
expect(!!fulfillmentSet.deleted_at).toEqual(softDeleted)
|
||||
expect(fulfillmentSet.service_zones).toHaveLength(1)
|
||||
|
||||
let serviceZone = fulfillmentSet.service_zones[0]
|
||||
expect(!!serviceZone.deleted_at).toEqual(softDeleted)
|
||||
expect(serviceZone.geo_zones).toHaveLength(1)
|
||||
expect(serviceZone.shipping_options).toHaveLength(1)
|
||||
|
||||
let geoZone = serviceZone.geo_zones[0]
|
||||
expect(!!geoZone.deleted_at).toEqual(softDeleted)
|
||||
|
||||
let shippingOption = serviceZone.shipping_options[0]
|
||||
expect(!!shippingOption.deleted_at).toEqual(softDeleted)
|
||||
expect(!!shippingOption.shipping_profile.deleted_at).toEqual(false)
|
||||
expect(!!shippingOption.type.deleted_at).toEqual(softDeleted)
|
||||
expect(shippingOption.fulfillments).toHaveLength(1)
|
||||
expect(shippingOption.rules).toHaveLength(1)
|
||||
|
||||
let rule = shippingOption.rules[0]
|
||||
expect(!!rule.deleted_at).toEqual(softDeleted)
|
||||
|
||||
/**
|
||||
* We do not expect the fulfillment to be soft deleted when soft deleting parents entities
|
||||
*/
|
||||
|
||||
let fulfillment = shippingOption.fulfillments[0]
|
||||
expect(!!fulfillment.deleted_at).toEqual(false)
|
||||
expect(fulfillment.labels).toHaveLength(1)
|
||||
expect(fulfillment.items).toHaveLength(1)
|
||||
|
||||
let label = fulfillment.labels[0]
|
||||
expect(!!label.deleted_at).toEqual(false)
|
||||
|
||||
let item = fulfillment.items[0]
|
||||
expect(!!item.deleted_at).toEqual(false)
|
||||
|
||||
let deliveryAddress = fulfillment.delivery_address
|
||||
expect(!!deliveryAddress.deleted_at).toEqual(false)
|
||||
}
|
||||
|
||||
moduleIntegrationTestRunner({
|
||||
moduleName: Modules.FULFILLMENT,
|
||||
moduleOptions,
|
||||
testSuite: ({
|
||||
MikroOrmWrapper,
|
||||
service,
|
||||
}: SuiteOptions<IFulfillmentModuleService>) =>
|
||||
describe("Fulfillment Module Service", () => {
|
||||
it("should load and save all the providers on bootstrap with the correct is_enabled value", async () => {
|
||||
const databaseConfig = {
|
||||
schema: "public",
|
||||
clientUrl: MikroOrmWrapper.clientUrl,
|
||||
}
|
||||
|
||||
const providersConfig = {}
|
||||
for (let i = 0; i < 10; i++) {
|
||||
providersConfig[`provider-${i}`] = {}
|
||||
}
|
||||
|
||||
let moduleOptions = {
|
||||
databaseConfig,
|
||||
modulesConfig: {
|
||||
[Modules.FULFILLMENT]: {
|
||||
definition: ModulesDefinition[Modules.FULFILLMENT],
|
||||
options: {
|
||||
databaseConfig,
|
||||
providers: [
|
||||
{
|
||||
resolve: resolve(
|
||||
process.cwd() +
|
||||
"/integration-tests/__fixtures__/providers/default-provider"
|
||||
),
|
||||
options: {
|
||||
config: providersConfig,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
let { shutdown } = await initModules(moduleOptions)
|
||||
|
||||
let fulfillmentProviders = await MikroOrmWrapper.forkManager().execute(
|
||||
`SELECT * FROM fulfillment_provider`
|
||||
)
|
||||
|
||||
expect(fulfillmentProviders).toHaveLength(
|
||||
Object.keys(providersConfig).length + 1 // +1 for the default provider
|
||||
)
|
||||
|
||||
for (const [name] of Object.entries(providersConfig)) {
|
||||
const provider = fulfillmentProviders.find((p) => {
|
||||
return (
|
||||
p.id ===
|
||||
FulfillmentProviderService.getRegistrationIdentifier(
|
||||
FulfillmentProviderServiceFixtures,
|
||||
name
|
||||
)
|
||||
)
|
||||
})
|
||||
expect(provider).toBeDefined()
|
||||
expect(provider.is_enabled).toBeTruthy()
|
||||
}
|
||||
|
||||
await shutdown()
|
||||
|
||||
const providersConfig2 = {}
|
||||
for (let i = 10; i < 20; i++) {
|
||||
providersConfig2[`provider-${i}`] = {}
|
||||
}
|
||||
|
||||
moduleOptions = {
|
||||
databaseConfig,
|
||||
modulesConfig: {
|
||||
[Modules.FULFILLMENT]: {
|
||||
definition: ModulesDefinition[Modules.FULFILLMENT],
|
||||
options: {
|
||||
databaseConfig,
|
||||
providers: [
|
||||
{
|
||||
resolve: resolve(
|
||||
process.cwd() +
|
||||
"/integration-tests/__fixtures__/providers/default-provider"
|
||||
),
|
||||
options: {
|
||||
config: providersConfig2,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const medusaApp = await initModules(moduleOptions)
|
||||
shutdown = medusaApp.shutdown
|
||||
|
||||
fulfillmentProviders = await MikroOrmWrapper.forkManager().execute(
|
||||
`SELECT * FROM fulfillment_provider`
|
||||
)
|
||||
|
||||
expect(fulfillmentProviders).toHaveLength(
|
||||
Object.keys(providersConfig2).length +
|
||||
Object.keys(providersConfig).length +
|
||||
1 // +1 for the default provider
|
||||
)
|
||||
|
||||
const allProviders = Object.assign(
|
||||
{},
|
||||
providersConfig,
|
||||
providersConfig2
|
||||
)
|
||||
|
||||
for (const [name] of Object.entries(allProviders)) {
|
||||
const provider = fulfillmentProviders.find((p) => {
|
||||
return (
|
||||
p.id ===
|
||||
FulfillmentProviderService.getRegistrationIdentifier(
|
||||
FulfillmentProviderServiceFixtures,
|
||||
name
|
||||
)
|
||||
)
|
||||
})
|
||||
expect(provider).toBeDefined()
|
||||
|
||||
const isEnabled = !!providersConfig2[name]
|
||||
expect(provider.is_enabled).toEqual(isEnabled)
|
||||
}
|
||||
|
||||
await shutdown().catch(() => void 0)
|
||||
})
|
||||
|
||||
it("should soft delete and restore the data respecting the configured cascade", async () => {
|
||||
await createFullDataStructure(service, { providerId })
|
||||
|
||||
let fulfillmentSets = await list(service)
|
||||
expectSoftDeleted(fulfillmentSets)
|
||||
|
||||
/**
|
||||
* Soft delete the fulfillment set
|
||||
*/
|
||||
|
||||
await service.softDelete(fulfillmentSets[0].id)
|
||||
const deletedFulfillmentSets = await list(
|
||||
service,
|
||||
{},
|
||||
{
|
||||
withDeleted: true,
|
||||
}
|
||||
)
|
||||
expectSoftDeleted(deletedFulfillmentSets, { softDeleted: true })
|
||||
|
||||
/**
|
||||
* Restore the fulfillment set
|
||||
*/
|
||||
|
||||
await service.restore(fulfillmentSets[0].id)
|
||||
const restoredFulfillmentSets = await list(service)
|
||||
expectSoftDeleted(restoredFulfillmentSets)
|
||||
})
|
||||
}),
|
||||
})
|
||||
+445
@@ -0,0 +1,445 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
CreateServiceZoneDTO,
|
||||
GeoZoneDTO,
|
||||
IFulfillmentModuleService,
|
||||
UpdateServiceZoneDTO,
|
||||
} from "@medusajs/types"
|
||||
import { GeoZoneType } from "@medusajs/utils"
|
||||
import { SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils"
|
||||
|
||||
jest.setTimeout(100000)
|
||||
|
||||
moduleIntegrationTestRunner({
|
||||
moduleName: Modules.FULFILLMENT,
|
||||
testSuite: ({ service }: SuiteOptions<IFulfillmentModuleService>) => {
|
||||
describe("Fulfillment Module Service", () => {
|
||||
describe("read", () => {
|
||||
it("should list service zones with a filter", async function () {
|
||||
const fulfillmentSet = await service.create({
|
||||
name: "test",
|
||||
type: "test-type",
|
||||
})
|
||||
|
||||
const createdZone1 = await service.createServiceZones({
|
||||
name: "test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
})
|
||||
const createdZone2 = await service.createServiceZones({
|
||||
name: "test2",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
geo_zones: [
|
||||
{
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "fr",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
let listedZones = await service.listServiceZones({
|
||||
name: createdZone2.name,
|
||||
})
|
||||
|
||||
expect(listedZones).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ id: createdZone2.id }),
|
||||
])
|
||||
)
|
||||
expect(listedZones).not.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ id: createdZone1.id }),
|
||||
])
|
||||
)
|
||||
|
||||
listedZones = await service.listServiceZones({
|
||||
geo_zones: { country_code: "fr" },
|
||||
})
|
||||
|
||||
expect(listedZones).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ id: createdZone2.id }),
|
||||
])
|
||||
)
|
||||
expect(listedZones).not.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ id: createdZone1.id }),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("mutations", () => {
|
||||
describe("on create", () => {
|
||||
it("should create a new service zone", async function () {
|
||||
const fulfillmentSet = await service.create({
|
||||
name: "test",
|
||||
type: "test-type",
|
||||
})
|
||||
|
||||
const data: CreateServiceZoneDTO = {
|
||||
name: "test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
geo_zones: [
|
||||
{
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "fr",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const serviceZone = await service.createServiceZones(data)
|
||||
|
||||
expect(serviceZone).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
name: data.name,
|
||||
geo_zones: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: (data.geo_zones![0] as GeoZoneDTO).type,
|
||||
country_code: (data.geo_zones![0] as GeoZoneDTO)
|
||||
.country_code,
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should create a collection of service zones", async function () {
|
||||
const fulfillmentSet = await service.create({
|
||||
name: "test",
|
||||
type: "test-type",
|
||||
})
|
||||
|
||||
const data: CreateServiceZoneDTO[] = [
|
||||
{
|
||||
name: "test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
geo_zones: [
|
||||
{
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "fr",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "test2",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
geo_zones: [
|
||||
{
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "fr",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "test3",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
geo_zones: [
|
||||
{
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "uk",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const serviceZones = await service.createServiceZones(data)
|
||||
|
||||
expect(serviceZones).toHaveLength(3)
|
||||
|
||||
let i = 0
|
||||
for (const data_ of data) {
|
||||
expect(serviceZones[i]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
name: data_.name,
|
||||
geo_zones: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: (data_.geo_zones![0] as GeoZoneDTO).type,
|
||||
country_code: (data_.geo_zones![0] as GeoZoneDTO)
|
||||
.country_code,
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
++i
|
||||
}
|
||||
})
|
||||
|
||||
it("should fail on duplicated service zone name", async function () {
|
||||
const fulfillmentSet = await service.create({
|
||||
name: "test",
|
||||
type: "test-type",
|
||||
})
|
||||
|
||||
const data: CreateServiceZoneDTO = {
|
||||
name: "test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
geo_zones: [
|
||||
{
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "fr",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
await service.createServiceZones(data)
|
||||
const err = await service.createServiceZones(data).catch((e) => e)
|
||||
|
||||
expect(err).toBeDefined()
|
||||
expect(err.message).toContain("exists")
|
||||
})
|
||||
|
||||
it("should fail on creating a service zone and new geo zones that are not valid", async function () {
|
||||
const fulfillmentSet = await service.create({
|
||||
name: "test",
|
||||
type: "test-type",
|
||||
})
|
||||
|
||||
let data: CreateServiceZoneDTO = {
|
||||
name: "test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
geo_zones: [
|
||||
{
|
||||
type: GeoZoneType.PROVINCE,
|
||||
country_code: "fr",
|
||||
} as any,
|
||||
],
|
||||
}
|
||||
|
||||
let err = await service.createServiceZones(data).catch((e) => e)
|
||||
expect(err.message).toBe(
|
||||
"Missing required property province_code for geo zone type province"
|
||||
)
|
||||
|
||||
data = {
|
||||
name: "test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
geo_zones: [
|
||||
{
|
||||
type: GeoZoneType.CITY,
|
||||
country_code: "fr",
|
||||
province_code: "test",
|
||||
} as any,
|
||||
],
|
||||
}
|
||||
|
||||
err = await service.createServiceZones(data).catch((e) => e)
|
||||
expect(err.message).toBe(
|
||||
"Missing required property city for geo zone type city"
|
||||
)
|
||||
|
||||
data = {
|
||||
name: "test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
geo_zones: [
|
||||
{
|
||||
type: GeoZoneType.ZIP,
|
||||
postal_expression: "test",
|
||||
} as any,
|
||||
],
|
||||
}
|
||||
|
||||
err = await service.createServiceZones(data).catch((e) => e)
|
||||
expect(err.message).toBe(
|
||||
"Missing required property country_code for geo zone type zip"
|
||||
)
|
||||
|
||||
data = {
|
||||
name: "test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
geo_zones: [
|
||||
{
|
||||
type: "unknown",
|
||||
postal_expression: "test",
|
||||
} as any,
|
||||
],
|
||||
}
|
||||
|
||||
err = await service.createServiceZones(data).catch((e) => e)
|
||||
expect(err.message).toBe(`Invalid geo zone type: unknown`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("on update", () => {
|
||||
it("should update an existing service zone", async function () {
|
||||
const fulfillmentSet = await service.create({
|
||||
name: "test",
|
||||
type: "test-type",
|
||||
})
|
||||
|
||||
const createData: CreateServiceZoneDTO = {
|
||||
name: "service-zone-test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
geo_zones: [
|
||||
{
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "fr",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const createdServiceZone = await service.createServiceZones(
|
||||
createData
|
||||
)
|
||||
|
||||
const updateData = {
|
||||
name: "updated-service-zone-test",
|
||||
geo_zones: [
|
||||
{
|
||||
id: createdServiceZone.geo_zones[0].id,
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "us",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const updatedServiceZone = await service.updateServiceZones(
|
||||
createdServiceZone.id,
|
||||
updateData
|
||||
)
|
||||
|
||||
expect(updatedServiceZone).toEqual(
|
||||
expect.objectContaining({
|
||||
id: createdServiceZone.id,
|
||||
name: updateData.name,
|
||||
geo_zones: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: updateData.geo_zones[0].id,
|
||||
type: updateData.geo_zones[0].type,
|
||||
country_code: updateData.geo_zones[0].country_code,
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail on duplicated service zone name", async function () {
|
||||
const fulfillmentSet = await service.create({
|
||||
name: "test",
|
||||
type: "test-type",
|
||||
})
|
||||
|
||||
const createData: CreateServiceZoneDTO[] = [
|
||||
{
|
||||
name: "service-zone-test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
geo_zones: [
|
||||
{
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "fr",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "service-zone-test2",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
geo_zones: [
|
||||
{
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "us",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const createdServiceZones = await service.createServiceZones(
|
||||
createData
|
||||
)
|
||||
|
||||
const updateData: UpdateServiceZoneDTO = {
|
||||
name: "service-zone-test",
|
||||
geo_zones: [
|
||||
{
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "us",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const err = await service
|
||||
.updateServiceZones(createdServiceZones[1].id, updateData)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(err).toBeDefined()
|
||||
expect(err.message).toContain("exists")
|
||||
})
|
||||
})
|
||||
|
||||
describe("on upsert", () => {
|
||||
it("should upsert a collection of service zones", async function () {
|
||||
const fulfillmentSet = await service.create({
|
||||
name: "test",
|
||||
type: "test-type",
|
||||
})
|
||||
|
||||
const createData: CreateServiceZoneDTO[] = [
|
||||
{
|
||||
name: "service-zone-test",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
geo_zones: [
|
||||
{
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "fr",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "service-zone-test2",
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
geo_zones: [
|
||||
{
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: "us",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const createdServiceZones = await service.createServiceZones(
|
||||
createData
|
||||
)
|
||||
|
||||
const updateData: UpdateServiceZoneDTO[] = createdServiceZones.map(
|
||||
(serviceZone, index) => ({
|
||||
id: serviceZone.id,
|
||||
name: `updated-service-zone-test${index + 1}`,
|
||||
geo_zones: [
|
||||
{
|
||||
type: GeoZoneType.COUNTRY,
|
||||
country_code: index % 2 === 0 ? "us" : "fr",
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
const updatedServiceZones = await service.upsertServiceZones(
|
||||
updateData
|
||||
)
|
||||
|
||||
expect(updatedServiceZones).toHaveLength(2)
|
||||
|
||||
for (const data_ of updateData) {
|
||||
const expectedServiceZone = updatedServiceZones.find(
|
||||
(serviceZone) => serviceZone.id === data_.id
|
||||
)
|
||||
expect(expectedServiceZone).toEqual(
|
||||
expect.objectContaining({
|
||||
id: data_.id,
|
||||
name: data_.name,
|
||||
geo_zones: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: (data_.geo_zones![0] as GeoZoneDTO).type,
|
||||
country_code: (data_.geo_zones![0] as GeoZoneDTO)
|
||||
.country_code,
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
+1195
File diff suppressed because it is too large
Load Diff
+82
@@ -0,0 +1,82 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
CreateShippingProfileDTO,
|
||||
IFulfillmentModuleService,
|
||||
} from "@medusajs/types"
|
||||
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
|
||||
|
||||
jest.setTimeout(100000)
|
||||
|
||||
moduleIntegrationTestRunner({
|
||||
moduleName: Modules.FULFILLMENT,
|
||||
testSuite: ({ service }: SuiteOptions<IFulfillmentModuleService>) => {
|
||||
describe("Fulfillment Module Service", () => {
|
||||
describe("mutations", () => {
|
||||
describe("on create", () => {
|
||||
it("should create a new shipping profile", async function () {
|
||||
const createData: CreateShippingProfileDTO = {
|
||||
name: "test-default-profile",
|
||||
type: "default",
|
||||
}
|
||||
|
||||
const createdShippingProfile = await service.createShippingProfiles(
|
||||
createData
|
||||
)
|
||||
|
||||
expect(createdShippingProfile).toEqual(
|
||||
expect.objectContaining({
|
||||
name: createData.name,
|
||||
type: createData.type,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should create multiple new shipping profiles", async function () {
|
||||
const createData: CreateShippingProfileDTO[] = [
|
||||
{
|
||||
name: "test-profile-1",
|
||||
type: "default",
|
||||
},
|
||||
{
|
||||
name: "test-profile-2",
|
||||
type: "custom",
|
||||
},
|
||||
]
|
||||
|
||||
const createdShippingProfiles =
|
||||
await service.createShippingProfiles(createData)
|
||||
|
||||
expect(createdShippingProfiles).toHaveLength(2)
|
||||
|
||||
let i = 0
|
||||
for (const data_ of createData) {
|
||||
expect(createdShippingProfiles[i]).toEqual(
|
||||
expect.objectContaining({
|
||||
name: data_.name,
|
||||
type: data_.type,
|
||||
})
|
||||
)
|
||||
++i
|
||||
}
|
||||
})
|
||||
|
||||
it("should fail on duplicated shipping profile name", async function () {
|
||||
const createData: CreateShippingProfileDTO = {
|
||||
name: "test-default-profile",
|
||||
type: "default",
|
||||
}
|
||||
|
||||
await service.createShippingProfiles(createData)
|
||||
|
||||
const err = await service
|
||||
.createShippingProfiles(createData)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(err).toBeDefined()
|
||||
expect(err.message).toContain("exists")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
module.exports = {
|
||||
moduleNameMapper: {
|
||||
"^@models": "<rootDir>/src/models",
|
||||
"^@services": "<rootDir>/src/services",
|
||||
"^@repositories": "<rootDir>/src/repositories",
|
||||
"^@types": "<rootDir>/src/types",
|
||||
"^@utils": "<rootDir>/src/utils",
|
||||
},
|
||||
transform: {
|
||||
"^.+\\.[jt]s?$": [
|
||||
"ts-jest",
|
||||
{
|
||||
tsconfig: "tsconfig.spec.json",
|
||||
isolatedModules: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
testEnvironment: `node`,
|
||||
moduleFileExtensions: [`js`, `ts`],
|
||||
modulePathIgnorePatterns: ["dist/"],
|
||||
/*setupFilesAfterEnv: ["<rootDir>/integration-tests/setup.js"],*/
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import * as entities from "./src/models"
|
||||
import { TSMigrationGenerator } from "@medusajs/utils"
|
||||
|
||||
module.exports = {
|
||||
entities: Object.values(entities),
|
||||
schema: "public",
|
||||
clientUrl: "postgres://postgres@localhost/medusa-fulfillment",
|
||||
type: "postgresql",
|
||||
migrations: {
|
||||
generator: TSMigrationGenerator,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "@medusajs/fulfillment",
|
||||
"version": "0.1.2",
|
||||
"description": "Medusa Fulfillment module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"bin": {
|
||||
"medusa-fulfillment-seed": "dist/scripts/bin/run-seed.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/medusajs/medusa",
|
||||
"directory": "packages/fulfillment"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"author": "Medusa",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"watch": "tsc --build --watch",
|
||||
"watch:test": "tsc --build tsconfig.spec.json --watch",
|
||||
"prepublishOnly": "cross-env NODE_ENV=production tsc --build && tsc-alias -p tsconfig.json",
|
||||
"build": "rimraf dist && tsc --build && tsc-alias -p tsconfig.json",
|
||||
"test": "jest --runInBand --bail --forceExit --passWithNoTests -- src",
|
||||
"test:integration": "jest --forceExit -- integration-tests/**/__tests__/**/*.spec.ts",
|
||||
"migration:generate": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:generate",
|
||||
"migration:initial": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:create --initial -n InitialSetupMigration",
|
||||
"migration:create": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:create",
|
||||
"migration:up": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:up",
|
||||
"orm:cache:clear": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm cache:clear"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@medusajs/fulfillment-manual": "workspace:*",
|
||||
"@mikro-orm/cli": "5.9.7",
|
||||
"cross-env": "^5.2.1",
|
||||
"jest": "^29.6.3",
|
||||
"medusa-test-utils": "^1.1.44",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsc-alias": "^1.8.6",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@medusajs/modules-sdk": "^1.12.11",
|
||||
"@medusajs/types": "^1.11.16",
|
||||
"@medusajs/utils": "^1.11.9",
|
||||
"@mikro-orm/core": "5.9.7",
|
||||
"@mikro-orm/migrations": "5.9.7",
|
||||
"@mikro-orm/postgresql": "5.9.7",
|
||||
"awilix": "^8.0.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"knex": "2.4.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { moduleDefinition } from "./module-definition"
|
||||
import { initializeFactory, Modules } from "@medusajs/modules-sdk"
|
||||
|
||||
export * from "./types"
|
||||
export * from "./models"
|
||||
export * from "./services"
|
||||
|
||||
export const initialize = initializeFactory({
|
||||
moduleName: Modules.FULFILLMENT,
|
||||
moduleDefinition,
|
||||
})
|
||||
export const runMigrations = moduleDefinition.runMigrations
|
||||
export const revertMigration = moduleDefinition.revertMigration
|
||||
export default moduleDefinition
|
||||
@@ -0,0 +1,94 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import { MapToConfig } from "@medusajs/utils"
|
||||
import {
|
||||
Fulfillment,
|
||||
FulfillmentProvider,
|
||||
FulfillmentSet,
|
||||
GeoZone,
|
||||
ServiceZone,
|
||||
ShippingOption,
|
||||
ShippingOptionRule,
|
||||
ShippingProfile,
|
||||
} from "@models"
|
||||
|
||||
export const LinkableKeys: Record<string, string> = {
|
||||
fulfillment_id: Fulfillment.name,
|
||||
fulfillment_set_id: FulfillmentSet.name,
|
||||
shipping_option_id: ShippingOption.name,
|
||||
shipping_option_rule_id: ShippingOptionRule.name,
|
||||
}
|
||||
|
||||
const entityLinkableKeysMap: MapToConfig = {}
|
||||
Object.entries(LinkableKeys).forEach(([key, value]) => {
|
||||
entityLinkableKeysMap[value] ??= []
|
||||
entityLinkableKeysMap[value].push({
|
||||
mapTo: key,
|
||||
valueFrom: key.split("_").pop()!,
|
||||
})
|
||||
})
|
||||
|
||||
export const entityNameToLinkableKeysMap: MapToConfig = entityLinkableKeysMap
|
||||
|
||||
export const joinerConfig: ModuleJoinerConfig = {
|
||||
serviceName: Modules.FULFILLMENT,
|
||||
primaryKeys: ["id"],
|
||||
linkableKeys: LinkableKeys,
|
||||
alias: [
|
||||
{
|
||||
name: ["fulfillment_set", "fulfillment_sets"],
|
||||
args: {
|
||||
entity: FulfillmentSet.name,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["shipping_option", "shipping_options"],
|
||||
args: {
|
||||
entity: ShippingOption.name,
|
||||
methodSuffix: "ShippingOptions",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["shipping_profile", "shipping_profiles"],
|
||||
args: {
|
||||
entity: ShippingProfile.name,
|
||||
methodSuffix: "ShippingProfiles",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["fulfillment", "fulfillments"],
|
||||
args: {
|
||||
entity: Fulfillment.name,
|
||||
methodSuffix: "Fulfillments",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["fulfillment_provider", "fulfillment_providers"],
|
||||
args: {
|
||||
entity: FulfillmentProvider.name,
|
||||
methodSuffix: "FulfillmentProviders",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["service_zone", "service_zones"],
|
||||
args: {
|
||||
entity: ServiceZone.name,
|
||||
methodSuffix: "ServiceZones",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["geo_zone", "geo_zones"],
|
||||
args: {
|
||||
entity: GeoZone.name,
|
||||
methodSuffix: "GeoZones",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["shipping_option_rule", "shipping_option_rules"],
|
||||
args: {
|
||||
entity: ShippingOptionRule.name,
|
||||
methodSuffix: "ShippingOptionRules",
|
||||
},
|
||||
},
|
||||
],
|
||||
} as ModuleJoinerConfig
|
||||
@@ -0,0 +1,113 @@
|
||||
import { moduleProviderLoader } from "@medusajs/modules-sdk"
|
||||
import { LoaderOptions, ModuleProvider, ModulesSdkTypes } from "@medusajs/types"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
lowerCaseFirst,
|
||||
promiseAll,
|
||||
} from "@medusajs/utils"
|
||||
import { FulfillmentProviderService } from "@services"
|
||||
import { FulfillmentIdentifiersRegistrationName } from "@types"
|
||||
import { Lifetime, asFunction, asValue } from "awilix"
|
||||
|
||||
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) ?? console
|
||||
|
||||
try {
|
||||
const providerIdentifiers: string[] = (
|
||||
container.resolve(FulfillmentIdentifiersRegistrationName) ?? []
|
||||
).filter(Boolean)
|
||||
|
||||
const providerService: ModulesSdkTypes.InternalModuleService<any> =
|
||||
container.resolve(providerServiceRegistrationKey)
|
||||
|
||||
const providers = await providerService.list({})
|
||||
const loadedProvidersMap = new Map(providers.map((p) => [p.id, p]))
|
||||
|
||||
const providersToCreate = providerIdentifiers.filter(
|
||||
(id) => !loadedProvidersMap.has(id)
|
||||
)
|
||||
const providersToEnabled = providerIdentifiers.filter((id) =>
|
||||
loadedProvidersMap.has(id)
|
||||
)
|
||||
const providersToDisable = providers.filter(
|
||||
(p) => !providerIdentifiers.includes(p.id)
|
||||
)
|
||||
|
||||
const promises: Promise<any>[] = []
|
||||
|
||||
if (providersToCreate.length) {
|
||||
promises.push(
|
||||
providerService.create(providersToCreate.map((id) => ({ id })))
|
||||
)
|
||||
}
|
||||
|
||||
if (providersToEnabled.length) {
|
||||
promises.push(
|
||||
providerService.update(
|
||||
providersToEnabled.map((id) => ({ id, is_enabled: true }))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (providersToDisable.length) {
|
||||
promises.push(
|
||||
providerService.update(
|
||||
providersToDisable.map((p) => ({ id: p.id, is_enabled: false }))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
await promiseAll(promises)
|
||||
} catch (error) {
|
||||
logger.error(`Error syncing the fulfillment providers: ${error.message}`)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+603
@@ -0,0 +1,603 @@
|
||||
import { Migration } from "@mikro-orm/migrations"
|
||||
|
||||
export class Migration20240311145700_InitialSetupMigration extends Migration {
|
||||
async up(): Promise<void> {
|
||||
const shippingOptionTable = await this.execute(
|
||||
`SELECT * FROM information_schema.tables where table_name = 'shipping_option' and table_schema = 'public';`
|
||||
)
|
||||
|
||||
if (!shippingOptionTable.length) {
|
||||
await migrateUpModuleMigration.call(this)
|
||||
} else {
|
||||
await migrateUpBackwardCompatibility.call(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This migration is for the initial setup of the fulfillment module in the case of
|
||||
* an already existing database. It will check if the `shipping_option` table exists and in that case
|
||||
* assume that all other associated tables exists and will migrate them all.
|
||||
*
|
||||
* This migration does not take into account the data migration part which
|
||||
* should be handled separately. Also, after the data migration
|
||||
* the tables should be cleaned up from the old columns.
|
||||
**/
|
||||
async function migrateUpBackwardCompatibility(
|
||||
this: Migration20240311145700_InitialSetupMigration
|
||||
) {
|
||||
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(`
|
||||
alter table fulfillment_provider
|
||||
alter column id type text using id::text;
|
||||
|
||||
alter table fulfillment_provider
|
||||
add is_enabled boolean default true 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_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;'
|
||||
)
|
||||
this.addSql(
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_service_zone_deleted_at" ON "service_zone" (deleted_at) WHERE deleted_at IS NOT NULL;'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'create table if not exists "geo_zone" ("id" text not null, "type" text check ("type" in (\'country\', \'province\', \'city\', \'zip\')) not null default \'country\', "country_code" text not null, "province_code" text null, "city" text null, "service_zone_id" text not null, "postal_expression" jsonb null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "geo_zone_pkey" primary key ("id"));'
|
||||
)
|
||||
this.addSql(
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_country_code" ON "geo_zone" (country_code) WHERE deleted_at IS NULL;'
|
||||
)
|
||||
this.addSql(
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_province_code" ON "geo_zone" (province_code) WHERE deleted_at IS NULL AND province_code IS NOT NULL;'
|
||||
)
|
||||
this.addSql(
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_city" ON "geo_zone" (city) WHERE deleted_at IS NULL AND city IS NOT NULL;'
|
||||
)
|
||||
this.addSql(
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_service_zone_id" ON "geo_zone" (service_zone_id) WHERE deleted_at IS NULL;'
|
||||
)
|
||||
this.addSql(
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_deleted_at" ON "geo_zone" (deleted_at) WHERE deleted_at IS NOT NULL;'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'create table if not exists "shipping_option_type" ("id" text not null, "label" text not null, "description" text null, "code" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_type_pkey" primary key ("id"));'
|
||||
)
|
||||
this.addSql(
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_type_deleted_at" ON "shipping_option_type" (deleted_at) WHERE deleted_at IS NOT NULL;'
|
||||
)
|
||||
|
||||
this.addSql(`
|
||||
alter table shipping_profile
|
||||
alter column id type text using id::text;
|
||||
|
||||
alter table shipping_profile
|
||||
alter column name type text using name::text;
|
||||
|
||||
alter table shipping_profile
|
||||
alter column type type text using type::text;
|
||||
|
||||
create unique index if not exists "IDX_shipping_profile_name_unique"
|
||||
on shipping_profile (name)
|
||||
where (deleted_at IS NULL);
|
||||
|
||||
create index if not exists "IDX_shipping_profile_deleted_at"
|
||||
on shipping_profile (deleted_at)
|
||||
where (deleted_at IS NOT NULL);
|
||||
`)
|
||||
|
||||
this.addSql(`
|
||||
alter table shipping_option
|
||||
alter column id type text using id::text;
|
||||
|
||||
alter table shipping_option
|
||||
alter column name type text using name::text;
|
||||
|
||||
alter table shipping_option
|
||||
add service_zone_id text not null;
|
||||
|
||||
alter table shipping_option
|
||||
RENAME profile_id TO shipping_profile_id;
|
||||
|
||||
alter table shipping_option
|
||||
alter column region_id drop not null;
|
||||
|
||||
alter table shipping_option
|
||||
alter column price_type type text using price_type::text;
|
||||
|
||||
alter table shipping_option
|
||||
alter column price_type set default 'calculated'::text;
|
||||
|
||||
alter table shipping_option
|
||||
alter column data drop not null;
|
||||
|
||||
alter table shipping_option
|
||||
add shipping_option_type_id text not null;
|
||||
|
||||
drop index if exists "IDX_5c58105f1752fca0f4ce69f466";
|
||||
|
||||
drop index if exists "IDX_c951439af4c98bf2bd7fb8726c";
|
||||
|
||||
drop index if exists "IDX_a0e206bfaed3cb63c186091734";
|
||||
|
||||
create index if not exists "IDX_shipping_option_service_zone_id"
|
||||
on shipping_option (service_zone_id)
|
||||
where (deleted_at IS NULL);
|
||||
|
||||
create index if not exists "IDX_shipping_option_shipping_profile_id"
|
||||
on shipping_option (shipping_profile_id)
|
||||
where (deleted_at IS NULL);
|
||||
|
||||
create index if not exists "IDX_shipping_option_provider_id"
|
||||
on shipping_option (provider_id)
|
||||
where (deleted_at IS NULL);
|
||||
|
||||
create index if not exists "IDX_shipping_option_shipping_option_type_id"
|
||||
on shipping_option (shipping_option_type_id)
|
||||
where (deleted_at IS NULL);
|
||||
|
||||
create index if not exists "IDX_shipping_option_deleted_at"
|
||||
on shipping_option (deleted_at)
|
||||
where (deleted_at IS NOT NULL);
|
||||
|
||||
alter table shipping_option
|
||||
add constraint shipping_option_shipping_option_type_id_unique
|
||||
unique (shipping_option_type_id);
|
||||
|
||||
alter table shipping_option
|
||||
drop constraint "FK_5c58105f1752fca0f4ce69f4663";
|
||||
|
||||
alter table shipping_option
|
||||
drop constraint "FK_c951439af4c98bf2bd7fb8726cd";
|
||||
|
||||
alter table shipping_option
|
||||
drop constraint "FK_a0e206bfaed3cb63c1860917347";
|
||||
|
||||
alter table shipping_option
|
||||
add constraint shipping_option_service_zone_id_foreign
|
||||
foreign key (service_zone_id) references service_zone
|
||||
on update cascade on delete cascade;
|
||||
|
||||
alter table shipping_option
|
||||
add constraint shipping_option_shipping_profile_id_foreign
|
||||
foreign key (shipping_profile_id) references shipping_profile
|
||||
on update cascade on delete set null;
|
||||
|
||||
alter table shipping_option
|
||||
add constraint shipping_option_provider_id_foreign
|
||||
foreign key (provider_id) references fulfillment_provider
|
||||
on update cascade on delete set null;
|
||||
|
||||
alter table shipping_option
|
||||
add constraint shipping_option_shipping_option_type_id_foreign
|
||||
foreign key (shipping_option_type_id) references shipping_option_type
|
||||
on update cascade on delete cascade;
|
||||
|
||||
alter table shipping_option
|
||||
drop constraint "CHK_7a367f5901ae0a5b0df75aee38";
|
||||
|
||||
alter table shipping_option
|
||||
add constraint shipping_option_price_type_check
|
||||
check (price_type = ANY (ARRAY ['calculated'::text, 'flat'::text]));
|
||||
`)
|
||||
|
||||
this.addSql(
|
||||
'create table if not exists "shipping_option_rule" ("id" text not null, "attribute" text not null, "operator" text check ("operator" in (\'in\', \'eq\', \'ne\', \'gt\', \'gte\', \'lt\', \'lte\', \'nin\')) not null, "value" jsonb null, "shipping_option_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_rule_pkey" primary key ("id"));'
|
||||
)
|
||||
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(`
|
||||
alter table fulfillment
|
||||
alter
|
||||
column id type text using id::text;
|
||||
|
||||
alter table fulfillment
|
||||
add packed_at timestamp with time zone;
|
||||
|
||||
alter table fulfillment
|
||||
alter column data drop not null;
|
||||
|
||||
alter table fulfillment
|
||||
add delivered_at timestamp with time zone;
|
||||
|
||||
alter table fulfillment
|
||||
add shipping_option_id text;
|
||||
|
||||
alter table fulfillment
|
||||
add delivery_address_id text;
|
||||
|
||||
alter table fulfillment
|
||||
alter
|
||||
column provider_id type text using provider_id::text;
|
||||
|
||||
alter table fulfillment
|
||||
add deleted_at timestamp with time zone;
|
||||
|
||||
alter table fulfillment
|
||||
alter
|
||||
column location_id type text using location_id::text;
|
||||
|
||||
alter table fulfillment
|
||||
alter column location_id set not null;
|
||||
|
||||
drop index if exists "IDX_d73e55964e0ff2db8f03807d52";
|
||||
|
||||
drop index if exists "IDX_a52e234f729db789cf473297a5";
|
||||
|
||||
drop index if exists "IDX_f129acc85e346a10eed12b86fc";
|
||||
|
||||
drop index if exists "IDX_beb35a6de60a6c4f91d5ae57e4";
|
||||
|
||||
create index if not exists "IDX_fulfillment_location_id"
|
||||
on fulfillment (location_id) where (deleted_at IS NULL);
|
||||
|
||||
create index if not exists"IDX_fulfillment_provider_id"
|
||||
on fulfillment (provider_id) where (deleted_at IS NULL);
|
||||
|
||||
create index if not exists"IDX_fulfillment_shipping_option_id"
|
||||
on fulfillment (shipping_option_id) where (deleted_at IS NULL);
|
||||
|
||||
create index if not exists"IDX_fulfillment_deleted_at"
|
||||
on fulfillment (deleted_at) where (deleted_at IS NOT NULL);
|
||||
|
||||
alter table fulfillment
|
||||
add constraint fulfillment_delivery_address_id_unique
|
||||
unique (delivery_address_id);
|
||||
|
||||
alter table fulfillment
|
||||
drop
|
||||
constraint "FK_a52e234f729db789cf473297a5c"; /* swap id */
|
||||
|
||||
alter table fulfillment
|
||||
drop
|
||||
constraint "FK_f129acc85e346a10eed12b86fca"; /* order id */
|
||||
|
||||
alter table fulfillment
|
||||
drop
|
||||
constraint "FK_beb35a6de60a6c4f91d5ae57e44"; /* provider id */
|
||||
|
||||
alter table fulfillment
|
||||
drop
|
||||
constraint "FK_d73e55964e0ff2db8f03807d52e"; /* claim id */
|
||||
|
||||
alter table fulfillment
|
||||
add constraint fulfillment_provider_id_foreign
|
||||
foreign key (provider_id) references fulfillment_provider
|
||||
on update cascade on delete set null;
|
||||
|
||||
alter table fulfillment
|
||||
add constraint fulfillment_shipping_option_id_foreign
|
||||
foreign key (shipping_option_id) references shipping_option
|
||||
on update cascade on delete set null;
|
||||
|
||||
alter table fulfillment
|
||||
add constraint fulfillment_delivery_address_id_foreign
|
||||
foreign key (delivery_address_id) references fulfillment_address
|
||||
on update cascade on delete cascade;
|
||||
`)
|
||||
|
||||
this.addSql(
|
||||
'create table if not exists "fulfillment_label" ("id" text not null, "tracking_number" text not null, "tracking_url" text not null, "label_url" text not 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_label_pkey" primary key ("id"));'
|
||||
)
|
||||
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(`
|
||||
alter table fulfillment_item
|
||||
add id text not null;
|
||||
|
||||
alter table fulfillment_item
|
||||
add title text not null;
|
||||
|
||||
alter table fulfillment_item
|
||||
add sku text not null;
|
||||
|
||||
alter table fulfillment_item
|
||||
add barcode text not null;
|
||||
|
||||
alter table fulfillment_item
|
||||
alter column quantity type numeric using quantity::numeric;
|
||||
|
||||
alter table fulfillment_item
|
||||
add raw_quantity jsonb not null;
|
||||
|
||||
alter table fulfillment_item
|
||||
add line_item_id text;
|
||||
|
||||
alter table fulfillment_item
|
||||
add inventory_item_id text;
|
||||
|
||||
alter table fulfillment_item
|
||||
alter column fulfillment_id type text using fulfillment_id::text;
|
||||
|
||||
alter table fulfillment_item
|
||||
add created_at timestamp with time zone default now() not null;
|
||||
|
||||
alter table fulfillment_item
|
||||
add updated_at timestamp with time zone default now() not null;
|
||||
|
||||
alter table fulfillment_item
|
||||
add deleted_at timestamp with time zone;
|
||||
|
||||
create index if not exists "IDX_fulfillment_item_line_item_id"
|
||||
on fulfillment_item (line_item_id)
|
||||
where (deleted_at IS NULL);
|
||||
|
||||
create index if not exists "IDX_fulfillment_item_inventory_item_id"
|
||||
on fulfillment_item (inventory_item_id)
|
||||
where (deleted_at IS NULL);
|
||||
|
||||
create index if not exists "IDX_fulfillment_item_fulfillment_id"
|
||||
on fulfillment_item (fulfillment_id)
|
||||
where (deleted_at IS NULL);
|
||||
|
||||
create index if not exists "IDX_fulfillment_item_deleted_at"
|
||||
on fulfillment_item (deleted_at)
|
||||
where (deleted_at IS NOT NULL);
|
||||
|
||||
alter table fulfillment_item
|
||||
drop constraint "FK_a033f83cc6bd7701a5687ab4b38"; /* fulfillment id */
|
||||
|
||||
alter table fulfillment_item
|
||||
drop constraint "FK_e13ff60e74206b747a1896212d1"; /* item id */
|
||||
|
||||
alter table fulfillment_item
|
||||
drop constraint "PK_bc3e8a388de75db146a249922e0"; /* item id + fulfillment id PK */
|
||||
|
||||
alter table fulfillment_item
|
||||
alter column item_id drop not null;
|
||||
|
||||
alter table fulfillment_item
|
||||
add constraint fulfillment_item_fulfillment_id_foreign
|
||||
foreign key (fulfillment_id) references fulfillment
|
||||
on update cascade on delete cascade;
|
||||
|
||||
alter table "fulfillment_item" add primary key ("id");
|
||||
`)
|
||||
|
||||
this.addSql(
|
||||
'alter table if exists "service_zone" add constraint "service_zone_fulfillment_set_id_foreign" foreign key ("fulfillment_set_id") references "fulfillment_set" ("id") on update cascade on delete cascade;'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'alter table if exists "geo_zone" add constraint "geo_zone_service_zone_id_foreign" foreign key ("service_zone_id") references "service_zone" ("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 on delete 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 migration is for be initial setup of the fulfillment module in the case of
|
||||
* a new database. It will create all the necessary tables and indexes.
|
||||
*/
|
||||
async function migrateUpModuleMigration(
|
||||
this: Migration20240311145700_InitialSetupMigration
|
||||
) {
|
||||
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, "is_enabled" boolean not null default true, constraint "fulfillment_provider_pkey" primary key ("id"));'
|
||||
)
|
||||
|
||||
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_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;'
|
||||
)
|
||||
this.addSql(
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_service_zone_deleted_at" ON "service_zone" (deleted_at) WHERE deleted_at IS NOT NULL;'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'create table if not exists "geo_zone" ("id" text not null, "type" text check ("type" in (\'country\', \'province\', \'city\', \'zip\')) not null default \'country\', "country_code" text not null, "province_code" text null, "city" text null, "service_zone_id" text not null, "postal_expression" jsonb null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "geo_zone_pkey" primary key ("id"));'
|
||||
)
|
||||
this.addSql(
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_country_code" ON "geo_zone" (country_code) WHERE deleted_at IS NULL;'
|
||||
)
|
||||
this.addSql(
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_province_code" ON "geo_zone" (province_code) WHERE deleted_at IS NULL AND province_code IS NOT NULL;'
|
||||
)
|
||||
this.addSql(
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_city" ON "geo_zone" (city) WHERE deleted_at IS NULL AND city IS NOT NULL;'
|
||||
)
|
||||
this.addSql(
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_service_zone_id" ON "geo_zone" (service_zone_id) WHERE deleted_at IS NULL;'
|
||||
)
|
||||
this.addSql(
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_deleted_at" ON "geo_zone" (deleted_at) WHERE deleted_at IS NOT NULL;'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'create table if not exists "shipping_option_type" ("id" text not null, "label" text not null, "description" text null, "code" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_type_pkey" primary key ("id"));'
|
||||
)
|
||||
this.addSql(
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_type_deleted_at" ON "shipping_option_type" (deleted_at) WHERE deleted_at IS NOT NULL;'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'create table if not exists "shipping_profile" ("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 "shipping_profile_pkey" primary key ("id"));'
|
||||
)
|
||||
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 \'flat\', "service_zone_id" text not null, "shipping_profile_id" text null, "provider_id" text null, "data" jsonb null, "metadata" jsonb null, "shipping_option_type_id" text not 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_provider_id" ON "shipping_option" (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;'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'create table if not exists "shipping_option_rule" ("id" text not null, "attribute" text not null, "operator" text check ("operator" in (\'in\', \'eq\', \'ne\', \'gt\', \'gte\', \'lt\', \'lte\', \'nin\')) not null, "value" jsonb null, "shipping_option_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_rule_pkey" primary key ("id"));'
|
||||
)
|
||||
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 null, "shipping_option_id" text null, "metadata" jsonb null, "delivery_address_id" text 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;'
|
||||
)
|
||||
this.addSql(
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_deleted_at" ON "fulfillment" (deleted_at) WHERE deleted_at IS NOT NULL;'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'create table if not exists "fulfillment_label" ("id" text not null, "tracking_number" text not null, "tracking_url" text not null, "label_url" text not 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_label_pkey" primary key ("id"));'
|
||||
)
|
||||
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, "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;'
|
||||
)
|
||||
this.addSql(
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_deleted_at" ON "fulfillment_item" (deleted_at) WHERE deleted_at IS NOT NULL;'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'alter table if exists "service_zone" add constraint "service_zone_fulfillment_set_id_foreign" foreign key ("fulfillment_set_id") references "fulfillment_set" ("id") on update cascade on delete cascade;'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'alter table if exists "geo_zone" add constraint "geo_zone_service_zone_id_foreign" foreign key ("service_zone_id") references "service_zone" ("id") on update cascade on delete cascade;'
|
||||
)
|
||||
|
||||
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 on delete 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_provider_id_foreign" foreign key ("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 on delete 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 on delete set null;'
|
||||
)
|
||||
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_delivery_address_id_foreign" foreign key ("delivery_address_id") references "fulfillment_address" ("id") on update cascade on delete 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 on delete cascade;'
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { DAL } from "@medusajs/types"
|
||||
import {
|
||||
createPsqlIndexStatementHelper,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
|
||||
type OptionalAddressProps = DAL.SoftDeletableEntityDateColumns
|
||||
|
||||
const FulfillmentDeletedAtIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "fulfillment_address",
|
||||
columns: "deleted_at",
|
||||
where: "deleted_at IS NOT NULL",
|
||||
})
|
||||
|
||||
@Entity({ tableName: "fulfillment_address" })
|
||||
export default class Address {
|
||||
[OptionalProps]: OptionalAddressProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id!: string
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
company: string | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
first_name: string | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
last_name: string | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
address_1: string | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
address_2: string | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
city: string | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
country_code: string | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
province: string | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
postal_code: string | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
phone: string | null = null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
@FulfillmentDeletedAtIndex.MikroORMIndex()
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "fuladdr")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "fuladdr")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import {
|
||||
BigNumber,
|
||||
createPsqlIndexStatementHelper,
|
||||
DALUtils,
|
||||
generateEntityId,
|
||||
MikroOrmBigNumberProperty,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import { BigNumberRawValue, DAL } from "@medusajs/types"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
Filter,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import Fulfillment from "./fulfillment"
|
||||
|
||||
type FulfillmentItemOptionalProps = DAL.SoftDeletableEntityDateColumns
|
||||
|
||||
const FulfillmentIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "fulfillment_item",
|
||||
columns: "fulfillment_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
const LineItemIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "fulfillment_item",
|
||||
columns: "line_item_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
const InventoryItemIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "fulfillment_item",
|
||||
columns: "inventory_item_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
const FulfillmentItemDeletedAtIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "fulfillment_item",
|
||||
columns: "deleted_at",
|
||||
where: "deleted_at IS NOT NULL",
|
||||
})
|
||||
|
||||
@Entity()
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class FulfillmentItem {
|
||||
[OptionalProps]?: FulfillmentItemOptionalProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
title: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
sku: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
barcode: string
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
quantity: BigNumber | number
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_quantity: BigNumberRawValue
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
@LineItemIdIndex.MikroORMIndex()
|
||||
line_item_id: string | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
@InventoryItemIdIndex.MikroORMIndex()
|
||||
inventory_item_id: string | null = null
|
||||
|
||||
@ManyToOne(() => Fulfillment, {
|
||||
columnType: "text",
|
||||
mapToPk: true,
|
||||
fieldName: "fulfillment_id",
|
||||
onDelete: "cascade",
|
||||
})
|
||||
@FulfillmentIdIndex.MikroORMIndex()
|
||||
fulfillment_id: string
|
||||
|
||||
@ManyToOne(() => Fulfillment, { persist: false })
|
||||
fulfillment: Fulfillment
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@FulfillmentItemDeletedAtIndex.MikroORMIndex()
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import {
|
||||
createPsqlIndexStatementHelper,
|
||||
DALUtils,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import { DAL } from "@medusajs/types"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
Filter,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import Fulfillment from "./fulfillment"
|
||||
|
||||
type FulfillmentLabelOptionalProps = DAL.SoftDeletableEntityDateColumns
|
||||
|
||||
const FulfillmentIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "fulfillment_label",
|
||||
columns: "fulfillment_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
const DeletedAtIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "fulfillment_label",
|
||||
columns: "deleted_at",
|
||||
where: "deleted_at IS NOT NULL",
|
||||
})
|
||||
|
||||
@Entity()
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class FulfillmentLabel {
|
||||
[OptionalProps]?: FulfillmentLabelOptionalProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
tracking_number: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
tracking_url: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
label_url: string
|
||||
|
||||
@ManyToOne(() => Fulfillment, {
|
||||
columnType: "text",
|
||||
mapToPk: true,
|
||||
fieldName: "fulfillment_id",
|
||||
onDelete: "cascade",
|
||||
})
|
||||
@FulfillmentIdIndex.MikroORMIndex()
|
||||
fulfillment_id: string
|
||||
|
||||
@ManyToOne(() => Fulfillment, { persist: false })
|
||||
fulfillment: Fulfillment
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
@DeletedAtIndex.MikroORMIndex()
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { generateEntityId } from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
OnInit,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
|
||||
@Entity()
|
||||
export default class FulfillmentProvider {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "boolean", defaultRaw: "true" })
|
||||
is_enabled: boolean = true
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "serpro")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "serpro")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
createPsqlIndexStatementHelper,
|
||||
DALUtils,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import { DAL } from "@medusajs/types"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Cascade,
|
||||
Collection,
|
||||
Entity,
|
||||
Filter,
|
||||
OneToMany,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import ServiceZone from "./service-zone"
|
||||
|
||||
type FulfillmentSetOptionalProps = DAL.SoftDeletableEntityDateColumns
|
||||
|
||||
const DeletedAtIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "fulfillment_set",
|
||||
columns: "deleted_at",
|
||||
where: "deleted_at IS NOT NULL",
|
||||
})
|
||||
|
||||
const NameIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "fulfillment_set",
|
||||
columns: "name",
|
||||
unique: true,
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
@Entity()
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class FulfillmentSet {
|
||||
[OptionalProps]?: FulfillmentSetOptionalProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
@NameIndex.MikroORMIndex()
|
||||
name: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
type: string
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@OneToMany(() => ServiceZone, "fulfillment_set", {
|
||||
cascade: [Cascade.PERSIST, "soft-remove"] as any,
|
||||
orphanRemoval: true,
|
||||
})
|
||||
service_zones = new Collection<ServiceZone>(this)
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
@DeletedAtIndex.MikroORMIndex()
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "fuset")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "fuset")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
import {
|
||||
createPsqlIndexStatementHelper,
|
||||
DALUtils,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import { DAL } from "@medusajs/types"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Cascade,
|
||||
Collection,
|
||||
Entity,
|
||||
Filter,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import Address from "./address"
|
||||
import FulfillmentItem from "./fulfillment-item"
|
||||
import FulfillmentLabel from "./fulfillment-label"
|
||||
import FulfillmentProvider from "./fulfillment-provider"
|
||||
import ShippingOption from "./shipping-option"
|
||||
|
||||
type FulfillmentOptionalProps = DAL.SoftDeletableEntityDateColumns
|
||||
|
||||
const FulfillmentDeletedAtIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "fulfillment",
|
||||
columns: "deleted_at",
|
||||
where: "deleted_at IS NOT NULL",
|
||||
})
|
||||
|
||||
const FulfillmentProviderIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "fulfillment",
|
||||
columns: "provider_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
const FulfillmentLocationIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "fulfillment",
|
||||
columns: "location_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
const FulfillmentShippingOptionIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "fulfillment",
|
||||
columns: "shipping_option_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
@Entity()
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class Fulfillment {
|
||||
[OptionalProps]?: FulfillmentOptionalProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
@FulfillmentLocationIdIndex.MikroORMIndex()
|
||||
location_id: string
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
})
|
||||
packed_at: Date | null = null
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
})
|
||||
shipped_at: Date | null = null
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
})
|
||||
delivered_at: Date | null = null
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
})
|
||||
canceled_at: Date | null = null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
data: Record<string, unknown> | null = null
|
||||
|
||||
@ManyToOne(() => FulfillmentProvider, {
|
||||
columnType: "text",
|
||||
fieldName: "provider_id",
|
||||
mapToPk: true,
|
||||
nullable: true,
|
||||
onDelete: "set null",
|
||||
})
|
||||
@FulfillmentProviderIdIndex.MikroORMIndex()
|
||||
provider_id: string
|
||||
|
||||
@ManyToOne(() => ShippingOption, {
|
||||
columnType: "text",
|
||||
fieldName: "shipping_option_id",
|
||||
nullable: true,
|
||||
mapToPk: true,
|
||||
onDelete: "set null",
|
||||
})
|
||||
@FulfillmentShippingOptionIdIndex.MikroORMIndex()
|
||||
shipping_option_id: string | null = null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@ManyToOne(() => ShippingOption, { persist: false })
|
||||
shipping_option: ShippingOption | null
|
||||
|
||||
@ManyToOne(() => FulfillmentProvider, { persist: false })
|
||||
provider: FulfillmentProvider
|
||||
|
||||
@OneToOne({
|
||||
entity: () => Address,
|
||||
owner: true,
|
||||
cascade: [Cascade.PERSIST, "soft-remove"] as any,
|
||||
nullable: true,
|
||||
onDelete: "cascade",
|
||||
})
|
||||
delivery_address!: Address
|
||||
|
||||
@OneToMany(() => FulfillmentItem, (item) => item.fulfillment, {
|
||||
cascade: [Cascade.PERSIST, "soft-remove"] as any,
|
||||
orphanRemoval: true,
|
||||
})
|
||||
items = new Collection<FulfillmentItem>(this)
|
||||
|
||||
@OneToMany(() => FulfillmentLabel, (label) => label.fulfillment, {
|
||||
cascade: [Cascade.PERSIST, "soft-remove"] as any,
|
||||
orphanRemoval: true,
|
||||
})
|
||||
labels = new Collection<FulfillmentLabel>(this)
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@FulfillmentDeletedAtIndex.MikroORMIndex()
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "ful")
|
||||
this.provider_id ??= this.provider_id ?? this.provider?.id
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "ful")
|
||||
this.provider_id ??= this.provider_id ?? this.provider?.id
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import {
|
||||
createPsqlIndexStatementHelper,
|
||||
DALUtils,
|
||||
generateEntityId,
|
||||
GeoZoneType,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import { DAL } from "@medusajs/types"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
Enum,
|
||||
Filter,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import ServiceZone from "./service-zone"
|
||||
|
||||
type GeoZoneOptionalProps = DAL.SoftDeletableEntityDateColumns
|
||||
|
||||
const DeletedAtIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "geo_zone",
|
||||
columns: "deleted_at",
|
||||
where: "deleted_at IS NOT NULL",
|
||||
})
|
||||
|
||||
const CountryCodeIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "geo_zone",
|
||||
columns: "country_code",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
const ProvinceCodeIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "geo_zone",
|
||||
columns: "province_code",
|
||||
where: "deleted_at IS NULL AND province_code IS NOT NULL",
|
||||
})
|
||||
|
||||
const CityIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "geo_zone",
|
||||
columns: "city",
|
||||
where: "deleted_at IS NULL AND city IS NOT NULL",
|
||||
})
|
||||
|
||||
const ServiceZoneIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "geo_zone",
|
||||
columns: "service_zone_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
@Entity()
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class GeoZone {
|
||||
[OptionalProps]?: GeoZoneOptionalProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Enum({ items: () => GeoZoneType, default: GeoZoneType.COUNTRY })
|
||||
type: GeoZoneType
|
||||
|
||||
@CountryCodeIndex.MikroORMIndex()
|
||||
@Property({ columnType: "text" })
|
||||
country_code: string
|
||||
|
||||
@ProvinceCodeIndex.MikroORMIndex()
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
province_code: string | null = null
|
||||
|
||||
@CityIndex.MikroORMIndex()
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
city: string | null = null
|
||||
|
||||
@ManyToOne(() => ServiceZone, {
|
||||
type: "text",
|
||||
mapToPk: true,
|
||||
fieldName: "service_zone_id",
|
||||
onDelete: "cascade",
|
||||
})
|
||||
@ServiceZoneIdIndex.MikroORMIndex()
|
||||
service_zone_id: string
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
postal_expression: Record<string, unknown> | null = null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@ManyToOne(() => ServiceZone, {
|
||||
persist: false,
|
||||
})
|
||||
service_zone: ServiceZone
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@DeletedAtIndex.MikroORMIndex()
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, " fgz")
|
||||
this.service_zone_id ??= this.service_zone?.id
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "fgz")
|
||||
this.service_zone_id ??= this.service_zone?.id
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export { default as Address } from "./address"
|
||||
export { default as Fulfillment } from "./fulfillment"
|
||||
export { default as FulfillmentItem } from "./fulfillment-item"
|
||||
export { default as FulfillmentLabel } from "./fulfillment-label"
|
||||
export { default as FulfillmentProvider } from "./fulfillment-provider"
|
||||
export { default as FulfillmentSet } from "./fulfillment-set"
|
||||
export { default as GeoZone } from "./geo-zone"
|
||||
export { default as ServiceZone } from "./service-zone"
|
||||
export { default as ShippingOption } from "./shipping-option"
|
||||
export { default as ShippingOptionRule } from "./shipping-option-rule"
|
||||
export { default as ShippingOptionType } from "./shipping-option-type"
|
||||
export { default as ShippingProfile } from "./shipping-profile"
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import {
|
||||
createPsqlIndexStatementHelper,
|
||||
DALUtils,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import { DAL } from "@medusajs/types"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Cascade,
|
||||
Collection,
|
||||
Entity,
|
||||
Filter,
|
||||
Index,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import FulfillmentSet from "./fulfillment-set"
|
||||
import GeoZone from "./geo-zone"
|
||||
import ShippingOption from "./shipping-option"
|
||||
|
||||
type ServiceZoneOptionalProps = DAL.SoftDeletableEntityDateColumns
|
||||
|
||||
const deletedAtIndexName = "IDX_service_zone_deleted_at"
|
||||
const deletedAtIndexStatement = createPsqlIndexStatementHelper({
|
||||
name: deletedAtIndexName,
|
||||
tableName: "service_zone",
|
||||
columns: "deleted_at",
|
||||
where: "deleted_at IS NOT NULL",
|
||||
}).expression
|
||||
|
||||
const NameIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "service_zone",
|
||||
columns: "name",
|
||||
unique: true,
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
const FulfillmentSetIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "service_zone",
|
||||
columns: "fulfillment_set_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
@Entity()
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class ServiceZone {
|
||||
[OptionalProps]?: ServiceZoneOptionalProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
@NameIndex.MikroORMIndex()
|
||||
name: string
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@ManyToOne(() => FulfillmentSet, {
|
||||
type: "text",
|
||||
mapToPk: true,
|
||||
fieldName: "fulfillment_set_id",
|
||||
onDelete: "cascade",
|
||||
})
|
||||
@FulfillmentSetIdIndex.MikroORMIndex()
|
||||
fulfillment_set_id: string
|
||||
|
||||
@ManyToOne(() => FulfillmentSet, { persist: false })
|
||||
fulfillment_set: FulfillmentSet
|
||||
|
||||
@OneToMany(() => GeoZone, "service_zone", {
|
||||
cascade: [Cascade.PERSIST, "soft-remove"] as any,
|
||||
orphanRemoval: true,
|
||||
})
|
||||
geo_zones = new Collection<GeoZone>(this)
|
||||
|
||||
@OneToMany(
|
||||
() => ShippingOption,
|
||||
(shippingOption) => shippingOption.service_zone,
|
||||
{
|
||||
cascade: [Cascade.PERSIST, "soft-remove"] as any,
|
||||
orphanRemoval: true,
|
||||
}
|
||||
)
|
||||
shipping_options = new Collection<ShippingOption>(this)
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@Index({
|
||||
name: deletedAtIndexName,
|
||||
expression: deletedAtIndexStatement,
|
||||
})
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "serzo")
|
||||
this.fulfillment_set_id ??= this.fulfillment_set?.id
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "serzo")
|
||||
this.fulfillment_set_id ??= this.fulfillment_set?.id
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { DAL } from "@medusajs/types"
|
||||
import {
|
||||
createPsqlIndexStatementHelper,
|
||||
DALUtils,
|
||||
generateEntityId,
|
||||
RuleOperator,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
Enum,
|
||||
Filter,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import ShippingOption from "./shipping-option"
|
||||
|
||||
type ShippingOptionRuleOptionalProps = DAL.SoftDeletableEntityDateColumns
|
||||
|
||||
const DeletedAtIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "shipping_option_rule",
|
||||
columns: "deleted_at",
|
||||
where: "deleted_at IS NOT NULL",
|
||||
})
|
||||
|
||||
const ShippingOptionIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "shipping_option_rule",
|
||||
columns: "shipping_option_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
@Entity()
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class ShippingOptionRule {
|
||||
[OptionalProps]?: ShippingOptionRuleOptionalProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
attribute: string
|
||||
|
||||
@Enum({
|
||||
items: () => Object.values(RuleOperator),
|
||||
columnType: "text",
|
||||
})
|
||||
operator: Lowercase<keyof typeof RuleOperator>
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
value: string | string[] | null = null
|
||||
|
||||
@ManyToOne(() => ShippingOption, {
|
||||
type: "text",
|
||||
mapToPk: true,
|
||||
fieldName: "shipping_option_id",
|
||||
onDelete: "cascade",
|
||||
})
|
||||
@ShippingOptionIdIndex.MikroORMIndex()
|
||||
shipping_option_id: string
|
||||
|
||||
@ManyToOne(() => ShippingOption, {
|
||||
persist: false,
|
||||
})
|
||||
shipping_option: ShippingOption
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@DeletedAtIndex.MikroORMIndex()
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "sorul")
|
||||
this.shipping_option_id ??= this.shipping_option?.id
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "sorul")
|
||||
this.shipping_option_id ??= this.shipping_option?.id
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import {
|
||||
createPsqlIndexStatementHelper,
|
||||
DALUtils,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import { DAL } from "@medusajs/types"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
Filter,
|
||||
OneToOne,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import ShippingOption from "./shipping-option"
|
||||
|
||||
type ShippingOptionTypeOptionalProps = DAL.SoftDeletableEntityDateColumns
|
||||
|
||||
const DeletedAtIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "shipping_option_type",
|
||||
columns: "deleted_at",
|
||||
where: "deleted_at IS NOT NULL",
|
||||
})
|
||||
|
||||
const ShippingOptionIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "shipping_option_type",
|
||||
columns: "shipping_option_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
@Entity()
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class ShippingOptionType {
|
||||
[OptionalProps]?: ShippingOptionTypeOptionalProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
label: string
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
description: string | null = null
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
code: string
|
||||
|
||||
@OneToOne(() => ShippingOption, (so) => so.type, {
|
||||
type: "text",
|
||||
onDelete: "cascade",
|
||||
})
|
||||
shipping_option: ShippingOption
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@DeletedAtIndex.MikroORMIndex()
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "sotype")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "sotype")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
import {
|
||||
createPsqlIndexStatementHelper,
|
||||
DALUtils,
|
||||
generateEntityId,
|
||||
ShippingOptionPriceType,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import { DAL } from "@medusajs/types"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Cascade,
|
||||
Collection,
|
||||
Entity,
|
||||
Enum,
|
||||
Filter,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import Fulfillment from "./fulfillment"
|
||||
import FulfillmentProvider from "./fulfillment-provider"
|
||||
import ServiceZone from "./service-zone"
|
||||
import ShippingOptionRule from "./shipping-option-rule"
|
||||
import ShippingOptionType from "./shipping-option-type"
|
||||
import ShippingProfile from "./shipping-profile"
|
||||
|
||||
type ShippingOptionOptionalProps = DAL.SoftDeletableEntityDateColumns
|
||||
|
||||
const DeletedAtIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "shipping_option",
|
||||
columns: "deleted_at",
|
||||
where: "deleted_at IS NOT NULL",
|
||||
})
|
||||
|
||||
const ServiceZoneIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "shipping_option",
|
||||
columns: "service_zone_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
const ShippingProfileIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "shipping_option",
|
||||
columns: "shipping_profile_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
const FulfillmentProviderIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "shipping_option",
|
||||
columns: "provider_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
const ShippingOptionTypeIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "shipping_option",
|
||||
columns: "shipping_option_type_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
@Entity()
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class ShippingOption {
|
||||
[OptionalProps]?: ShippingOptionOptionalProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
name: string
|
||||
|
||||
@Enum({
|
||||
items: () => ShippingOptionPriceType,
|
||||
default: ShippingOptionPriceType.FLAT,
|
||||
})
|
||||
price_type: ShippingOptionPriceType
|
||||
|
||||
@ManyToOne(() => ServiceZone, {
|
||||
type: "text",
|
||||
fieldName: "service_zone_id",
|
||||
mapToPk: true,
|
||||
onDelete: "cascade",
|
||||
})
|
||||
@ServiceZoneIdIndex.MikroORMIndex()
|
||||
service_zone_id: string
|
||||
|
||||
@ManyToOne(() => ShippingProfile, {
|
||||
type: "text",
|
||||
fieldName: "shipping_profile_id",
|
||||
mapToPk: true,
|
||||
nullable: true,
|
||||
onDelete: "set null",
|
||||
})
|
||||
@ShippingProfileIdIndex.MikroORMIndex()
|
||||
shipping_profile_id: string | null
|
||||
|
||||
@ManyToOne(() => FulfillmentProvider, {
|
||||
type: "text",
|
||||
fieldName: "provider_id",
|
||||
mapToPk: true,
|
||||
nullable: true,
|
||||
})
|
||||
@FulfillmentProviderIdIndex.MikroORMIndex()
|
||||
provider_id: string
|
||||
|
||||
@Property({ columnType: "text", persist: false })
|
||||
@ShippingOptionTypeIdIndex.MikroORMIndex()
|
||||
shipping_option_type_id: string | null = null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
data: Record<string, unknown> | null = null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@ManyToOne(() => ServiceZone, { persist: false })
|
||||
service_zone: ServiceZone
|
||||
|
||||
@ManyToOne(() => ShippingProfile, {
|
||||
persist: false,
|
||||
})
|
||||
shipping_profile: ShippingProfile | null
|
||||
|
||||
@ManyToOne(() => FulfillmentProvider, {
|
||||
persist: false,
|
||||
})
|
||||
provider: FulfillmentProvider | null
|
||||
|
||||
@OneToOne(() => ShippingOptionType, (so) => so.shipping_option, {
|
||||
owner: true,
|
||||
cascade: [Cascade.PERSIST, "soft-remove"] as any,
|
||||
orphanRemoval: true,
|
||||
fieldName: "shipping_option_type_id",
|
||||
onDelete: "cascade",
|
||||
})
|
||||
type: ShippingOptionType
|
||||
|
||||
@OneToMany(() => ShippingOptionRule, "shipping_option", {
|
||||
cascade: [Cascade.PERSIST, "soft-remove"] as any,
|
||||
orphanRemoval: true,
|
||||
})
|
||||
rules = new Collection<ShippingOptionRule>(this)
|
||||
|
||||
@OneToMany(() => Fulfillment, (fulfillment) => fulfillment.shipping_option)
|
||||
fulfillments = new Collection<Fulfillment>(this)
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@DeletedAtIndex.MikroORMIndex()
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "so")
|
||||
this.shipping_option_type_id ??= this.type?.id
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "so")
|
||||
this.shipping_option_type_id ??= this.type?.id
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import {
|
||||
createPsqlIndexStatementHelper,
|
||||
DALUtils,
|
||||
generateEntityId,
|
||||
Searchable,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import { DAL } from "@medusajs/types"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Collection,
|
||||
Entity,
|
||||
Filter,
|
||||
OneToMany,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import ShippingOption from "./shipping-option"
|
||||
|
||||
type ShippingProfileOptionalProps = DAL.SoftDeletableEntityDateColumns
|
||||
|
||||
const DeletedAtIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "shipping_profile",
|
||||
columns: "deleted_at",
|
||||
where: "deleted_at IS NOT NULL",
|
||||
})
|
||||
|
||||
const ShippingProfileTypeIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "shipping_profile",
|
||||
columns: "name",
|
||||
unique: true,
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
@Entity()
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class ShippingProfile {
|
||||
[OptionalProps]?: ShippingProfileOptionalProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text" })
|
||||
@ShippingProfileTypeIndex.MikroORMIndex()
|
||||
name: string
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text" })
|
||||
type: string
|
||||
|
||||
@OneToMany(
|
||||
() => ShippingOption,
|
||||
(shippingOption) => shippingOption.shipping_profile
|
||||
)
|
||||
shipping_options = new Collection<ShippingOption>(this)
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@DeletedAtIndex.MikroORMIndex()
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "sp")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "sp")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { ModuleExports } from "@medusajs/types"
|
||||
import * as ModuleServices from "@services"
|
||||
import { FulfillmentModuleService } from "@services"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
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,
|
||||
models: ModuleModels,
|
||||
pathToMigrations: __dirname + "/migrations",
|
||||
}
|
||||
|
||||
const runMigrations = ModulesSdkUtils.buildMigrationScript(
|
||||
migrationScriptOptions
|
||||
)
|
||||
|
||||
const revertMigration = ModulesSdkUtils.buildRevertMigrationScript(
|
||||
migrationScriptOptions
|
||||
)
|
||||
|
||||
const containerLoader = ModulesSdkUtils.moduleContainerLoaderFactory({
|
||||
moduleModels: ModuleModels,
|
||||
moduleRepositories: ModuleRepositories,
|
||||
moduleServices: ModuleServices,
|
||||
})
|
||||
|
||||
const connectionLoader = ModulesSdkUtils.mikroOrmConnectionLoaderFactory({
|
||||
moduleName: Modules.FULFILLMENT,
|
||||
moduleModels: Object.values(ModuleModels),
|
||||
migrationsPath: __dirname + "/migrations",
|
||||
})
|
||||
|
||||
const service = FulfillmentModuleService
|
||||
const loaders = [containerLoader, connectionLoader, loadProviders]
|
||||
|
||||
export const moduleDefinition: ModuleExports = {
|
||||
service,
|
||||
loaders,
|
||||
revertMigration,
|
||||
runMigrations,
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
import { Context, FulfillmentTypes } from "@medusajs/types"
|
||||
import { DALUtils, promiseAll } from "@medusajs/utils"
|
||||
import { FulfillmentSet, ServiceZone } from "@models"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
|
||||
interface CreateFulfillmentSetDTO
|
||||
extends FulfillmentTypes.CreateFulfillmentSetDTO {
|
||||
service_zones: { id: string; name: string }[]
|
||||
}
|
||||
|
||||
export class FulfillmentSetRepository extends DALUtils.mikroOrmBaseRepositoryFactory<FulfillmentSet>(
|
||||
FulfillmentSet
|
||||
) {
|
||||
async update(
|
||||
data: {
|
||||
entity: FulfillmentSet
|
||||
update: FulfillmentTypes.FulfillmentSetDTO
|
||||
}[],
|
||||
context?: Context
|
||||
): Promise<FulfillmentSet[]> {
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
// init all service zones collections
|
||||
await promiseAll(
|
||||
data.map(async ({ entity }) => {
|
||||
return await entity.service_zones.init()
|
||||
})
|
||||
)
|
||||
|
||||
const flfillmentSetsToUpdate = data.map(({ entity, update }) => {
|
||||
const { service_zones, ...restToUpdate } = update
|
||||
|
||||
const currentServiceZones = entity.service_zones.getItems()
|
||||
const serviceZonesToDetach = currentServiceZones.filter(
|
||||
(serviceZone) =>
|
||||
!update.service_zones.find(
|
||||
(newServiceZone) => newServiceZone.id === serviceZone.id
|
||||
)
|
||||
)
|
||||
const serviceZonesToAttach = update.service_zones.filter(
|
||||
(newServiceZone) =>
|
||||
!currentServiceZones.find(
|
||||
(serviceZone) => serviceZone.id === newServiceZone.id
|
||||
)
|
||||
)
|
||||
|
||||
entity.service_zones.remove(serviceZonesToDetach)
|
||||
entity.service_zones.add(serviceZonesToAttach as unknown as ServiceZone[])
|
||||
|
||||
return manager.assign(entity, restToUpdate)
|
||||
})
|
||||
|
||||
manager.persist(flfillmentSetsToUpdate)
|
||||
|
||||
return flfillmentSetsToUpdate
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1 @@
|
||||
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import * as Models from "@models"
|
||||
import { EOL } from "os"
|
||||
|
||||
const args = process.argv
|
||||
const path = args.pop() as string
|
||||
|
||||
export default (async () => {
|
||||
const { config } = await import("dotenv")
|
||||
config()
|
||||
if (!path) {
|
||||
throw new Error(
|
||||
`filePath is required.${EOL}Example: medusa-fulfillment-seed <filePath>`
|
||||
)
|
||||
}
|
||||
|
||||
const run = ModulesSdkUtils.buildSeedScript({
|
||||
moduleName: Modules.FULFILLMENT,
|
||||
models: Models,
|
||||
pathToMigrations: __dirname + "/../../migrations",
|
||||
seedHandler: async ({ manager, data }) => {
|
||||
// TODO: Add seed logic
|
||||
},
|
||||
})
|
||||
await run({ path })
|
||||
})()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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(providerId: string, data: Record<string, unknown>) {
|
||||
const provider = this.retrieveProviderRegistration(providerId)
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as FulfillmentModuleService } from "./fulfillment-module-service"
|
||||
export { default as FulfillmentProviderService } from "./fulfillment-provider"
|
||||
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
IEventBusModuleService,
|
||||
Logger,
|
||||
ModuleProviderExports,
|
||||
ModuleServiceInitializeOptions,
|
||||
} from "@medusajs/types"
|
||||
|
||||
export type InitializeModuleInjectableDependencies = {
|
||||
logger?: Logger
|
||||
eventBusService?: IEventBusModuleService
|
||||
}
|
||||
|
||||
export const FulfillmentIdentifiersRegistrationName =
|
||||
"fulfillment_providers_identifier"
|
||||
|
||||
export type FulfillmentModuleOptions =
|
||||
Partial<ModuleServiceInitializeOptions> & {
|
||||
/**
|
||||
* Providers to be registered
|
||||
*/
|
||||
providers?: {
|
||||
/**
|
||||
* The module provider to be registered
|
||||
*/
|
||||
resolve: string | ModuleProviderExports
|
||||
options: {
|
||||
/**
|
||||
* key value pair of the provider name and the configuration to be passed to the provider constructor
|
||||
*/
|
||||
config: Record<string, unknown>
|
||||
}
|
||||
}[]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { FulfillmentTypes } from "@medusajs/types"
|
||||
|
||||
export type UpdateShippingOptionsInput = Required<
|
||||
Pick<FulfillmentTypes.UpdateShippingOptionDTO, "id">
|
||||
> &
|
||||
FulfillmentTypes.UpdateShippingOptionDTO
|
||||
@@ -0,0 +1,249 @@
|
||||
import { RuleOperator } from "@medusajs/utils"
|
||||
import { isContextValid } from "../utils"
|
||||
|
||||
describe("isContextValidForRules", () => {
|
||||
const context = {
|
||||
attribute1: "value1",
|
||||
attribute2: "value2",
|
||||
attribute3: "value3",
|
||||
}
|
||||
|
||||
const validRule = {
|
||||
attribute: "attribute1",
|
||||
operator: RuleOperator.EQ,
|
||||
value: "value1",
|
||||
}
|
||||
|
||||
const invalidRule = {
|
||||
attribute: "attribute2",
|
||||
operator: RuleOperator.EQ,
|
||||
value: "wrongValue",
|
||||
}
|
||||
|
||||
it("returns true when all rules are valid", () => {
|
||||
const rules = [validRule, validRule]
|
||||
expect(isContextValid(context, rules)).toBe(true)
|
||||
})
|
||||
|
||||
it("returns true when some rules are valid", () => {
|
||||
const rules = [validRule, validRule]
|
||||
const options = { someAreValid: true }
|
||||
expect(isContextValid(context, rules, options)).toBe(true)
|
||||
})
|
||||
|
||||
it("returns true when some rules are valid and someAreValid is true", () => {
|
||||
const rules = [validRule, invalidRule]
|
||||
const options = { someAreValid: true }
|
||||
expect(isContextValid(context, rules, options)).toBe(true)
|
||||
})
|
||||
|
||||
it("returns false when some rules are valid", () => {
|
||||
const rules = [validRule, invalidRule]
|
||||
expect(isContextValid(context, rules)).toBe(false)
|
||||
})
|
||||
|
||||
it("returns false when no rules are valid and someAreValid is true", () => {
|
||||
const rules = [invalidRule, invalidRule]
|
||||
const options = { someAreValid: true }
|
||||
expect(isContextValid(context, rules, options)).toBe(false)
|
||||
})
|
||||
|
||||
it("returns false when no rules are valid", () => {
|
||||
const rules = [invalidRule, invalidRule]
|
||||
expect(isContextValid(context, rules)).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true when the 'gt' operator is valid", () => {
|
||||
const rules = [
|
||||
{
|
||||
attribute: "attribute1",
|
||||
operator: RuleOperator.GT,
|
||||
value: "1", // 2 > 1
|
||||
},
|
||||
]
|
||||
const context = { attribute1: "2" }
|
||||
expect(isContextValid(context, rules)).toBe(true)
|
||||
})
|
||||
|
||||
it("returns false when the 'gt' operator is invalid", () => {
|
||||
const rules = [
|
||||
{
|
||||
attribute: "attribute1",
|
||||
operator: RuleOperator.GT,
|
||||
value: "0", // 0 > 0
|
||||
},
|
||||
]
|
||||
const context = { attribute1: "0" }
|
||||
expect(isContextValid(context, rules)).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true when the 'gte' operator is valid", () => {
|
||||
const rules = [
|
||||
{
|
||||
attribute: "attribute1",
|
||||
operator: RuleOperator.GTE,
|
||||
value: "2", // 2 >= 2
|
||||
},
|
||||
]
|
||||
const context = { attribute1: "2" }
|
||||
expect(isContextValid(context, rules)).toBe(true)
|
||||
})
|
||||
|
||||
it("returns false when the 'gte' operator is invalid", () => {
|
||||
const rules = [
|
||||
{
|
||||
attribute: "attribute1",
|
||||
operator: RuleOperator.GTE,
|
||||
value: "3", // 2 >= 3
|
||||
},
|
||||
]
|
||||
const context = { attribute1: "2" }
|
||||
expect(isContextValid(context, rules)).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true when the 'lt' operator is valid", () => {
|
||||
const rules = [
|
||||
{
|
||||
attribute: "attribute1",
|
||||
operator: RuleOperator.LT,
|
||||
value: "3", // 2 < 3
|
||||
},
|
||||
]
|
||||
const context = { attribute1: "2" }
|
||||
expect(isContextValid(context, rules)).toBe(true)
|
||||
})
|
||||
|
||||
it("returns false when the 'lt' operator is invalid", () => {
|
||||
const rules = [
|
||||
{
|
||||
attribute: "attribute1",
|
||||
operator: RuleOperator.LT,
|
||||
value: "2", // 2 < 2
|
||||
},
|
||||
]
|
||||
const context = { attribute1: "2" }
|
||||
expect(isContextValid(context, rules)).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true when the 'lte' operator is valid", () => {
|
||||
const rules = [
|
||||
{
|
||||
attribute: "attribute1",
|
||||
operator: RuleOperator.LTE,
|
||||
value: "2", // 2 <= 2
|
||||
},
|
||||
]
|
||||
const context = { attribute1: "2" }
|
||||
expect(isContextValid(context, rules)).toBe(true)
|
||||
})
|
||||
|
||||
// ... existing tests ...
|
||||
|
||||
it("returns false when the 'lte' operator is invalid", () => {
|
||||
const rules = [
|
||||
{
|
||||
attribute: "attribute1",
|
||||
operator: RuleOperator.LTE,
|
||||
value: "1", // 2 <= 1
|
||||
},
|
||||
]
|
||||
const context = { attribute1: "2" }
|
||||
expect(isContextValid(context, rules)).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true when the 'in' operator is valid", () => {
|
||||
const rules = [
|
||||
{
|
||||
attribute: "attribute1",
|
||||
operator: RuleOperator.IN,
|
||||
value: ["1", "2", "3"], // 2 in [1, 2, 3]
|
||||
},
|
||||
]
|
||||
const context = { attribute1: "2" }
|
||||
expect(isContextValid(context, rules)).toBe(true)
|
||||
})
|
||||
|
||||
it("returns false when the 'in' operator is invalid", () => {
|
||||
const rules = [
|
||||
{
|
||||
attribute: "attribute1",
|
||||
operator: RuleOperator.IN,
|
||||
value: ["1", "3", "4"], // 2 in [1, 3, 4]
|
||||
},
|
||||
]
|
||||
const context = { attribute1: "2" }
|
||||
expect(isContextValid(context, rules)).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true when the 'nin' operator is valid", () => {
|
||||
const rules = [
|
||||
{
|
||||
attribute: "attribute1",
|
||||
operator: RuleOperator.NIN,
|
||||
value: ["1", "3", "4"], // 2 not in [1, 3, 4]
|
||||
},
|
||||
]
|
||||
const context = { attribute1: "2" }
|
||||
expect(isContextValid(context, rules)).toBe(true)
|
||||
})
|
||||
|
||||
it("returns false when the 'nin' operator is invalid", () => {
|
||||
const rules = [
|
||||
{
|
||||
attribute: "attribute1",
|
||||
operator: RuleOperator.NIN,
|
||||
value: ["1", "2", "3"], // 2 not in [1, 2, 3]
|
||||
},
|
||||
]
|
||||
const context = { attribute1: "2" }
|
||||
expect(isContextValid(context, rules)).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true when the 'ne' operator is valid", () => {
|
||||
const rules = [
|
||||
{
|
||||
attribute: "attribute1",
|
||||
operator: RuleOperator.NE,
|
||||
value: "1", // 2 != 1
|
||||
},
|
||||
]
|
||||
const context = { attribute1: "2" }
|
||||
expect(isContextValid(context, rules)).toBe(true)
|
||||
})
|
||||
|
||||
it("returns false when the 'ne' operator is invalid", () => {
|
||||
const rules = [
|
||||
{
|
||||
attribute: "attribute1",
|
||||
operator: RuleOperator.NE,
|
||||
value: "2", // 2 != 2
|
||||
},
|
||||
]
|
||||
const context = { attribute1: "2" }
|
||||
expect(isContextValid(context, rules)).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true when the 'eq' operator is valid", () => {
|
||||
const rules = [
|
||||
{
|
||||
attribute: "attribute1",
|
||||
operator: RuleOperator.EQ,
|
||||
value: "2", // 2 == 2
|
||||
},
|
||||
]
|
||||
const context = { attribute1: "2" }
|
||||
expect(isContextValid(context, rules)).toBe(true)
|
||||
})
|
||||
|
||||
it("returns false when the 'eq' operator is invalid", () => {
|
||||
const rules = [
|
||||
{
|
||||
attribute: "attribute1",
|
||||
operator: RuleOperator.EQ,
|
||||
value: "1", // 2 == 1
|
||||
},
|
||||
]
|
||||
const context = { attribute1: "2" }
|
||||
expect(isContextValid(context, rules)).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1 @@
|
||||
export * from './utils'
|
||||
@@ -0,0 +1,172 @@
|
||||
import {
|
||||
isObject,
|
||||
isString,
|
||||
MedusaError,
|
||||
pickValueFromObject,
|
||||
RuleOperator,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
/**
|
||||
* The rule engine here is kept inside the module as of now, but it could be moved
|
||||
* to the utils package and be used across the different modules that provides context
|
||||
* based rule filtering.
|
||||
*
|
||||
* TODO: discussion around that should happen at some point
|
||||
*/
|
||||
|
||||
export type Rule = {
|
||||
attribute: string
|
||||
operator: Lowercase<keyof typeof RuleOperator>
|
||||
value: string | string[] | null
|
||||
}
|
||||
|
||||
export const availableOperators = Object.values(RuleOperator)
|
||||
|
||||
const isDate = (str: string) => {
|
||||
return !isNaN(Date.parse(str))
|
||||
}
|
||||
|
||||
const operatorsPredicate = {
|
||||
in: (contextValue: string, ruleValue: string[]) =>
|
||||
ruleValue.includes(contextValue),
|
||||
nin: (contextValue: string, ruleValue: string[]) =>
|
||||
!ruleValue.includes(contextValue),
|
||||
eq: (contextValue: string, ruleValue: string) => contextValue === ruleValue,
|
||||
ne: (contextValue: string, ruleValue: string) => contextValue !== ruleValue,
|
||||
gt: (contextValue: string, ruleValue: string) => {
|
||||
if (isDate(contextValue) && isDate(ruleValue)) {
|
||||
return new Date(contextValue) > new Date(ruleValue)
|
||||
}
|
||||
return Number(contextValue) > Number(ruleValue)
|
||||
},
|
||||
gte: (contextValue: string, ruleValue: string) => {
|
||||
if (isDate(contextValue) && isDate(ruleValue)) {
|
||||
return new Date(contextValue) >= new Date(ruleValue)
|
||||
}
|
||||
return Number(contextValue) >= Number(ruleValue)
|
||||
},
|
||||
lt: (contextValue: string, ruleValue: string) => {
|
||||
if (isDate(contextValue) && isDate(ruleValue)) {
|
||||
return new Date(contextValue) < new Date(ruleValue)
|
||||
}
|
||||
return Number(contextValue) < Number(ruleValue)
|
||||
},
|
||||
lte: (contextValue: string, ruleValue: string) => {
|
||||
if (isDate(contextValue) && isDate(ruleValue)) {
|
||||
return new Date(contextValue) <= new Date(ruleValue)
|
||||
}
|
||||
return Number(contextValue) <= Number(ruleValue)
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate contextValue context object from contextValue set of rules.
|
||||
* By default, all rules must be valid to return true unless the option atLeastOneValidRule is set to true.
|
||||
* @param context
|
||||
* @param rules
|
||||
* @param options
|
||||
*/
|
||||
export function isContextValid(
|
||||
context: Record<string, any>,
|
||||
rules: Rule[],
|
||||
options: {
|
||||
someAreValid: boolean
|
||||
} = {
|
||||
someAreValid: false,
|
||||
}
|
||||
): boolean {
|
||||
const { someAreValid } = options
|
||||
|
||||
const loopComparator = someAreValid ? rules.some : rules.every
|
||||
const predicate = (rule) => {
|
||||
const { attribute, operator, value } = rule
|
||||
const contextValue = pickValueFromObject(attribute, context)
|
||||
return operatorsPredicate[operator](
|
||||
contextValue,
|
||||
value as string & string[]
|
||||
)
|
||||
}
|
||||
|
||||
return loopComparator.apply(rules, [predicate])
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate contextValue rule object
|
||||
* @param rule
|
||||
*/
|
||||
export function validateRule(rule: Record<string, unknown>): boolean {
|
||||
if (!rule.attribute || !rule.operator || !rule.value) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Rule must have an attribute, an operator and a value"
|
||||
)
|
||||
}
|
||||
|
||||
if (!isString(rule.attribute)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Rule attribute must be a string"
|
||||
)
|
||||
}
|
||||
|
||||
if (!isString(rule.operator)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Rule operator must be a string"
|
||||
)
|
||||
}
|
||||
|
||||
if (!availableOperators.includes(rule.operator as RuleOperator)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Rule operator ${
|
||||
rule.operator
|
||||
} is not supported. Must be one of ${availableOperators.join(", ")}`
|
||||
)
|
||||
}
|
||||
|
||||
if (rule.operator === RuleOperator.IN || rule.operator === RuleOperator.NIN) {
|
||||
if (!Array.isArray(rule.value)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Rule value must be an array for in/nin operators"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (Array.isArray(rule.value) || isObject(rule.value)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Rule value must be a string, bool, number value for the selected operator ${rule.operator}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export function normalizeRulesValue<T extends Partial<Rule>>(rules: T[]): void {
|
||||
rules.forEach((rule) => {
|
||||
/**
|
||||
* If a string is provided, then we don't want jsonb to convert to the primitive value based on the RFC
|
||||
*/
|
||||
if (rule.value === "true" || rule.value === "false") {
|
||||
rule.value = rule.value === "true" ? '"true"' : '"false"'
|
||||
}
|
||||
|
||||
return rule
|
||||
})
|
||||
}
|
||||
|
||||
export function validateAndNormalizeRules<T extends Partial<Rule>>(rules: T[]) {
|
||||
rules.forEach(validateRule)
|
||||
normalizeRulesValue(rules)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate contextValue set of rules
|
||||
* @param rules
|
||||
*/
|
||||
export function validateRules(rules: Record<string, unknown>[]): boolean {
|
||||
rules.forEach(validateRule)
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es2020"],
|
||||
"target": "es2020",
|
||||
"outDir": "./dist",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"sourceMap": false,
|
||||
"noImplicitReturns": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitThis": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"downlevelIteration": true, // to use ES5 specific tooling
|
||||
"baseUrl": ".",
|
||||
"resolveJsonModule": true,
|
||||
"paths": {
|
||||
"@models": ["./src/models"],
|
||||
"@services": ["./src/services"],
|
||||
"@repositories": ["./src/repositories"],
|
||||
"@types": ["./src/types"],
|
||||
"@utils": ["./src/utils"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"./src/**/__tests__",
|
||||
"./src/**/__mocks__",
|
||||
"./src/**/__fixtures__",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["src", "integration-tests"],
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user