Merge branch 'develop' into feat/payment-module-models
This commit is contained in:
101 files changed
+4539
-221
No files matched your search
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/authentication": patch
|
||||||
|
"@medusajs/pricing": patch
|
||||||
|
"@medusajs/utils": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
chore(utils): Update base repository to infer primary keys and support composite
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/types": patch
|
||||||
|
"@medusajs/utils": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(utils,types): added item/shipping adjustments for order/items/shipping_methods
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/inventory": patch
|
||||||
|
"@medusajs/stock-location": patch
|
||||||
|
"@medusajs/types": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(cart): Partial module service implementation
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/types": patch
|
||||||
|
"@medusajs/utils": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(utils,types): campaigns and campaign budgets + services CRUD
|
||||||
+3
-2
@@ -72,7 +72,9 @@ module.exports = {
|
|||||||
node: true,
|
node: true,
|
||||||
jest: true,
|
jest: true,
|
||||||
},
|
},
|
||||||
ignorePatterns: [],
|
ignorePatterns: [
|
||||||
|
"packages/admin-next/dashboard/**/dist"
|
||||||
|
],
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ["*.ts"],
|
files: ["*.ts"],
|
||||||
@@ -232,7 +234,6 @@ module.exports = {
|
|||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
"plugin:react-hooks/recommended",
|
"plugin:react-hooks/recommended",
|
||||||
],
|
],
|
||||||
ignorePatterns: ["dist"],
|
|
||||||
parser: "@typescript-eslint/parser",
|
parser: "@typescript-eslint/parser",
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: "./packages/admin-next/dashboard/tsconfig.json",
|
project: "./packages/admin-next/dashboard/tsconfig.json",
|
||||||
|
|||||||
@@ -998,7 +998,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kind": "code",
|
"kind": "code",
|
||||||
"text": "```ts\nclass MyFulfillmentService extends AbstractFulfillmentService {\n // ...\n async createFulfillment(\n data: Record<string, unknown>,\n items: LineItem,\n order: Order,\n fulfillment: Fulfillment\n ) {\n // No data is being sent anywhere\n // No data to be stored in the fulfillment's data object\n return {}\n }\n}\n```"
|
"text": "```ts\nclass MyFulfillmentService extends AbstractFulfillmentService {\n // ...\n async createFulfillment(\n data: Record<string, unknown>,\n items: LineItem[],\n order: Order,\n fulfillment: Fulfillment\n ) {\n // No data is being sent anywhere\n // No data to be stored in the fulfillment's data object\n return {}\n }\n}\n```"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -3356,7 +3356,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kind": "code",
|
"kind": "code",
|
||||||
"text": "```ts\nclass MyFulfillmentService extends AbstractFulfillmentService {\n // ...\n async createFulfillment(\n data: Record<string, unknown>,\n items: LineItem,\n order: Order,\n fulfillment: Fulfillment\n ) {\n // No data is being sent anywhere\n // No data to be stored in the fulfillment's data object\n return {}\n }\n}\n```"
|
"text": "```ts\nclass MyFulfillmentService extends AbstractFulfillmentService {\n // ...\n async createFulfillment(\n data: Record<string, unknown>,\n items: LineItem[],\n order: Order,\n fulfillment: Fulfillment\n ) {\n // No data is being sent anywhere\n // No data to be stored in the fulfillment's data object\n return {}\n }\n}\n```"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,12 @@ export class AuthProviderRepository extends DALUtils.mikroOrmBaseRepositoryFacto
|
|||||||
{
|
{
|
||||||
create: RepositoryTypes.CreateAuthProviderDTO
|
create: RepositoryTypes.CreateAuthProviderDTO
|
||||||
}
|
}
|
||||||
>(AuthProvider, "provider") {
|
>(AuthProvider) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
|
||||||
async update(
|
async update(
|
||||||
data: RepositoryTypes.UpdateAuthProviderDTO[],
|
data: RepositoryTypes.UpdateAuthProviderDTO[],
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ import { SqlEntityManager } from "@mikro-orm/postgresql"
|
|||||||
export class AuthUserRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
export class AuthUserRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
||||||
AuthUser
|
AuthUser
|
||||||
) {
|
) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
|
||||||
async create(
|
async create(
|
||||||
data: RepositoryTypes.CreateAuthUserDTO[],
|
data: RepositoryTypes.CreateAuthUserDTO[],
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const defaultCartsData = [
|
|||||||
first_name: "Tony",
|
first_name: "Tony",
|
||||||
last_name: "Stark",
|
last_name: "Stark",
|
||||||
},
|
},
|
||||||
billing_address_id: {
|
billing_address: {
|
||||||
address_1: "Stark Industries",
|
address_1: "Stark Industries",
|
||||||
city: "New York",
|
city: "New York",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ describe("Address Service", () => {
|
|||||||
.catch((e) => e)
|
.catch((e) => e)
|
||||||
|
|
||||||
expect(error.message).toContain(
|
expect(error.message).toContain(
|
||||||
"Address with id \"none-existing\" not found"
|
'Address with id "none-existing" not found'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,245 @@
|
|||||||
|
import { ICartModuleService } from "@medusajs/types"
|
||||||
|
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||||
|
import { initialize } from "../../../../src/initialize"
|
||||||
|
import { DB_URL, MikroOrmWrapper } from "../../../utils"
|
||||||
|
|
||||||
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
|
describe("Cart Module Service", () => {
|
||||||
|
let service: ICartModuleService
|
||||||
|
let repositoryManager: SqlEntityManager
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await MikroOrmWrapper.setupDatabase()
|
||||||
|
repositoryManager = await MikroOrmWrapper.forkManager()
|
||||||
|
|
||||||
|
service = await initialize({
|
||||||
|
database: {
|
||||||
|
clientUrl: DB_URL,
|
||||||
|
schema: process.env.MEDUSA_CART_DB_SCHEMA,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await MikroOrmWrapper.clearDatabase()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("create", () => {
|
||||||
|
it("should throw an error when required params are not passed", async () => {
|
||||||
|
const error = await service
|
||||||
|
.create([
|
||||||
|
{
|
||||||
|
email: "test@email.com",
|
||||||
|
} as any,
|
||||||
|
])
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(error.message).toContain(
|
||||||
|
"Value for Cart.currency_code is required, 'undefined' found"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create a cart successfully", async () => {
|
||||||
|
const [createdCart] = await service.create([
|
||||||
|
{
|
||||||
|
currency_code: "eur",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const [cart] = await service.list({ id: [createdCart.id] })
|
||||||
|
|
||||||
|
expect(cart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: createdCart.id,
|
||||||
|
currency_code: "eur",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create a cart with billing + shipping address successfully", async () => {
|
||||||
|
const [createdCart] = await service.create([
|
||||||
|
{
|
||||||
|
currency_code: "eur",
|
||||||
|
billing_address: {
|
||||||
|
first_name: "John",
|
||||||
|
last_name: "Doe",
|
||||||
|
},
|
||||||
|
shipping_address: {
|
||||||
|
first_name: "John",
|
||||||
|
last_name: "Doe",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const [cart] = await service.list(
|
||||||
|
{ id: [createdCart.id] },
|
||||||
|
{ relations: ["billing_address", "shipping_address"] }
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(cart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: createdCart.id,
|
||||||
|
currency_code: "eur",
|
||||||
|
billing_address: expect.objectContaining({
|
||||||
|
first_name: "John",
|
||||||
|
last_name: "Doe",
|
||||||
|
}),
|
||||||
|
shipping_address: expect.objectContaining({
|
||||||
|
first_name: "John",
|
||||||
|
last_name: "Doe",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create a cart with billing id + shipping id successfully", async () => {
|
||||||
|
const [createdAddress] = await service.createAddresses([
|
||||||
|
{
|
||||||
|
first_name: "John",
|
||||||
|
last_name: "Doe",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const [createdCart] = await service.create([
|
||||||
|
{
|
||||||
|
currency_code: "eur",
|
||||||
|
billing_address_id: createdAddress.id,
|
||||||
|
shipping_address_id: createdAddress.id,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(createdCart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: createdCart.id,
|
||||||
|
currency_code: "eur",
|
||||||
|
billing_address: expect.objectContaining({
|
||||||
|
id: createdAddress.id,
|
||||||
|
first_name: "John",
|
||||||
|
last_name: "Doe",
|
||||||
|
}),
|
||||||
|
shipping_address: expect.objectContaining({
|
||||||
|
id: createdAddress.id,
|
||||||
|
first_name: "John",
|
||||||
|
last_name: "Doe",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("update", () => {
|
||||||
|
it("should throw an error if cart does not exist", async () => {
|
||||||
|
const error = await service
|
||||||
|
.update([
|
||||||
|
{
|
||||||
|
id: "none-existing",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(error.message).toContain('Cart with id "none-existing" not found')
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update a cart successfully", async () => {
|
||||||
|
const [createdCart] = await service.create([
|
||||||
|
{
|
||||||
|
currency_code: "eur",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const [updatedCart] = await service.update([
|
||||||
|
{
|
||||||
|
id: createdCart.id,
|
||||||
|
email: "test@email.com",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const [cart] = await service.list({ id: [createdCart.id] })
|
||||||
|
|
||||||
|
expect(cart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: createdCart.id,
|
||||||
|
currency_code: "eur",
|
||||||
|
email: updatedCart.email,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("delete", () => {
|
||||||
|
it("should delete a cart successfully", async () => {
|
||||||
|
const [createdCart] = await service.create([
|
||||||
|
{
|
||||||
|
currency_code: "eur",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
await service.delete([createdCart.id])
|
||||||
|
|
||||||
|
const carts = await service.list({ id: [createdCart.id] })
|
||||||
|
|
||||||
|
expect(carts.length).toEqual(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("createAddresses", () => {
|
||||||
|
it("should create an address successfully", async () => {
|
||||||
|
const [createdAddress] = await service.createAddresses([
|
||||||
|
{
|
||||||
|
first_name: "John",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const [address] = await service.listAddresses({
|
||||||
|
id: [createdAddress.id!],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(address).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: createdAddress.id,
|
||||||
|
first_name: "John",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("updateAddresses", () => {
|
||||||
|
it("should update an address successfully", async () => {
|
||||||
|
const [createdAddress] = await service.createAddresses([
|
||||||
|
{
|
||||||
|
first_name: "John",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const [updatedAddress] = await service.updateAddresses([
|
||||||
|
{ id: createdAddress.id!, first_name: "Jane" },
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(updatedAddress).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: createdAddress.id,
|
||||||
|
first_name: "Jane",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("deleteAddresses", () => {
|
||||||
|
it("should delete an address successfully", async () => {
|
||||||
|
const [createdAddress] = await service.createAddresses([
|
||||||
|
{
|
||||||
|
first_name: "John",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
await service.deleteAddresses([createdAddress.id!])
|
||||||
|
|
||||||
|
const [address] = await service.listAddresses({
|
||||||
|
id: [createdAddress.id!],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(address).toBe(undefined)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -17,7 +17,7 @@ describe("Cart Service", () => {
|
|||||||
testManager = await MikroOrmWrapper.forkManager()
|
testManager = await MikroOrmWrapper.forkManager()
|
||||||
|
|
||||||
const cartRepository = new CartRepository({
|
const cartRepository = new CartRepository({
|
||||||
manager: repositoryManager,
|
manager: repositoryManager
|
||||||
})
|
})
|
||||||
|
|
||||||
service = new CartService({
|
service = new CartService({
|
||||||
@@ -74,9 +74,7 @@ describe("Cart Service", () => {
|
|||||||
])
|
])
|
||||||
.catch((e) => e)
|
.catch((e) => e)
|
||||||
|
|
||||||
expect(error.message).toContain(
|
expect(error.message).toContain('Cart with id "none-existing" not found')
|
||||||
"Cart with id \"none-existing\" not found"
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should update a cart successfully", async () => {
|
it("should update a cart successfully", async () => {
|
||||||
@@ -87,7 +85,6 @@ describe("Cart Service", () => {
|
|||||||
])
|
])
|
||||||
|
|
||||||
const [updatedCart] = await service.update([
|
const [updatedCart] = await service.update([
|
||||||
|
|
||||||
{
|
{
|
||||||
id: createdCart.id,
|
id: createdCart.id,
|
||||||
email: "test@email.com",
|
email: "test@email.com",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"orm:cache:clear": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm cache:clear"
|
"orm:cache:clear": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm cache:clear"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@medusajs/types": "workspace:^",
|
||||||
"@mikro-orm/cli": "5.9.7",
|
"@mikro-orm/cli": "5.9.7",
|
||||||
"cross-env": "^5.2.1",
|
"cross-env": "^5.2.1",
|
||||||
"jest": "^29.6.3",
|
"jest": "^29.6.3",
|
||||||
@@ -51,7 +52,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@medusajs/modules-sdk": "^1.12.5",
|
"@medusajs/modules-sdk": "^1.12.5",
|
||||||
"@medusajs/types": "^1.11.9",
|
|
||||||
"@medusajs/utils": "^1.11.2",
|
"@medusajs/utils": "^1.11.2",
|
||||||
"@mikro-orm/core": "5.9.7",
|
"@mikro-orm/core": "5.9.7",
|
||||||
"@mikro-orm/migrations": "5.9.7",
|
"@mikro-orm/migrations": "5.9.7",
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ import { moduleDefinition } from "../module-definition"
|
|||||||
import { InitializeModuleInjectableDependencies } from "../types"
|
import { InitializeModuleInjectableDependencies } from "../types"
|
||||||
|
|
||||||
export const initialize = async (
|
export const initialize = async (
|
||||||
options?: ModulesSdkTypes.ModuleBootstrapDeclaration,
|
options?:
|
||||||
|
| ModulesSdkTypes.ModuleServiceInitializeOptions
|
||||||
|
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
|
||||||
|
| ExternalModuleDeclaration
|
||||||
|
| InternalModuleDeclaration,
|
||||||
injectedDependencies?: InitializeModuleInjectableDependencies
|
injectedDependencies?: InitializeModuleInjectableDependencies
|
||||||
): Promise<ICartModuleService> => {
|
): Promise<ICartModuleService> => {
|
||||||
const loaded = await MedusaModule.bootstrap<ICartModuleService>({
|
const loaded = await MedusaModule.bootstrap<ICartModuleService>({
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as defaultRepositories from "@repositories"
|
|||||||
import { LoaderOptions } from "@medusajs/modules-sdk"
|
import { LoaderOptions } from "@medusajs/modules-sdk"
|
||||||
import { ModulesSdkTypes } from "@medusajs/types"
|
import { ModulesSdkTypes } from "@medusajs/types"
|
||||||
import { loadCustomRepositories } from "@medusajs/utils"
|
import { loadCustomRepositories } from "@medusajs/utils"
|
||||||
|
import * as defaultServices from "@services"
|
||||||
import { asClass } from "awilix"
|
import { asClass } from "awilix"
|
||||||
|
|
||||||
export default async ({
|
export default async ({
|
||||||
@@ -17,7 +18,8 @@ export default async ({
|
|||||||
)?.repositories
|
)?.repositories
|
||||||
|
|
||||||
container.register({
|
container.register({
|
||||||
// cartService: asClass(defaultServices.CartService).singleton(),
|
cartService: asClass(defaultServices.CartService).singleton(),
|
||||||
|
addressService: asClass(defaultServices.AddressService).singleton(),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (customRepositories) {
|
if (customRepositories) {
|
||||||
@@ -34,5 +36,7 @@ export default async ({
|
|||||||
function loadDefaultRepositories({ container }) {
|
function loadDefaultRepositories({ container }) {
|
||||||
container.register({
|
container.register({
|
||||||
baseRepository: asClass(defaultRepositories.BaseRepository).singleton(),
|
baseRepository: asClass(defaultRepositories.BaseRepository).singleton(),
|
||||||
|
cartRepository: asClass(defaultRepositories.CartRepository).singleton(),
|
||||||
|
addressRepository: asClass(defaultRepositories.AddressRepository).singleton(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -5,9 +5,10 @@ import {
|
|||||||
Cascade,
|
Cascade,
|
||||||
Collection,
|
Collection,
|
||||||
Entity,
|
Entity,
|
||||||
|
Index,
|
||||||
|
ManyToOne,
|
||||||
OnInit,
|
OnInit,
|
||||||
OneToMany,
|
OneToMany,
|
||||||
OneToOne,
|
|
||||||
OptionalProps,
|
OptionalProps,
|
||||||
PrimaryKey,
|
PrimaryKey,
|
||||||
Property,
|
Property,
|
||||||
@@ -19,7 +20,7 @@ import ShippingMethod from "./shipping-method"
|
|||||||
type OptionalCartProps =
|
type OptionalCartProps =
|
||||||
| "shipping_address"
|
| "shipping_address"
|
||||||
| "billing_address"
|
| "billing_address"
|
||||||
| DAL.EntityDateColumns // TODO: To be revisited when more clear
|
| DAL.EntityDateColumns
|
||||||
|
|
||||||
@Entity({ tableName: "cart" })
|
@Entity({ tableName: "cart" })
|
||||||
export default class Cart {
|
export default class Cart {
|
||||||
@@ -47,18 +48,22 @@ export default class Cart {
|
|||||||
@Property({ columnType: "text" })
|
@Property({ columnType: "text" })
|
||||||
currency_code: string
|
currency_code: string
|
||||||
|
|
||||||
@OneToOne({
|
@Index({ name: "IDX_cart_shipping_address_id" })
|
||||||
entity: () => Address,
|
@Property({ columnType: "text", nullable: true })
|
||||||
joinColumn: "shipping_address_id",
|
shipping_address_id?: string | null
|
||||||
cascade: [Cascade.REMOVE],
|
|
||||||
|
@ManyToOne(() => Address, {
|
||||||
|
fieldName: "shipping_address_id",
|
||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
shipping_address?: Address | null
|
shipping_address?: Address | null
|
||||||
|
|
||||||
@OneToOne({
|
@Index({ name: "IDX_cart_billing_address_id" })
|
||||||
entity: () => Address,
|
@Property({ columnType: "text", nullable: true })
|
||||||
joinColumn: "billing_address_id",
|
billing_address_id?: string | null
|
||||||
cascade: [Cascade.REMOVE],
|
|
||||||
|
@ManyToOne(() => Address, {
|
||||||
|
fieldName: "billing_address_id",
|
||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
billing_address?: Address | null
|
billing_address?: Address | null
|
||||||
@@ -116,7 +121,7 @@ export default class Cart {
|
|||||||
columnType: "timestamptz",
|
columnType: "timestamptz",
|
||||||
defaultRaw: "now()",
|
defaultRaw: "now()",
|
||||||
})
|
})
|
||||||
created_at: Date
|
created_at?: Date
|
||||||
|
|
||||||
@Property({
|
@Property({
|
||||||
onCreate: () => new Date(),
|
onCreate: () => new Date(),
|
||||||
@@ -124,7 +129,10 @@ export default class Cart {
|
|||||||
columnType: "timestamptz",
|
columnType: "timestamptz",
|
||||||
defaultRaw: "now()",
|
defaultRaw: "now()",
|
||||||
})
|
})
|
||||||
updated_at: Date
|
updated_at?: Date
|
||||||
|
|
||||||
|
@Property({ columnType: "timestamptz", nullable: true })
|
||||||
|
deleted_at?: Date
|
||||||
|
|
||||||
@BeforeCreate()
|
@BeforeCreate()
|
||||||
onCreate() {
|
onCreate() {
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ export { default as LineItemTaxLine } from "./line-item-tax-line"
|
|||||||
export { default as ShippingMethod } from "./shipping-method"
|
export { default as ShippingMethod } from "./shipping-method"
|
||||||
export { default as ShippingMethodAdjustmentLine } from "./shipping-method-adjustment-line"
|
export { default as ShippingMethodAdjustmentLine } from "./shipping-method-adjustment-line"
|
||||||
export { default as ShippingMethodTaxLine } from "./shipping-method-tax-line"
|
export { default as ShippingMethodTaxLine } from "./shipping-method-tax-line"
|
||||||
|
|
||||||
@@ -10,6 +10,11 @@ export class AddressRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
|
|||||||
create: CreateAddressDTO
|
create: CreateAddressDTO
|
||||||
}
|
}
|
||||||
>(Address) {
|
>(Address) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
|
||||||
async update(
|
async update(
|
||||||
data: { address: Address; update: UpdateAddressDTO }[],
|
data: { address: Address; update: UpdateAddressDTO }[],
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ export class CartRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
|
|||||||
create: CreateCartDTO
|
create: CreateCartDTO
|
||||||
}
|
}
|
||||||
>(Cart) {
|
>(Cart) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
|
||||||
async update(
|
async update(
|
||||||
data: { cart: Cart; update: UpdateCartDTO }[],
|
data: { cart: Cart; update: UpdateCartDTO }[],
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
|
AddressDTO,
|
||||||
|
CartAddressDTO,
|
||||||
CartDTO,
|
CartDTO,
|
||||||
Context,
|
Context,
|
||||||
CreateCartDTO,
|
CreateCartDTO,
|
||||||
@@ -11,11 +13,13 @@ import {
|
|||||||
UpdateCartDTO,
|
UpdateCartDTO,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
|
|
||||||
|
import { FilterableAddressProps } from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
InjectManager,
|
InjectManager,
|
||||||
InjectTransactionManager,
|
InjectTransactionManager,
|
||||||
MedusaContext,
|
MedusaContext,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
|
import { CreateAddressDTO, UpdateAddressDTO } from "@types"
|
||||||
import { joinerConfig } from "../joiner-config"
|
import { joinerConfig } from "../joiner-config"
|
||||||
import AddressService from "./address"
|
import AddressService from "./address"
|
||||||
import CartService from "./cart"
|
import CartService from "./cart"
|
||||||
@@ -32,10 +36,12 @@ export default class CartModuleService implements ICartModuleService {
|
|||||||
protected addressService_: AddressService
|
protected addressService_: AddressService
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{ baseRepository }: InjectedDependencies,
|
{ baseRepository, cartService, addressService }: InjectedDependencies,
|
||||||
protected readonly moduleDeclaration: InternalModuleDeclaration
|
protected readonly moduleDeclaration: InternalModuleDeclaration
|
||||||
) {
|
) {
|
||||||
this.baseRepository_ = baseRepository
|
this.baseRepository_ = baseRepository
|
||||||
|
this.cartService_ = cartService
|
||||||
|
this.addressService_ = addressService
|
||||||
}
|
}
|
||||||
|
|
||||||
__joinerConfig(): ModuleJoinerConfig {
|
__joinerConfig(): ModuleJoinerConfig {
|
||||||
@@ -157,15 +163,9 @@ export default class CartModuleService implements ICartModuleService {
|
|||||||
return await this.cartService_.update(data, sharedContext)
|
return await this.cartService_.update(data, sharedContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(
|
async delete(ids: string[], sharedContext?: Context): Promise<void>
|
||||||
ids: string[],
|
|
||||||
sharedContext?: Context
|
|
||||||
): Promise<void>
|
|
||||||
|
|
||||||
async delete(
|
async delete(ids: string, sharedContext?: Context): Promise<void>
|
||||||
ids: string,
|
|
||||||
sharedContext?: Context
|
|
||||||
): Promise<void>
|
|
||||||
|
|
||||||
@InjectTransactionManager("baseRepository_")
|
@InjectTransactionManager("baseRepository_")
|
||||||
async delete(
|
async delete(
|
||||||
@@ -175,4 +175,93 @@ export default class CartModuleService implements ICartModuleService {
|
|||||||
const cartIds = Array.isArray(ids) ? ids : [ids]
|
const cartIds = Array.isArray(ids) ? ids : [ids]
|
||||||
await this.cartService_.delete(cartIds, sharedContext)
|
await this.cartService_.delete(cartIds, sharedContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InjectManager("baseRepository_")
|
||||||
|
async listAddresses(
|
||||||
|
filters: FilterableAddressProps = {},
|
||||||
|
config: FindConfig<AddressDTO> = {},
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
) {
|
||||||
|
const addresses = await this.addressService_.list(
|
||||||
|
filters,
|
||||||
|
config,
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
return await this.baseRepository_.serialize<CartAddressDTO[]>(addresses, {
|
||||||
|
populate: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async createAddresses(data: CreateAddressDTO, sharedContext?: Context)
|
||||||
|
async createAddresses(data: CreateAddressDTO[], sharedContext?: Context)
|
||||||
|
|
||||||
|
@InjectManager("baseRepository_")
|
||||||
|
async createAddresses(
|
||||||
|
data: CreateAddressDTO[] | CreateAddressDTO,
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
) {
|
||||||
|
const input = Array.isArray(data) ? data : [data]
|
||||||
|
const addresses = await this.createAddresses_(input, sharedContext)
|
||||||
|
|
||||||
|
const result = await this.listAddresses(
|
||||||
|
{ id: addresses.map((p) => p.id) },
|
||||||
|
{},
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
return (Array.isArray(data) ? result : result[0]) as
|
||||||
|
| AddressDTO
|
||||||
|
| AddressDTO[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("baseRepository_")
|
||||||
|
protected async createAddresses_(
|
||||||
|
data: CreateAddressDTO[],
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
) {
|
||||||
|
return await this.addressService_.create(data, sharedContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateAddresses(data: UpdateAddressDTO, sharedContext?: Context)
|
||||||
|
async updateAddresses(data: UpdateAddressDTO[], sharedContext?: Context)
|
||||||
|
|
||||||
|
@InjectManager("baseRepository_")
|
||||||
|
async updateAddresses(
|
||||||
|
data: UpdateAddressDTO[] | UpdateAddressDTO,
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
) {
|
||||||
|
const input = Array.isArray(data) ? data : [data]
|
||||||
|
const addresses = await this.updateAddresses_(input, sharedContext)
|
||||||
|
|
||||||
|
const result = await this.listAddresses(
|
||||||
|
{ id: addresses.map((p) => p.id) },
|
||||||
|
{},
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
return (Array.isArray(data) ? result : result[0]) as
|
||||||
|
| AddressDTO
|
||||||
|
| AddressDTO[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("baseRepository_")
|
||||||
|
protected async updateAddresses_(
|
||||||
|
data: UpdateAddressDTO[],
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
) {
|
||||||
|
return await this.addressService_.update(data, sharedContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteAddresses(ids: string[], sharedContext?: Context)
|
||||||
|
async deleteAddresses(ids: string, sharedContext?: Context)
|
||||||
|
|
||||||
|
@InjectTransactionManager("baseRepository_")
|
||||||
|
async deleteAddresses(
|
||||||
|
ids: string[] | string,
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
) {
|
||||||
|
const addressIds = Array.isArray(ids) ? ids : [ids]
|
||||||
|
await this.addressService_.delete(addressIds, sharedContext)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"author": "Medusa",
|
"author": "Medusa",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@medusajs/types": "^1.11.8",
|
"@medusajs/types": "^1.11.9",
|
||||||
"cross-env": "^5.2.1",
|
"cross-env": "^5.2.1",
|
||||||
"jest": "^29.6.3",
|
"jest": "^29.6.3",
|
||||||
"rimraf": "^5.0.1",
|
"rimraf": "^5.0.1",
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ export interface FulfillmentService extends TransactionBaseService {
|
|||||||
* // ...
|
* // ...
|
||||||
* async createFulfillment(
|
* async createFulfillment(
|
||||||
* data: Record<string, unknown>,
|
* data: Record<string, unknown>,
|
||||||
* items: LineItem,
|
* items: LineItem[],
|
||||||
* order: Order,
|
* order: Order,
|
||||||
* fulfillment: Fulfillment
|
* fulfillment: Fulfillment
|
||||||
* ) {
|
* ) {
|
||||||
|
|||||||
@@ -9,4 +9,9 @@ export class CurrencyRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
|
|||||||
create: RepositoryTypes.CreateCurrencyDTO
|
create: RepositoryTypes.CreateCurrencyDTO
|
||||||
update: RepositoryTypes.UpdateCurrencyDTO
|
update: RepositoryTypes.UpdateCurrencyDTO
|
||||||
}
|
}
|
||||||
>(Currency, "code") {}
|
>(Currency) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,4 +9,9 @@ export class MoneyAmountRepository extends DALUtils.mikroOrmBaseRepositoryFactor
|
|||||||
create: RepositoryTypes.CreateMoneyAmountDTO
|
create: RepositoryTypes.CreateMoneyAmountDTO
|
||||||
update: RepositoryTypes.UpdateMoneyAmountDTO
|
update: RepositoryTypes.UpdateMoneyAmountDTO
|
||||||
}
|
}
|
||||||
>(MoneyAmount) {}
|
>(MoneyAmount) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,11 @@ export class PriceListRuleValueRepository extends DALUtils.mikroOrmBaseRepositor
|
|||||||
update: RepositoryTypes.UpdatePriceListRuleValueDTO
|
update: RepositoryTypes.UpdatePriceListRuleValueDTO
|
||||||
}
|
}
|
||||||
>(PriceListRuleValue) {
|
>(PriceListRuleValue) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
|
||||||
async create(
|
async create(
|
||||||
data: RepositoryTypes.CreatePriceListRuleValueDTO[],
|
data: RepositoryTypes.CreatePriceListRuleValueDTO[],
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import { RepositoryTypes } from "@types"
|
|||||||
export class PriceListRuleRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
export class PriceListRuleRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
||||||
PriceListRule
|
PriceListRule
|
||||||
) {
|
) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
|
||||||
async create(
|
async create(
|
||||||
data: RepositoryTypes.CreatePriceListRuleDTO[],
|
data: RepositoryTypes.CreatePriceListRuleDTO[],
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import { RepositoryTypes } from "@types"
|
|||||||
export class PriceListRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
export class PriceListRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
||||||
PriceList
|
PriceList
|
||||||
) {
|
) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
|
||||||
async create(
|
async create(
|
||||||
data: RepositoryTypes.CreatePriceListDTO[],
|
data: RepositoryTypes.CreatePriceListDTO[],
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ export class PriceRuleRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
|
|||||||
update: RepositoryTypes.UpdatePriceRuleDTO
|
update: RepositoryTypes.UpdatePriceRuleDTO
|
||||||
}
|
}
|
||||||
>(PriceRule) {
|
>(PriceRule) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
|
||||||
async create(
|
async create(
|
||||||
data: RepositoryTypes.CreatePriceRuleDTO[],
|
data: RepositoryTypes.CreatePriceRuleDTO[],
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
|
|||||||
@@ -9,4 +9,9 @@ export class PriceSetMoneyAmountRulesRepository extends DALUtils.mikroOrmBaseRep
|
|||||||
create: RepositoryTypes.CreatePriceSetMoneyAmountRulesDTO
|
create: RepositoryTypes.CreatePriceSetMoneyAmountRulesDTO
|
||||||
update: RepositoryTypes.UpdatePriceSetMoneyAmountRulesDTO
|
update: RepositoryTypes.UpdatePriceSetMoneyAmountRulesDTO
|
||||||
}
|
}
|
||||||
>(PriceSetMoneyAmountRules) {}
|
>(PriceSetMoneyAmountRules) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,4 +9,9 @@ export class PriceSetMoneyAmountRepository extends DALUtils.mikroOrmBaseReposito
|
|||||||
create: RepositoryTypes.CreatePriceSetMoneyAmountDTO
|
create: RepositoryTypes.CreatePriceSetMoneyAmountDTO
|
||||||
update: RepositoryTypes.UpdatePriceSetMoneyAmountDTO
|
update: RepositoryTypes.UpdatePriceSetMoneyAmountDTO
|
||||||
}
|
}
|
||||||
>(PriceSetMoneyAmount) {}
|
>(PriceSetMoneyAmount) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,4 +9,9 @@ export class PriceSetRuleTypeRepository extends DALUtils.mikroOrmBaseRepositoryF
|
|||||||
create: RepositoryTypes.CreatePriceSetRuleTypeDTO
|
create: RepositoryTypes.CreatePriceSetRuleTypeDTO
|
||||||
update: RepositoryTypes.UpdatePriceSetRuleTypeDTO
|
update: RepositoryTypes.UpdatePriceSetRuleTypeDTO
|
||||||
}
|
}
|
||||||
>(PriceSetRuleType) {}
|
>(PriceSetRuleType) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,4 +9,9 @@ export class PriceSetRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
|
|||||||
create: RepositoryTypes.CreatePriceSetDTO
|
create: RepositoryTypes.CreatePriceSetDTO
|
||||||
update: RepositoryTypes.UpdatePriceSetDTO
|
update: RepositoryTypes.UpdatePriceSetDTO
|
||||||
}
|
}
|
||||||
>(PriceSet) {}
|
>(PriceSet) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,4 +8,9 @@ export class RuleTypeRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
|
|||||||
create: RepositoryTypes.CreateRuleTypeDTO
|
create: RepositoryTypes.CreateRuleTypeDTO
|
||||||
update: RepositoryTypes.UpdateRuleTypeDTO
|
update: RepositoryTypes.UpdateRuleTypeDTO
|
||||||
}
|
}
|
||||||
>(RuleType) {}
|
>(RuleType) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,11 @@ type CreateProductCollection = ProductTypes.CreateProductCollectionDTO & {
|
|||||||
export class ProductCollectionRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
export class ProductCollectionRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
||||||
ProductCollection
|
ProductCollection
|
||||||
) {
|
) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
|
||||||
async create(
|
async create(
|
||||||
data: CreateProductCollection[],
|
data: CreateProductCollection[],
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import { DALUtils } from "@medusajs/utils"
|
|||||||
export class ProductImageRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
export class ProductImageRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
||||||
Image
|
Image
|
||||||
) {
|
) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
|
||||||
async upsert(urls: string[], context: Context = {}): Promise<Image[]> {
|
async upsert(urls: string[], context: Context = {}): Promise<Image[]> {
|
||||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ import { SqlEntityManager } from "@mikro-orm/postgresql"
|
|||||||
export class ProductOptionValueRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
export class ProductOptionValueRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
||||||
ProductOptionValue
|
ProductOptionValue
|
||||||
) {
|
) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
|
||||||
async upsert(
|
async upsert(
|
||||||
optionValues: (UpdateProductOptionValueDTO | CreateProductOptionValueDTO)[],
|
optionValues: (UpdateProductOptionValueDTO | CreateProductOptionValueDTO)[],
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ export class ProductOptionRepository extends DALUtils.mikroOrmBaseRepositoryFact
|
|||||||
update: ProductTypes.UpdateProductOptionDTO
|
update: ProductTypes.UpdateProductOptionDTO
|
||||||
}
|
}
|
||||||
>(ProductOption) {
|
>(ProductOption) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
|
||||||
async create(
|
async create(
|
||||||
data: ProductTypes.CreateProductOptionDTO[],
|
data: ProductTypes.CreateProductOptionDTO[],
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ export class ProductTagRepository extends DALUtils.mikroOrmBaseRepositoryFactory
|
|||||||
update: UpdateProductTagDTO
|
update: UpdateProductTagDTO
|
||||||
}
|
}
|
||||||
>(ProductTag) {
|
>(ProductTag) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
|
||||||
async upsert(
|
async upsert(
|
||||||
tags: UpsertProductTagDTO[],
|
tags: UpsertProductTagDTO[],
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ export class ProductTypeRepository extends DALUtils.mikroOrmBaseRepositoryFactor
|
|||||||
update: UpdateProductTypeDTO
|
update: UpdateProductTypeDTO
|
||||||
}
|
}
|
||||||
>(ProductType) {
|
>(ProductType) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
|
||||||
async upsert(
|
async upsert(
|
||||||
types: CreateProductTypeDTO[],
|
types: CreateProductTypeDTO[],
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
|
|||||||
@@ -14,4 +14,9 @@ export class ProductVariantRepository extends DALUtils.mikroOrmBaseRepositoryFac
|
|||||||
"id"
|
"id"
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
>(ProductVariant) {}
|
>(ProductVariant) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,11 @@ export class ProductRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
|
|||||||
create: WithRequiredProperty<ProductTypes.CreateProductOnlyDTO, "status">
|
create: WithRequiredProperty<ProductTypes.CreateProductOnlyDTO, "status">
|
||||||
}
|
}
|
||||||
>(Product) {
|
>(Product) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
|
||||||
async find(
|
async find(
|
||||||
findOptions: DAL.FindOptions<Product & { q?: string }> = { where: {} },
|
findOptions: DAL.FindOptions<Product & { q?: string }> = { where: {} },
|
||||||
context: Context = {}
|
context: Context = {}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { CampaignBudgetType } from "@medusajs/utils"
|
||||||
|
|
||||||
|
export const defaultCampaignsData = [
|
||||||
|
{
|
||||||
|
id: "campaign-id-1",
|
||||||
|
name: "campaign 1",
|
||||||
|
description: "test description",
|
||||||
|
currency: "USD",
|
||||||
|
campaign_identifier: "test-1",
|
||||||
|
starts_at: new Date("01/01/2023"),
|
||||||
|
ends_at: new Date("01/01/2024"),
|
||||||
|
budget: {
|
||||||
|
type: CampaignBudgetType.SPEND,
|
||||||
|
limit: 1000,
|
||||||
|
used: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "campaign-id-2",
|
||||||
|
name: "campaign 1",
|
||||||
|
description: "test description",
|
||||||
|
currency: "USD",
|
||||||
|
campaign_identifier: "test-2",
|
||||||
|
starts_at: new Date("01/01/2023"),
|
||||||
|
ends_at: new Date("01/01/2024"),
|
||||||
|
budget: {
|
||||||
|
type: CampaignBudgetType.USAGE,
|
||||||
|
limit: 1000,
|
||||||
|
used: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { CreateCampaignDTO } from "@medusajs/types"
|
||||||
|
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||||
|
import { Campaign } from "@models"
|
||||||
|
import { defaultCampaignsData } from "./data"
|
||||||
|
|
||||||
|
export * from "./data"
|
||||||
|
|
||||||
|
export async function createCampaigns(
|
||||||
|
manager: SqlEntityManager,
|
||||||
|
campaignsData: CreateCampaignDTO[] = defaultCampaignsData
|
||||||
|
): Promise<Campaign[]> {
|
||||||
|
const campaigns: Campaign[] = []
|
||||||
|
|
||||||
|
for (let campaignData of campaignsData) {
|
||||||
|
let campaign = manager.create(Campaign, campaignData)
|
||||||
|
|
||||||
|
manager.persist(campaign)
|
||||||
|
|
||||||
|
await manager.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
return campaigns
|
||||||
|
}
|
||||||
+241
@@ -0,0 +1,241 @@
|
|||||||
|
import { IPromotionModuleService } from "@medusajs/types"
|
||||||
|
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||||
|
import { initialize } from "../../../../src"
|
||||||
|
import { createCampaigns } from "../../../__fixtures__/campaigns"
|
||||||
|
import { DB_URL, MikroOrmWrapper } from "../../../utils"
|
||||||
|
|
||||||
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
|
describe("Promotion Module Service: Campaigns", () => {
|
||||||
|
let service: IPromotionModuleService
|
||||||
|
let repositoryManager: SqlEntityManager
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await MikroOrmWrapper.setupDatabase()
|
||||||
|
repositoryManager = MikroOrmWrapper.forkManager()
|
||||||
|
|
||||||
|
service = await initialize({
|
||||||
|
database: {
|
||||||
|
clientUrl: DB_URL,
|
||||||
|
schema: process.env.MEDUSA_PROMOTION_DB_SCHEMA,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await MikroOrmWrapper.clearDatabase()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("createCampaigns", () => {
|
||||||
|
it("should throw an error when required params are not passed", async () => {
|
||||||
|
const error = await service
|
||||||
|
.createCampaigns([
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
} as any,
|
||||||
|
])
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(error.message).toContain(
|
||||||
|
"Value for Campaign.campaign_identifier is required, 'undefined' found"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create a basic campaign successfully", async () => {
|
||||||
|
const startsAt = new Date("01/01/2024")
|
||||||
|
const endsAt = new Date("01/01/2025")
|
||||||
|
const [createdCampaign] = await service.createCampaigns([
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
campaign_identifier: "test",
|
||||||
|
starts_at: startsAt,
|
||||||
|
ends_at: endsAt,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const campaign = await service.retrieveCampaign(createdCampaign.id)
|
||||||
|
|
||||||
|
expect(campaign).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "test",
|
||||||
|
campaign_identifier: "test",
|
||||||
|
starts_at: startsAt,
|
||||||
|
ends_at: endsAt,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create a campaign with campaign budget successfully", async () => {
|
||||||
|
const startsAt = new Date("01/01/2024")
|
||||||
|
const endsAt = new Date("01/01/2025")
|
||||||
|
|
||||||
|
const [createdPromotion] = await service.createCampaigns([
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
campaign_identifier: "test",
|
||||||
|
starts_at: startsAt,
|
||||||
|
ends_at: endsAt,
|
||||||
|
budget: {
|
||||||
|
limit: 1000,
|
||||||
|
type: "usage",
|
||||||
|
used: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const campaign = await service.retrieveCampaign(createdPromotion.id, {
|
||||||
|
relations: ["budget"],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(campaign).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "test",
|
||||||
|
campaign_identifier: "test",
|
||||||
|
starts_at: startsAt,
|
||||||
|
ends_at: endsAt,
|
||||||
|
budget: expect.objectContaining({
|
||||||
|
limit: 1000,
|
||||||
|
type: "usage",
|
||||||
|
used: 10,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("updateCampaigns", () => {
|
||||||
|
it("should throw an error when required params are not passed", async () => {
|
||||||
|
const error = await service
|
||||||
|
.updateCampaigns([
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
} as any,
|
||||||
|
])
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(error.message).toContain('Campaign with id "" not found')
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update the attributes of a campaign successfully", async () => {
|
||||||
|
await createCampaigns(repositoryManager)
|
||||||
|
|
||||||
|
const [updatedCampaign] = await service.updateCampaigns([
|
||||||
|
{
|
||||||
|
id: "campaign-id-1",
|
||||||
|
description: "test description 1",
|
||||||
|
currency: "EUR",
|
||||||
|
campaign_identifier: "new",
|
||||||
|
starts_at: new Date("01/01/2024"),
|
||||||
|
ends_at: new Date("01/01/2025"),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(updatedCampaign).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
description: "test description 1",
|
||||||
|
currency: "EUR",
|
||||||
|
campaign_identifier: "new",
|
||||||
|
starts_at: new Date("01/01/2024"),
|
||||||
|
ends_at: new Date("01/01/2025"),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update the attributes of a campaign budget successfully", async () => {
|
||||||
|
await createCampaigns(repositoryManager)
|
||||||
|
|
||||||
|
const [updatedCampaign] = await service.updateCampaigns([
|
||||||
|
{
|
||||||
|
id: "campaign-id-1",
|
||||||
|
budget: {
|
||||||
|
limit: 100,
|
||||||
|
used: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(updatedCampaign).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
budget: expect.objectContaining({
|
||||||
|
limit: 100,
|
||||||
|
used: 100,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("retrieveCampaign", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createCampaigns(repositoryManager)
|
||||||
|
})
|
||||||
|
|
||||||
|
const id = "campaign-id-1"
|
||||||
|
|
||||||
|
it("should return campaign for the given id", async () => {
|
||||||
|
const campaign = await service.retrieveCampaign(id)
|
||||||
|
|
||||||
|
expect(campaign).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw an error when campaign with id does not exist", async () => {
|
||||||
|
let error
|
||||||
|
|
||||||
|
try {
|
||||||
|
await service.retrieveCampaign("does-not-exist")
|
||||||
|
} catch (e) {
|
||||||
|
error = e
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error.message).toEqual(
|
||||||
|
"Campaign with id: does-not-exist was not found"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw an error when a id is not provided", async () => {
|
||||||
|
let error
|
||||||
|
|
||||||
|
try {
|
||||||
|
await service.retrieveCampaign(undefined as unknown as string)
|
||||||
|
} catch (e) {
|
||||||
|
error = e
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error.message).toEqual('"campaignId" must be defined')
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return campaign based on config select param", async () => {
|
||||||
|
const campaign = await service.retrieveCampaign(id, {
|
||||||
|
select: ["id"],
|
||||||
|
})
|
||||||
|
|
||||||
|
const serialized = JSON.parse(JSON.stringify(campaign))
|
||||||
|
|
||||||
|
expect(serialized).toEqual({
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("deleteCampaigns", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createCampaigns(repositoryManager)
|
||||||
|
})
|
||||||
|
|
||||||
|
const id = "campaign-id-1"
|
||||||
|
|
||||||
|
it("should delete the campaigns given an id successfully", async () => {
|
||||||
|
await service.deleteCampaigns([id])
|
||||||
|
|
||||||
|
const campaigns = await service.list({
|
||||||
|
id: [id],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(campaigns).toHaveLength(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
+1852
File diff suppressed because it is too large.
Load diff
+52
-82
@@ -99,8 +99,50 @@ describe("Promotion Service", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should create a promotion with order application method with rules successfully", async () => {
|
it("should throw error when creating an item application method without allocation", async () => {
|
||||||
const [createdPromotion] = await service.create([
|
const error = await service
|
||||||
|
.create([
|
||||||
|
{
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
target_type: "items",
|
||||||
|
value: "100",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(error.message).toContain(
|
||||||
|
"application_method.allocation should be either 'across OR each' when application_method.target_type is either 'shipping_methods OR items'"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw error when creating an item application, each allocation, without max quanity", async () => {
|
||||||
|
const error = await service
|
||||||
|
.create([
|
||||||
|
{
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
allocation: "each",
|
||||||
|
target_type: "shipping_methods",
|
||||||
|
value: "100",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(error.message).toContain(
|
||||||
|
"application_method.max_quantity is required when application_method.allocation is 'each'"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw error when creating an order application method with rules", async () => {
|
||||||
|
const error = await service
|
||||||
|
.create([
|
||||||
{
|
{
|
||||||
code: "PROMOTION_TEST",
|
code: "PROMOTION_TEST",
|
||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
@@ -118,82 +160,10 @@ describe("Promotion Service", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
const [promotion] = await service.list(
|
|
||||||
{
|
|
||||||
id: [createdPromotion.id],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
relations: [
|
|
||||||
"application_method",
|
|
||||||
"application_method.target_rules.values",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(promotion).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
code: "PROMOTION_TEST",
|
|
||||||
is_automatic: false,
|
|
||||||
type: "standard",
|
|
||||||
application_method: expect.objectContaining({
|
|
||||||
type: "fixed",
|
|
||||||
target_type: "order",
|
|
||||||
value: 100,
|
|
||||||
target_rules: [
|
|
||||||
expect.objectContaining({
|
|
||||||
attribute: "product_id",
|
|
||||||
operator: "eq",
|
|
||||||
values: expect.arrayContaining([
|
|
||||||
expect.objectContaining({
|
|
||||||
value: "prod_tshirt",
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should throw error when creating an item application method without allocation", async () => {
|
|
||||||
const error = await service
|
|
||||||
.create([
|
|
||||||
{
|
|
||||||
code: "PROMOTION_TEST",
|
|
||||||
type: PromotionType.STANDARD,
|
|
||||||
application_method: {
|
|
||||||
type: "fixed",
|
|
||||||
target_type: "item",
|
|
||||||
value: "100",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
.catch((e) => e)
|
.catch((e) => e)
|
||||||
|
|
||||||
expect(error.message).toContain(
|
expect(error.message).toContain(
|
||||||
"application_method.allocation should be either 'across OR each' when application_method.target_type is either 'shipping OR item'"
|
"Target rules for application method with target type (order) is not allowed"
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should throw error when creating an item application, each allocation, without max quanity", async () => {
|
|
||||||
const error = await service
|
|
||||||
.create([
|
|
||||||
{
|
|
||||||
code: "PROMOTION_TEST",
|
|
||||||
type: PromotionType.STANDARD,
|
|
||||||
application_method: {
|
|
||||||
type: "fixed",
|
|
||||||
allocation: "each",
|
|
||||||
target_type: "shipping",
|
|
||||||
value: "100",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
.catch((e) => e)
|
|
||||||
|
|
||||||
expect(error.message).toContain(
|
|
||||||
"application_method.max_quantity is required when application_method.allocation is 'each'"
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -390,7 +360,7 @@ describe("Promotion Service", () => {
|
|||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "item",
|
target_type: "items",
|
||||||
allocation: "across",
|
allocation: "across",
|
||||||
value: "100",
|
value: "100",
|
||||||
},
|
},
|
||||||
@@ -424,7 +394,7 @@ describe("Promotion Service", () => {
|
|||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "item",
|
target_type: "items",
|
||||||
allocation: "each",
|
allocation: "each",
|
||||||
value: "100",
|
value: "100",
|
||||||
max_quantity: 500,
|
max_quantity: 500,
|
||||||
@@ -483,7 +453,7 @@ describe("Promotion Service", () => {
|
|||||||
.catch((e) => e)
|
.catch((e) => e)
|
||||||
|
|
||||||
expect(error.message).toContain(
|
expect(error.message).toContain(
|
||||||
`application_method.target_type should be one of order, shipping, item`
|
`application_method.target_type should be one of order, shipping_methods, items`
|
||||||
)
|
)
|
||||||
|
|
||||||
error = await service
|
error = await service
|
||||||
@@ -604,7 +574,7 @@ describe("Promotion Service", () => {
|
|||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "item",
|
target_type: "items",
|
||||||
allocation: "each",
|
allocation: "each",
|
||||||
value: "100",
|
value: "100",
|
||||||
max_quantity: 500,
|
max_quantity: 500,
|
||||||
@@ -676,7 +646,7 @@ describe("Promotion Service", () => {
|
|||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "item",
|
target_type: "items",
|
||||||
allocation: "each",
|
allocation: "each",
|
||||||
value: "100",
|
value: "100",
|
||||||
max_quantity: 500,
|
max_quantity: 500,
|
||||||
@@ -760,7 +730,7 @@ describe("Promotion Service", () => {
|
|||||||
],
|
],
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "item",
|
target_type: "items",
|
||||||
allocation: "each",
|
allocation: "each",
|
||||||
value: "100",
|
value: "100",
|
||||||
max_quantity: 500,
|
max_quantity: 500,
|
||||||
@@ -821,7 +791,7 @@ describe("Promotion Service", () => {
|
|||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "item",
|
target_type: "items",
|
||||||
allocation: "each",
|
allocation: "each",
|
||||||
value: "100",
|
value: "100",
|
||||||
max_quantity: 500,
|
max_quantity: 500,
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ module.exports = {
|
|||||||
"^@models": "<rootDir>/src/models",
|
"^@models": "<rootDir>/src/models",
|
||||||
"^@services": "<rootDir>/src/services",
|
"^@services": "<rootDir>/src/services",
|
||||||
"^@repositories": "<rootDir>/src/repositories",
|
"^@repositories": "<rootDir>/src/repositories",
|
||||||
|
"^@utils": "<rootDir>/src/utils",
|
||||||
|
"^@types": "<rootDir>/src/types",
|
||||||
},
|
},
|
||||||
transform: {
|
transform: {
|
||||||
"^.+\\.[jt]s?$": [
|
"^.+\\.[jt]s?$": [
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ export default async ({
|
|||||||
applicationMethodService: asClass(
|
applicationMethodService: asClass(
|
||||||
defaultServices.ApplicationMethodService
|
defaultServices.ApplicationMethodService
|
||||||
).singleton(),
|
).singleton(),
|
||||||
|
campaignService: asClass(defaultServices.CampaignService).singleton(),
|
||||||
|
campaignBudgetService: asClass(
|
||||||
|
defaultServices.CampaignBudgetService
|
||||||
|
).singleton(),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (customRepositories) {
|
if (customRepositories) {
|
||||||
@@ -56,5 +60,11 @@ function loadDefaultRepositories({ container }) {
|
|||||||
promotionRuleValueRepository: asClass(
|
promotionRuleValueRepository: asClass(
|
||||||
defaultRepositories.PromotionRuleValueRepository
|
defaultRepositories.PromotionRuleValueRepository
|
||||||
).singleton(),
|
).singleton(),
|
||||||
|
campaignRepository: asClass(
|
||||||
|
defaultRepositories.CampaignRepository
|
||||||
|
).singleton(),
|
||||||
|
campaignBudgetRepository: asClass(
|
||||||
|
defaultRepositories.CampaignBudgetRepository
|
||||||
|
).singleton(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
ApplicationMethodAllocationValues,
|
ApplicationMethodAllocationValues,
|
||||||
ApplicationMethodTargetTypeValues,
|
ApplicationMethodTargetTypeValues,
|
||||||
ApplicationMethodTypeValues,
|
ApplicationMethodTypeValues,
|
||||||
|
DAL,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import { PromotionUtils, generateEntityId } from "@medusajs/utils"
|
import { PromotionUtils, generateEntityId } from "@medusajs/utils"
|
||||||
import {
|
import {
|
||||||
@@ -24,9 +25,8 @@ type OptionalFields =
|
|||||||
| "value"
|
| "value"
|
||||||
| "max_quantity"
|
| "max_quantity"
|
||||||
| "allocation"
|
| "allocation"
|
||||||
| "created_at"
|
|
||||||
| "updated_at"
|
|
||||||
| "deleted_at"
|
| "deleted_at"
|
||||||
|
| DAL.EntityDateColumns
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export default class ApplicationMethod {
|
export default class ApplicationMethod {
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { CampaignBudgetTypeValues, DAL } from "@medusajs/types"
|
||||||
|
import { PromotionUtils, generateEntityId } from "@medusajs/utils"
|
||||||
|
import {
|
||||||
|
BeforeCreate,
|
||||||
|
Entity,
|
||||||
|
Enum,
|
||||||
|
Index,
|
||||||
|
OnInit,
|
||||||
|
OneToOne,
|
||||||
|
OptionalProps,
|
||||||
|
PrimaryKey,
|
||||||
|
Property,
|
||||||
|
} from "@mikro-orm/core"
|
||||||
|
import Campaign from "./campaign"
|
||||||
|
|
||||||
|
type OptionalFields =
|
||||||
|
| "description"
|
||||||
|
| "limit"
|
||||||
|
| "used"
|
||||||
|
| "deleted_at"
|
||||||
|
| DAL.EntityDateColumns
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export default class CampaignBudget {
|
||||||
|
[OptionalProps]?: OptionalFields
|
||||||
|
|
||||||
|
@PrimaryKey({ columnType: "text" })
|
||||||
|
id!: string
|
||||||
|
|
||||||
|
@Index({ name: "IDX_campaign_budget_type" })
|
||||||
|
@Enum(() => PromotionUtils.CampaignBudgetType)
|
||||||
|
type: CampaignBudgetTypeValues
|
||||||
|
|
||||||
|
@OneToOne({
|
||||||
|
entity: () => Campaign,
|
||||||
|
})
|
||||||
|
campaign?: Campaign
|
||||||
|
|
||||||
|
@Property({
|
||||||
|
columnType: "numeric",
|
||||||
|
nullable: true,
|
||||||
|
serializer: Number,
|
||||||
|
default: null,
|
||||||
|
})
|
||||||
|
limit: number | null
|
||||||
|
|
||||||
|
@Property({
|
||||||
|
columnType: "numeric",
|
||||||
|
serializer: Number,
|
||||||
|
default: 0,
|
||||||
|
})
|
||||||
|
used: number
|
||||||
|
|
||||||
|
@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 })
|
||||||
|
deleted_at: Date | null
|
||||||
|
|
||||||
|
@BeforeCreate()
|
||||||
|
onCreate() {
|
||||||
|
this.id = generateEntityId(this.id, "probudg")
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnInit()
|
||||||
|
onInit() {
|
||||||
|
this.id = generateEntityId(this.id, "probudg")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import { DAL } from "@medusajs/types"
|
||||||
|
import { generateEntityId } from "@medusajs/utils"
|
||||||
|
import {
|
||||||
|
BeforeCreate,
|
||||||
|
Collection,
|
||||||
|
Entity,
|
||||||
|
OnInit,
|
||||||
|
OneToMany,
|
||||||
|
OneToOne,
|
||||||
|
OptionalProps,
|
||||||
|
PrimaryKey,
|
||||||
|
Property,
|
||||||
|
Unique,
|
||||||
|
} from "@mikro-orm/core"
|
||||||
|
import CampaignBudget from "./campaign-budget"
|
||||||
|
import Promotion from "./promotion"
|
||||||
|
|
||||||
|
type OptionalFields =
|
||||||
|
| "description"
|
||||||
|
| "currency"
|
||||||
|
| "starts_at"
|
||||||
|
| "ends_at"
|
||||||
|
| "deleted_at"
|
||||||
|
| DAL.EntityDateColumns
|
||||||
|
|
||||||
|
type OptionalRelations = "budget"
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export default class Campaign {
|
||||||
|
[OptionalProps]?: OptionalFields | OptionalRelations
|
||||||
|
|
||||||
|
@PrimaryKey({ columnType: "text" })
|
||||||
|
id!: string
|
||||||
|
|
||||||
|
@Property({ columnType: "text" })
|
||||||
|
name: string
|
||||||
|
|
||||||
|
@Property({ columnType: "text", nullable: true })
|
||||||
|
description: string | null
|
||||||
|
|
||||||
|
@Property({ columnType: "text", nullable: true })
|
||||||
|
currency: string | null
|
||||||
|
|
||||||
|
@Property({ columnType: "text" })
|
||||||
|
@Unique({
|
||||||
|
name: "IDX_campaign_identifier_unique",
|
||||||
|
properties: ["campaign_identifier"],
|
||||||
|
})
|
||||||
|
campaign_identifier: string
|
||||||
|
|
||||||
|
@Property({
|
||||||
|
columnType: "timestamptz",
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
starts_at: Date | null
|
||||||
|
|
||||||
|
@Property({
|
||||||
|
columnType: "timestamptz",
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
ends_at: Date | null
|
||||||
|
|
||||||
|
@OneToOne({
|
||||||
|
entity: () => CampaignBudget,
|
||||||
|
mappedBy: (cb) => cb.campaign,
|
||||||
|
cascade: ["soft-remove"] as any,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
budget: CampaignBudget | null
|
||||||
|
|
||||||
|
@OneToMany(() => Promotion, (promotion) => promotion.campaign, {
|
||||||
|
orphanRemoval: true,
|
||||||
|
})
|
||||||
|
promotions = new Collection<Promotion>(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 })
|
||||||
|
deleted_at: Date | null
|
||||||
|
|
||||||
|
@BeforeCreate()
|
||||||
|
onCreate() {
|
||||||
|
this.id = generateEntityId(this.id, "procamp")
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnInit()
|
||||||
|
onInit() {
|
||||||
|
this.id = generateEntityId(this.id, "procamp")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
export { default as ApplicationMethod } from "./application-method"
|
export { default as ApplicationMethod } from "./application-method"
|
||||||
|
export { default as Campaign } from "./campaign"
|
||||||
|
export { default as CampaignBudget } from "./campaign-budget"
|
||||||
export { default as Promotion } from "./promotion"
|
export { default as Promotion } from "./promotion"
|
||||||
export { default as PromotionRule } from "./promotion-rule"
|
export { default as PromotionRule } from "./promotion-rule"
|
||||||
export { default as PromotionRuleValue } from "./promotion-rule-value"
|
export { default as PromotionRuleValue } from "./promotion-rule-value"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PromotionRuleOperatorValues } from "@medusajs/types"
|
import { DAL, PromotionRuleOperatorValues } from "@medusajs/types"
|
||||||
import { PromotionUtils, generateEntityId } from "@medusajs/utils"
|
import { PromotionUtils, generateEntityId } from "@medusajs/utils"
|
||||||
import {
|
import {
|
||||||
BeforeCreate,
|
BeforeCreate,
|
||||||
@@ -18,7 +18,7 @@ import ApplicationMethod from "./application-method"
|
|||||||
import Promotion from "./promotion"
|
import Promotion from "./promotion"
|
||||||
import PromotionRuleValue from "./promotion-rule-value"
|
import PromotionRuleValue from "./promotion-rule-value"
|
||||||
|
|
||||||
type OptionalFields = "description" | "created_at" | "updated_at" | "deleted_at"
|
type OptionalFields = "description" | "deleted_at" | DAL.EntityDateColumns
|
||||||
type OptionalRelations = "values" | "promotions"
|
type OptionalRelations = "values" | "promotions"
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PromotionType } from "@medusajs/types"
|
import { DAL, PromotionType } from "@medusajs/types"
|
||||||
import { PromotionUtils, generateEntityId } from "@medusajs/utils"
|
import { PromotionUtils, generateEntityId } from "@medusajs/utils"
|
||||||
import {
|
import {
|
||||||
BeforeCreate,
|
BeforeCreate,
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
Enum,
|
Enum,
|
||||||
Index,
|
Index,
|
||||||
ManyToMany,
|
ManyToMany,
|
||||||
|
ManyToOne,
|
||||||
OnInit,
|
OnInit,
|
||||||
OneToOne,
|
OneToOne,
|
||||||
OptionalProps,
|
OptionalProps,
|
||||||
@@ -15,14 +16,12 @@ import {
|
|||||||
Unique,
|
Unique,
|
||||||
} from "@mikro-orm/core"
|
} from "@mikro-orm/core"
|
||||||
import ApplicationMethod from "./application-method"
|
import ApplicationMethod from "./application-method"
|
||||||
|
import Campaign from "./campaign"
|
||||||
import PromotionRule from "./promotion-rule"
|
import PromotionRule from "./promotion-rule"
|
||||||
|
|
||||||
type OptionalFields =
|
type OptionalFields = "is_automatic" | "deleted_at" | DAL.EntityDateColumns
|
||||||
| "is_automatic"
|
type OptionalRelations = "application_method" | "campaign"
|
||||||
| "created_at"
|
|
||||||
| "updated_at"
|
|
||||||
| "deleted_at"
|
|
||||||
type OptionalRelations = "application_method"
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export default class Promotion {
|
export default class Promotion {
|
||||||
[OptionalProps]?: OptionalFields | OptionalRelations
|
[OptionalProps]?: OptionalFields | OptionalRelations
|
||||||
@@ -38,6 +37,13 @@ export default class Promotion {
|
|||||||
})
|
})
|
||||||
code: string
|
code: string
|
||||||
|
|
||||||
|
@ManyToOne(() => Campaign, {
|
||||||
|
joinColumn: "campaign",
|
||||||
|
fieldName: "campaign_id",
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
campaign: Campaign
|
||||||
|
|
||||||
@Property({ columnType: "boolean", default: false })
|
@Property({ columnType: "boolean", default: false })
|
||||||
is_automatic?: boolean = false
|
is_automatic?: boolean = false
|
||||||
|
|
||||||
|
|||||||
@@ -8,4 +8,9 @@ export class ApplicationMethodRepository extends DALUtils.mikroOrmBaseRepository
|
|||||||
create: CreateApplicationMethodDTO
|
create: CreateApplicationMethodDTO
|
||||||
update: UpdateApplicationMethodDTO
|
update: UpdateApplicationMethodDTO
|
||||||
}
|
}
|
||||||
>(ApplicationMethod) {}
|
>(ApplicationMethod) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { DALUtils } from "@medusajs/utils"
|
||||||
|
import { CampaignBudget } from "@models"
|
||||||
|
import { CreateCampaignBudgetDTO, UpdateCampaignBudgetDTO } from "@types"
|
||||||
|
|
||||||
|
export class CampaignBudgetRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
|
||||||
|
CampaignBudget,
|
||||||
|
{
|
||||||
|
create: CreateCampaignBudgetDTO
|
||||||
|
update: UpdateCampaignBudgetDTO
|
||||||
|
}
|
||||||
|
>(CampaignBudget) {}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { DALUtils } from "@medusajs/utils"
|
||||||
|
import { Campaign } from "@models"
|
||||||
|
import { CreateCampaignDTO, UpdateCampaignDTO } from "@types"
|
||||||
|
|
||||||
|
export class CampaignRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
|
||||||
|
Campaign,
|
||||||
|
{
|
||||||
|
create: CreateCampaignDTO
|
||||||
|
update: UpdateCampaignDTO
|
||||||
|
}
|
||||||
|
>(Campaign) {}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
|
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
|
||||||
export { ApplicationMethodRepository } from "./application-method"
|
export { ApplicationMethodRepository } from "./application-method"
|
||||||
|
export { CampaignRepository } from "./campaign"
|
||||||
|
export { CampaignBudgetRepository } from "./campaign-budget"
|
||||||
export { PromotionRepository } from "./promotion"
|
export { PromotionRepository } from "./promotion"
|
||||||
export { PromotionRuleRepository } from "./promotion-rule"
|
export { PromotionRuleRepository } from "./promotion-rule"
|
||||||
export { PromotionRuleValueRepository } from "./promotion-rule-value"
|
export { PromotionRuleValueRepository } from "./promotion-rule-value"
|
||||||
@@ -11,4 +11,9 @@ export class PromotionRuleValueRepository extends DALUtils.mikroOrmBaseRepositor
|
|||||||
create: CreatePromotionRuleValueDTO
|
create: CreatePromotionRuleValueDTO
|
||||||
update: UpdatePromotionRuleValueDTO
|
update: UpdatePromotionRuleValueDTO
|
||||||
}
|
}
|
||||||
>(PromotionRuleValue) {}
|
>(PromotionRuleValue) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,4 +8,9 @@ export class PromotionRuleRepository extends DALUtils.mikroOrmBaseRepositoryFact
|
|||||||
create: CreatePromotionRuleDTO
|
create: CreatePromotionRuleDTO
|
||||||
update: UpdatePromotionRuleDTO
|
update: UpdatePromotionRuleDTO
|
||||||
}
|
}
|
||||||
>(PromotionRule) {}
|
>(PromotionRule) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,4 +8,9 @@ export class PromotionRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
|
|||||||
create: CreatePromotionDTO
|
create: CreatePromotionDTO
|
||||||
Update: UpdatePromotionDTO
|
Update: UpdatePromotionDTO
|
||||||
}
|
}
|
||||||
>(Promotion) {}
|
>(Promotion) {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import { Context, DAL, FindConfig, PromotionTypes } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
InjectManager,
|
||||||
|
InjectTransactionManager,
|
||||||
|
MedusaContext,
|
||||||
|
ModulesSdkUtils,
|
||||||
|
retrieveEntity,
|
||||||
|
} from "@medusajs/utils"
|
||||||
|
import { CampaignBudget } from "@models"
|
||||||
|
import { CampaignBudgetRepository } from "@repositories"
|
||||||
|
import { CreateCampaignBudgetDTO, UpdateCampaignBudgetDTO } from "../types"
|
||||||
|
|
||||||
|
type InjectedDependencies = {
|
||||||
|
campaignBudgetRepository: DAL.RepositoryService
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class CampaignBudgetService<
|
||||||
|
TEntity extends CampaignBudget = CampaignBudget
|
||||||
|
> {
|
||||||
|
protected readonly campaignBudgetRepository_: DAL.RepositoryService
|
||||||
|
|
||||||
|
constructor({ campaignBudgetRepository }: InjectedDependencies) {
|
||||||
|
this.campaignBudgetRepository_ = campaignBudgetRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectManager("campaignBudgetRepository_")
|
||||||
|
async retrieve(
|
||||||
|
campaignBudgetId: string,
|
||||||
|
config: FindConfig<PromotionTypes.CampaignBudgetDTO> = {},
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<TEntity> {
|
||||||
|
return (await retrieveEntity<
|
||||||
|
CampaignBudget,
|
||||||
|
PromotionTypes.CampaignBudgetDTO
|
||||||
|
>({
|
||||||
|
id: campaignBudgetId,
|
||||||
|
entityName: CampaignBudget.name,
|
||||||
|
repository: this.campaignBudgetRepository_,
|
||||||
|
config,
|
||||||
|
sharedContext,
|
||||||
|
})) as TEntity
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectManager("campaignBudgetRepository_")
|
||||||
|
async list(
|
||||||
|
filters: PromotionTypes.FilterableCampaignBudgetProps = {},
|
||||||
|
config: FindConfig<PromotionTypes.CampaignBudgetDTO> = {},
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<TEntity[]> {
|
||||||
|
const queryOptions = ModulesSdkUtils.buildQuery<CampaignBudget>(
|
||||||
|
filters,
|
||||||
|
config
|
||||||
|
)
|
||||||
|
|
||||||
|
return (await this.campaignBudgetRepository_.find(
|
||||||
|
queryOptions,
|
||||||
|
sharedContext
|
||||||
|
)) as TEntity[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectManager("campaignBudgetRepository_")
|
||||||
|
async listAndCount(
|
||||||
|
filters: PromotionTypes.FilterableCampaignBudgetProps = {},
|
||||||
|
config: FindConfig<PromotionTypes.CampaignBudgetDTO> = {},
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<[TEntity[], number]> {
|
||||||
|
const queryOptions = ModulesSdkUtils.buildQuery<CampaignBudget>(
|
||||||
|
filters,
|
||||||
|
config
|
||||||
|
)
|
||||||
|
|
||||||
|
return (await this.campaignBudgetRepository_.findAndCount(
|
||||||
|
queryOptions,
|
||||||
|
sharedContext
|
||||||
|
)) as [TEntity[], number]
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("campaignBudgetRepository_")
|
||||||
|
async create(
|
||||||
|
data: CreateCampaignBudgetDTO[],
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<TEntity[]> {
|
||||||
|
return (await (
|
||||||
|
this.campaignBudgetRepository_ as CampaignBudgetRepository
|
||||||
|
).create(data, sharedContext)) as TEntity[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("campaignBudgetRepository_")
|
||||||
|
async update(
|
||||||
|
data: UpdateCampaignBudgetDTO[],
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<TEntity[]> {
|
||||||
|
return (await (
|
||||||
|
this.campaignBudgetRepository_ as CampaignBudgetRepository
|
||||||
|
).update(data, sharedContext)) as TEntity[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("campaignBudgetRepository_")
|
||||||
|
async delete(
|
||||||
|
ids: string[],
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<void> {
|
||||||
|
await this.campaignBudgetRepository_.delete(ids, sharedContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import { Context, DAL, FindConfig, PromotionTypes } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
InjectManager,
|
||||||
|
InjectTransactionManager,
|
||||||
|
MedusaContext,
|
||||||
|
ModulesSdkUtils,
|
||||||
|
retrieveEntity,
|
||||||
|
} from "@medusajs/utils"
|
||||||
|
import { Campaign } from "@models"
|
||||||
|
import { CampaignRepository } from "@repositories"
|
||||||
|
import { CreateCampaignDTO, UpdateCampaignDTO } from "../types"
|
||||||
|
|
||||||
|
type InjectedDependencies = {
|
||||||
|
campaignRepository: DAL.RepositoryService
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class CampaignService<TEntity extends Campaign = Campaign> {
|
||||||
|
protected readonly campaignRepository_: DAL.RepositoryService
|
||||||
|
|
||||||
|
constructor({ campaignRepository }: InjectedDependencies) {
|
||||||
|
this.campaignRepository_ = campaignRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectManager("campaignRepository_")
|
||||||
|
async retrieve(
|
||||||
|
campaignId: string,
|
||||||
|
config: FindConfig<PromotionTypes.CampaignDTO> = {},
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<TEntity> {
|
||||||
|
return (await retrieveEntity<Campaign, PromotionTypes.CampaignDTO>({
|
||||||
|
id: campaignId,
|
||||||
|
entityName: Campaign.name,
|
||||||
|
repository: this.campaignRepository_,
|
||||||
|
config,
|
||||||
|
sharedContext,
|
||||||
|
})) as TEntity
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectManager("campaignRepository_")
|
||||||
|
async list(
|
||||||
|
filters: PromotionTypes.FilterableCampaignProps = {},
|
||||||
|
config: FindConfig<PromotionTypes.CampaignDTO> = {},
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<TEntity[]> {
|
||||||
|
const queryOptions = ModulesSdkUtils.buildQuery<Campaign>(filters, config)
|
||||||
|
|
||||||
|
return (await this.campaignRepository_.find(
|
||||||
|
queryOptions,
|
||||||
|
sharedContext
|
||||||
|
)) as TEntity[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectManager("campaignRepository_")
|
||||||
|
async listAndCount(
|
||||||
|
filters: PromotionTypes.FilterableCampaignProps = {},
|
||||||
|
config: FindConfig<PromotionTypes.CampaignDTO> = {},
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<[TEntity[], number]> {
|
||||||
|
const queryOptions = ModulesSdkUtils.buildQuery<Campaign>(filters, config)
|
||||||
|
|
||||||
|
return (await this.campaignRepository_.findAndCount(
|
||||||
|
queryOptions,
|
||||||
|
sharedContext
|
||||||
|
)) as [TEntity[], number]
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("campaignRepository_")
|
||||||
|
async create(
|
||||||
|
data: CreateCampaignDTO[],
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<TEntity[]> {
|
||||||
|
return (await (this.campaignRepository_ as CampaignRepository).create(
|
||||||
|
data,
|
||||||
|
sharedContext
|
||||||
|
)) as TEntity[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("campaignRepository_")
|
||||||
|
async update(
|
||||||
|
data: UpdateCampaignDTO[],
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<TEntity[]> {
|
||||||
|
return (await (this.campaignRepository_ as CampaignRepository).update(
|
||||||
|
data,
|
||||||
|
sharedContext
|
||||||
|
)) as TEntity[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("campaignRepository_")
|
||||||
|
async delete(
|
||||||
|
ids: string[],
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<void> {
|
||||||
|
await this.campaignRepository_.delete(ids, sharedContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
export { default as ApplicationMethodService } from "./application-method"
|
export { default as ApplicationMethodService } from "./application-method"
|
||||||
|
export { default as CampaignService } from "./campaign"
|
||||||
|
export { default as CampaignBudgetService } from "./campaign-budget"
|
||||||
export { default as PromotionService } from "./promotion"
|
export { default as PromotionService } from "./promotion"
|
||||||
export { default as PromotionModuleService } from "./promotion-module"
|
export { default as PromotionModuleService } from "./promotion-module"
|
||||||
export { default as PromotionRuleService } from "./promotion-rule"
|
export { default as PromotionRuleService } from "./promotion-rule"
|
||||||
|
|||||||
@@ -7,30 +7,41 @@ import {
|
|||||||
PromotionTypes,
|
PromotionTypes,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
|
ApplicationMethodTargetType,
|
||||||
InjectManager,
|
InjectManager,
|
||||||
InjectTransactionManager,
|
InjectTransactionManager,
|
||||||
MedusaContext,
|
MedusaContext,
|
||||||
MedusaError,
|
MedusaError,
|
||||||
|
isString,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import { ApplicationMethod, Promotion } from "@models"
|
import { ApplicationMethod, Promotion } from "@models"
|
||||||
import {
|
import {
|
||||||
ApplicationMethodService,
|
ApplicationMethodService,
|
||||||
|
CampaignBudgetService,
|
||||||
|
CampaignService,
|
||||||
PromotionRuleService,
|
PromotionRuleService,
|
||||||
PromotionRuleValueService,
|
PromotionRuleValueService,
|
||||||
PromotionService,
|
PromotionService,
|
||||||
} from "@services"
|
} from "@services"
|
||||||
import { joinerConfig } from "../joiner-config"
|
|
||||||
import {
|
import {
|
||||||
CreateApplicationMethodDTO,
|
CreateApplicationMethodDTO,
|
||||||
|
CreateCampaignBudgetDTO,
|
||||||
|
CreateCampaignDTO,
|
||||||
CreatePromotionDTO,
|
CreatePromotionDTO,
|
||||||
|
CreatePromotionRuleDTO,
|
||||||
UpdateApplicationMethodDTO,
|
UpdateApplicationMethodDTO,
|
||||||
|
UpdateCampaignBudgetDTO,
|
||||||
|
UpdateCampaignDTO,
|
||||||
UpdatePromotionDTO,
|
UpdatePromotionDTO,
|
||||||
} from "../types"
|
} from "@types"
|
||||||
import {
|
import {
|
||||||
|
ComputeActionUtils,
|
||||||
allowedAllocationForQuantity,
|
allowedAllocationForQuantity,
|
||||||
|
areRulesValidForContext,
|
||||||
validateApplicationMethodAttributes,
|
validateApplicationMethodAttributes,
|
||||||
validatePromotionRuleAttributes,
|
validatePromotionRuleAttributes,
|
||||||
} from "../utils"
|
} from "@utils"
|
||||||
|
import { joinerConfig } from "../joiner-config"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
baseRepository: DAL.RepositoryService
|
baseRepository: DAL.RepositoryService
|
||||||
@@ -38,6 +49,8 @@ type InjectedDependencies = {
|
|||||||
applicationMethodService: ApplicationMethodService
|
applicationMethodService: ApplicationMethodService
|
||||||
promotionRuleService: PromotionRuleService
|
promotionRuleService: PromotionRuleService
|
||||||
promotionRuleValueService: PromotionRuleValueService
|
promotionRuleValueService: PromotionRuleValueService
|
||||||
|
campaignService: CampaignService
|
||||||
|
campaignBudgetService: CampaignBudgetService
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class PromotionModuleService<
|
export default class PromotionModuleService<
|
||||||
@@ -49,6 +62,8 @@ export default class PromotionModuleService<
|
|||||||
protected applicationMethodService_: ApplicationMethodService
|
protected applicationMethodService_: ApplicationMethodService
|
||||||
protected promotionRuleService_: PromotionRuleService
|
protected promotionRuleService_: PromotionRuleService
|
||||||
protected promotionRuleValueService_: PromotionRuleValueService
|
protected promotionRuleValueService_: PromotionRuleValueService
|
||||||
|
protected campaignService_: CampaignService
|
||||||
|
protected campaignBudgetService_: CampaignBudgetService
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{
|
{
|
||||||
@@ -57,6 +72,8 @@ export default class PromotionModuleService<
|
|||||||
applicationMethodService,
|
applicationMethodService,
|
||||||
promotionRuleService,
|
promotionRuleService,
|
||||||
promotionRuleValueService,
|
promotionRuleValueService,
|
||||||
|
campaignService,
|
||||||
|
campaignBudgetService,
|
||||||
}: InjectedDependencies,
|
}: InjectedDependencies,
|
||||||
protected readonly moduleDeclaration: InternalModuleDeclaration
|
protected readonly moduleDeclaration: InternalModuleDeclaration
|
||||||
) {
|
) {
|
||||||
@@ -65,12 +82,169 @@ export default class PromotionModuleService<
|
|||||||
this.applicationMethodService_ = applicationMethodService
|
this.applicationMethodService_ = applicationMethodService
|
||||||
this.promotionRuleService_ = promotionRuleService
|
this.promotionRuleService_ = promotionRuleService
|
||||||
this.promotionRuleValueService_ = promotionRuleValueService
|
this.promotionRuleValueService_ = promotionRuleValueService
|
||||||
|
this.campaignService_ = campaignService
|
||||||
|
this.campaignBudgetService_ = campaignBudgetService
|
||||||
}
|
}
|
||||||
|
|
||||||
__joinerConfig(): ModuleJoinerConfig {
|
__joinerConfig(): ModuleJoinerConfig {
|
||||||
return joinerConfig
|
return joinerConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async computeActions(
|
||||||
|
promotionCodesToApply: string[],
|
||||||
|
applicationContext: PromotionTypes.ComputeActionContext,
|
||||||
|
// TODO: specify correct type with options
|
||||||
|
options: Record<string, any> = {}
|
||||||
|
): Promise<PromotionTypes.ComputeActions[]> {
|
||||||
|
const computedActions: PromotionTypes.ComputeActions[] = []
|
||||||
|
const { items = [], shipping_methods: shippingMethods = [] } =
|
||||||
|
applicationContext
|
||||||
|
const appliedItemCodes: string[] = []
|
||||||
|
const appliedShippingCodes: string[] = []
|
||||||
|
const codeAdjustmentMap = new Map<
|
||||||
|
string,
|
||||||
|
PromotionTypes.ComputeActionAdjustmentLine
|
||||||
|
>()
|
||||||
|
const methodIdPromoValueMap = new Map<string, number>()
|
||||||
|
|
||||||
|
items.forEach((item) => {
|
||||||
|
item.adjustments?.forEach((adjustment) => {
|
||||||
|
if (isString(adjustment.code)) {
|
||||||
|
codeAdjustmentMap.set(adjustment.code, adjustment)
|
||||||
|
appliedItemCodes.push(adjustment.code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
shippingMethods.forEach((shippingMethod) => {
|
||||||
|
shippingMethod.adjustments?.forEach((adjustment) => {
|
||||||
|
if (isString(adjustment.code)) {
|
||||||
|
codeAdjustmentMap.set(adjustment.code, adjustment)
|
||||||
|
appliedShippingCodes.push(adjustment.code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const promotions = await this.list(
|
||||||
|
{
|
||||||
|
code: [
|
||||||
|
...promotionCodesToApply,
|
||||||
|
...appliedItemCodes,
|
||||||
|
...appliedShippingCodes,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relations: [
|
||||||
|
"application_method",
|
||||||
|
"application_method.target_rules",
|
||||||
|
"application_method.target_rules.values",
|
||||||
|
"rules",
|
||||||
|
"rules.values",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const existingPromotionsMap = new Map<string, PromotionTypes.PromotionDTO>(
|
||||||
|
promotions.map((promotion) => [promotion.code!, promotion])
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const appliedCode of [...appliedShippingCodes, ...appliedItemCodes]) {
|
||||||
|
const promotion = existingPromotionsMap.get(appliedCode)
|
||||||
|
|
||||||
|
if (!promotion) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Applied Promotion for code (${appliedCode}) not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (promotionCodesToApply.includes(appliedCode)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appliedItemCodes.includes(appliedCode)) {
|
||||||
|
computedActions.push({
|
||||||
|
action: "removeItemAdjustment",
|
||||||
|
adjustment_id: codeAdjustmentMap.get(appliedCode)!.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appliedShippingCodes.includes(appliedCode)) {
|
||||||
|
computedActions.push({
|
||||||
|
action: "removeShippingMethodAdjustment",
|
||||||
|
adjustment_id: codeAdjustmentMap.get(appliedCode)!.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const promotionCode of promotionCodesToApply) {
|
||||||
|
const promotion = existingPromotionsMap.get(promotionCode)
|
||||||
|
|
||||||
|
if (!promotion) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Promotion for code (${promotionCode}) not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
application_method: applicationMethod,
|
||||||
|
rules: promotionRules = [],
|
||||||
|
} = promotion
|
||||||
|
|
||||||
|
if (!applicationMethod) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPromotionApplicable = areRulesValidForContext(
|
||||||
|
promotionRules,
|
||||||
|
applicationContext
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!isPromotionApplicable) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applicationMethod.target_type === ApplicationMethodTargetType.ORDER) {
|
||||||
|
const computedActionsForItems =
|
||||||
|
ComputeActionUtils.getComputedActionsForOrder(
|
||||||
|
promotion,
|
||||||
|
applicationContext,
|
||||||
|
methodIdPromoValueMap
|
||||||
|
)
|
||||||
|
|
||||||
|
computedActions.push(...computedActionsForItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applicationMethod.target_type === ApplicationMethodTargetType.ITEMS) {
|
||||||
|
const computedActionsForItems =
|
||||||
|
ComputeActionUtils.getComputedActionsForItems(
|
||||||
|
promotion,
|
||||||
|
applicationContext[ApplicationMethodTargetType.ITEMS],
|
||||||
|
methodIdPromoValueMap
|
||||||
|
)
|
||||||
|
|
||||||
|
computedActions.push(...computedActionsForItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
applicationMethod.target_type ===
|
||||||
|
ApplicationMethodTargetType.SHIPPING_METHODS
|
||||||
|
) {
|
||||||
|
const computedActionsForShippingMethods =
|
||||||
|
ComputeActionUtils.getComputedActionsForShippingMethods(
|
||||||
|
promotion,
|
||||||
|
applicationContext[ApplicationMethodTargetType.SHIPPING_METHODS],
|
||||||
|
methodIdPromoValueMap
|
||||||
|
)
|
||||||
|
|
||||||
|
computedActions.push(...computedActionsForShippingMethods)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return computedActions
|
||||||
|
}
|
||||||
|
|
||||||
@InjectManager("baseRepository_")
|
@InjectManager("baseRepository_")
|
||||||
async retrieve(
|
async retrieve(
|
||||||
id: string,
|
id: string,
|
||||||
@@ -111,15 +285,28 @@ export default class PromotionModuleService<
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@InjectManager("baseRepository_")
|
async create(
|
||||||
|
data: PromotionTypes.CreatePromotionDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<PromotionTypes.PromotionDTO>
|
||||||
|
|
||||||
async create(
|
async create(
|
||||||
data: PromotionTypes.CreatePromotionDTO[],
|
data: PromotionTypes.CreatePromotionDTO[],
|
||||||
@MedusaContext() sharedContext: Context = {}
|
sharedContext?: Context
|
||||||
): Promise<PromotionTypes.PromotionDTO[]> {
|
): Promise<PromotionTypes.PromotionDTO[]>
|
||||||
const promotions = await this.create_(data, sharedContext)
|
|
||||||
|
|
||||||
return await this.list(
|
@InjectManager("baseRepository_")
|
||||||
{ id: promotions.map((p) => p!.id) },
|
async create(
|
||||||
|
data:
|
||||||
|
| PromotionTypes.CreatePromotionDTO
|
||||||
|
| PromotionTypes.CreatePromotionDTO[],
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<PromotionTypes.PromotionDTO | PromotionTypes.PromotionDTO[]> {
|
||||||
|
const input = Array.isArray(data) ? data : [data]
|
||||||
|
const createdPromotions = await this.create_(input, sharedContext)
|
||||||
|
|
||||||
|
const promotions = await this.list(
|
||||||
|
{ id: createdPromotions.map((p) => p!.id) },
|
||||||
{
|
{
|
||||||
relations: [
|
relations: [
|
||||||
"application_method",
|
"application_method",
|
||||||
@@ -131,6 +318,8 @@ export default class PromotionModuleService<
|
|||||||
},
|
},
|
||||||
sharedContext
|
sharedContext
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return Array.isArray(data) ? promotions : promotions[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
@InjectTransactionManager("baseRepository_")
|
@InjectTransactionManager("baseRepository_")
|
||||||
@@ -194,6 +383,17 @@ export default class PromotionModuleService<
|
|||||||
promotion,
|
promotion,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
applicationMethodData.target_type ===
|
||||||
|
ApplicationMethodTargetType.ORDER &&
|
||||||
|
targetRulesData.length
|
||||||
|
) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Target rules for application method with target type (${ApplicationMethodTargetType.ORDER}) is not allowed`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
validateApplicationMethodAttributes(applicationMethodData)
|
validateApplicationMethodAttributes(applicationMethodData)
|
||||||
applicationMethodsData.push(applicationMethodData)
|
applicationMethodsData.push(applicationMethodData)
|
||||||
|
|
||||||
@@ -228,15 +428,28 @@ export default class PromotionModuleService<
|
|||||||
return createdPromotions
|
return createdPromotions
|
||||||
}
|
}
|
||||||
|
|
||||||
@InjectManager("baseRepository_")
|
async update(
|
||||||
|
data: PromotionTypes.UpdatePromotionDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<PromotionTypes.PromotionDTO>
|
||||||
|
|
||||||
async update(
|
async update(
|
||||||
data: PromotionTypes.UpdatePromotionDTO[],
|
data: PromotionTypes.UpdatePromotionDTO[],
|
||||||
@MedusaContext() sharedContext: Context = {}
|
sharedContext?: Context
|
||||||
): Promise<PromotionTypes.PromotionDTO[]> {
|
): Promise<PromotionTypes.PromotionDTO[]>
|
||||||
const promotions = await this.update_(data, sharedContext)
|
|
||||||
|
|
||||||
return await this.list(
|
@InjectManager("baseRepository_")
|
||||||
{ id: promotions.map((p) => p!.id) },
|
async update(
|
||||||
|
data:
|
||||||
|
| PromotionTypes.UpdatePromotionDTO
|
||||||
|
| PromotionTypes.UpdatePromotionDTO[],
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<PromotionTypes.PromotionDTO | PromotionTypes.PromotionDTO[]> {
|
||||||
|
const input = Array.isArray(data) ? data : [data]
|
||||||
|
const updatedPromotions = await this.update_(input, sharedContext)
|
||||||
|
|
||||||
|
const promotions = await this.list(
|
||||||
|
{ id: updatedPromotions.map((p) => p!.id) },
|
||||||
{
|
{
|
||||||
relations: [
|
relations: [
|
||||||
"application_method",
|
"application_method",
|
||||||
@@ -247,6 +460,8 @@ export default class PromotionModuleService<
|
|||||||
},
|
},
|
||||||
sharedContext
|
sharedContext
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return Array.isArray(data) ? promotions : promotions[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
@InjectTransactionManager("baseRepository_")
|
@InjectTransactionManager("baseRepository_")
|
||||||
@@ -394,7 +609,7 @@ export default class PromotionModuleService<
|
|||||||
|
|
||||||
for (const ruleData of rulesData) {
|
for (const ruleData of rulesData) {
|
||||||
const { values, ...rest } = ruleData
|
const { values, ...rest } = ruleData
|
||||||
const promotionRuleData = {
|
const promotionRuleData: CreatePromotionRuleDTO = {
|
||||||
...rest,
|
...rest,
|
||||||
[relationName]: [relation],
|
[relationName]: [relation],
|
||||||
}
|
}
|
||||||
@@ -416,10 +631,12 @@ export default class PromotionModuleService<
|
|||||||
|
|
||||||
@InjectTransactionManager("baseRepository_")
|
@InjectTransactionManager("baseRepository_")
|
||||||
async delete(
|
async delete(
|
||||||
ids: string[],
|
ids: string | string[],
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.promotionService_.delete(ids, sharedContext)
|
const promotionIds = Array.isArray(ids) ? ids : [ids]
|
||||||
|
|
||||||
|
await this.promotionService_.delete(promotionIds, sharedContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
@InjectManager("baseRepository_")
|
@InjectManager("baseRepository_")
|
||||||
@@ -520,4 +737,214 @@ export default class PromotionModuleService<
|
|||||||
sharedContext
|
sharedContext
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InjectManager("baseRepository_")
|
||||||
|
async retrieveCampaign(
|
||||||
|
id: string,
|
||||||
|
config: FindConfig<PromotionTypes.CampaignDTO> = {},
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<PromotionTypes.CampaignDTO> {
|
||||||
|
const campaign = await this.campaignService_.retrieve(
|
||||||
|
id,
|
||||||
|
config,
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
return await this.baseRepository_.serialize<PromotionTypes.CampaignDTO>(
|
||||||
|
campaign,
|
||||||
|
{
|
||||||
|
populate: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectManager("baseRepository_")
|
||||||
|
async listCampaigns(
|
||||||
|
filters: PromotionTypes.FilterableCampaignProps = {},
|
||||||
|
config: FindConfig<PromotionTypes.CampaignDTO> = {},
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<PromotionTypes.CampaignDTO[]> {
|
||||||
|
const campaigns = await this.campaignService_.list(
|
||||||
|
filters,
|
||||||
|
config,
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
return await this.baseRepository_.serialize<PromotionTypes.CampaignDTO[]>(
|
||||||
|
campaigns,
|
||||||
|
{
|
||||||
|
populate: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async createCampaigns(
|
||||||
|
data: PromotionTypes.CreateCampaignDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<PromotionTypes.CampaignDTO>
|
||||||
|
|
||||||
|
async createCampaigns(
|
||||||
|
data: PromotionTypes.CreateCampaignDTO[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<PromotionTypes.CampaignDTO[]>
|
||||||
|
|
||||||
|
@InjectManager("baseRepository_")
|
||||||
|
async createCampaigns(
|
||||||
|
data: PromotionTypes.CreateCampaignDTO | PromotionTypes.CreateCampaignDTO[],
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<PromotionTypes.CampaignDTO | PromotionTypes.CampaignDTO[]> {
|
||||||
|
const input = Array.isArray(data) ? data : [data]
|
||||||
|
const createdCampaigns = await this.createCampaigns_(input, sharedContext)
|
||||||
|
|
||||||
|
const campaigns = await this.listCampaigns(
|
||||||
|
{ id: createdCampaigns.map((p) => p!.id) },
|
||||||
|
{
|
||||||
|
relations: ["budget"],
|
||||||
|
},
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
return Array.isArray(data) ? campaigns : campaigns[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("baseRepository_")
|
||||||
|
protected async createCampaigns_(
|
||||||
|
data: PromotionTypes.CreateCampaignDTO[],
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
) {
|
||||||
|
const campaignsData: CreateCampaignDTO[] = []
|
||||||
|
const campaignBudgetsData: CreateCampaignBudgetDTO[] = []
|
||||||
|
const campaignIdentifierBudgetMap = new Map<
|
||||||
|
string,
|
||||||
|
CreateCampaignBudgetDTO
|
||||||
|
>()
|
||||||
|
|
||||||
|
for (const createCampaignData of data) {
|
||||||
|
const { budget: campaignBudgetData, ...campaignData } = createCampaignData
|
||||||
|
|
||||||
|
if (campaignBudgetData) {
|
||||||
|
campaignIdentifierBudgetMap.set(
|
||||||
|
campaignData.campaign_identifier,
|
||||||
|
campaignBudgetData
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
campaignsData.push(campaignData)
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdCampaigns = await this.campaignService_.create(
|
||||||
|
campaignsData,
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const createdCampaign of createdCampaigns) {
|
||||||
|
const campaignBudgetData = campaignIdentifierBudgetMap.get(
|
||||||
|
createdCampaign.campaign_identifier
|
||||||
|
)
|
||||||
|
|
||||||
|
if (campaignBudgetData) {
|
||||||
|
campaignBudgetsData.push({
|
||||||
|
...campaignBudgetData,
|
||||||
|
campaign: createdCampaign.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (campaignBudgetsData.length) {
|
||||||
|
await this.campaignBudgetService_.create(
|
||||||
|
campaignBudgetsData,
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return createdCampaigns
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateCampaigns(
|
||||||
|
data: PromotionTypes.UpdateCampaignDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<PromotionTypes.CampaignDTO>
|
||||||
|
|
||||||
|
async updateCampaigns(
|
||||||
|
data: PromotionTypes.UpdateCampaignDTO[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<PromotionTypes.CampaignDTO[]>
|
||||||
|
|
||||||
|
@InjectManager("baseRepository_")
|
||||||
|
async updateCampaigns(
|
||||||
|
data: PromotionTypes.UpdateCampaignDTO | PromotionTypes.UpdateCampaignDTO[],
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<PromotionTypes.CampaignDTO | PromotionTypes.CampaignDTO[]> {
|
||||||
|
const input = Array.isArray(data) ? data : [data]
|
||||||
|
|
||||||
|
const updatedCampaigns = await this.updateCampaigns_(input, sharedContext)
|
||||||
|
|
||||||
|
const campaigns = await this.listCampaigns(
|
||||||
|
{ id: updatedCampaigns.map((p) => p!.id) },
|
||||||
|
{
|
||||||
|
relations: ["budget"],
|
||||||
|
},
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
return Array.isArray(data) ? campaigns : campaigns[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("baseRepository_")
|
||||||
|
protected async updateCampaigns_(
|
||||||
|
data: PromotionTypes.UpdateCampaignDTO[],
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
) {
|
||||||
|
const campaignIds = data.map((d) => d.id)
|
||||||
|
const campaignsData: UpdateCampaignDTO[] = []
|
||||||
|
const campaignBudgetsData: UpdateCampaignBudgetDTO[] = []
|
||||||
|
|
||||||
|
const existingCampaigns = await this.listCampaigns(
|
||||||
|
{
|
||||||
|
id: campaignIds,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relations: ["budget"],
|
||||||
|
},
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
const existingCampaignsMap = new Map<string, PromotionTypes.CampaignDTO>(
|
||||||
|
existingCampaigns.map((campaign) => [campaign.id, campaign])
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const updateCampaignData of data) {
|
||||||
|
const { budget: campaignBudgetData, ...campaignData } = updateCampaignData
|
||||||
|
|
||||||
|
const existingCampaign = existingCampaignsMap.get(campaignData.id)
|
||||||
|
const existingCampaignBudget = existingCampaign?.budget
|
||||||
|
|
||||||
|
campaignsData.push(campaignData)
|
||||||
|
|
||||||
|
if (existingCampaignBudget && campaignBudgetData) {
|
||||||
|
campaignBudgetsData.push({
|
||||||
|
id: existingCampaignBudget.id,
|
||||||
|
...campaignBudgetData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedCampaigns = await this.campaignService_.update(campaignsData)
|
||||||
|
|
||||||
|
if (campaignBudgetsData.length) {
|
||||||
|
await this.campaignBudgetService_.update(campaignBudgetsData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedCampaigns
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("baseRepository_")
|
||||||
|
async deleteCampaigns(
|
||||||
|
ids: string | string[],
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<void> {
|
||||||
|
const campaignIds = Array.isArray(ids) ? ids : [ids]
|
||||||
|
|
||||||
|
await this.promotionService_.delete(campaignIds, sharedContext)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { CampaignBudgetTypeValues } from "@medusajs/types"
|
||||||
|
import { Campaign } from "@models"
|
||||||
|
|
||||||
|
export interface CreateCampaignBudgetDTO {
|
||||||
|
type: CampaignBudgetTypeValues
|
||||||
|
limit: number | null
|
||||||
|
used?: number
|
||||||
|
campaign?: Campaign | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateCampaignBudgetDTO {
|
||||||
|
id: string
|
||||||
|
type?: CampaignBudgetTypeValues
|
||||||
|
limit?: number | null
|
||||||
|
used?: number
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
export interface CreateCampaignDTO {
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
currency?: string
|
||||||
|
campaign_identifier: string
|
||||||
|
starts_at: Date
|
||||||
|
ends_at: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateCampaignDTO {
|
||||||
|
id: string
|
||||||
|
name?: string
|
||||||
|
description?: string
|
||||||
|
currency?: string
|
||||||
|
campaign_identifier?: string
|
||||||
|
starts_at?: Date
|
||||||
|
ends_at?: Date
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ export type InitializeModuleInjectableDependencies = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export * from "./application-method"
|
export * from "./application-method"
|
||||||
|
export * from "./campaign"
|
||||||
|
export * from "./campaign-budget"
|
||||||
export * from "./promotion"
|
export * from "./promotion"
|
||||||
export * from "./promotion-rule"
|
export * from "./promotion-rule"
|
||||||
export * from "./promotion-rule-value"
|
export * from "./promotion-rule-value"
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import { PromotionRuleDTO } from "@medusajs/types"
|
import { PromotionRuleDTO } from "@medusajs/types"
|
||||||
|
import { PromotionRule } from "@models"
|
||||||
|
|
||||||
export interface CreatePromotionRuleValueDTO {
|
export interface CreatePromotionRuleValueDTO {
|
||||||
value: any
|
value: any
|
||||||
promotion_rule: string | PromotionRuleDTO
|
promotion_rule: string | PromotionRuleDTO | PromotionRule
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdatePromotionRuleValueDTO {
|
export interface UpdatePromotionRuleValueDTO {
|
||||||
id: string
|
id: string
|
||||||
value: any
|
value: any
|
||||||
promotion_rule: string | PromotionRuleDTO
|
promotion_rule: string | PromotionRuleDTO | PromotionRule
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { PromotionRuleOperatorValues } from "@medusajs/types"
|
import { PromotionRuleOperatorValues } from "@medusajs/types"
|
||||||
|
|
||||||
export interface CreatePromotionRuleDTO {
|
export interface CreatePromotionRuleDTO {
|
||||||
description?: string
|
description?: string | null
|
||||||
attribute: string
|
attribute: string
|
||||||
operator: PromotionRuleOperatorValues
|
operator: PromotionRuleOperatorValues
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./items"
|
||||||
|
export * from "./order"
|
||||||
|
export * from "./shipping-methods"
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import {
|
||||||
|
ApplicationMethodAllocationValues,
|
||||||
|
PromotionTypes,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
ApplicationMethodAllocation,
|
||||||
|
ApplicationMethodTargetType,
|
||||||
|
MedusaError,
|
||||||
|
} from "@medusajs/utils"
|
||||||
|
import { areRulesValidForContext } from "../validations"
|
||||||
|
|
||||||
|
export function getComputedActionsForItems(
|
||||||
|
promotion: PromotionTypes.PromotionDTO,
|
||||||
|
itemApplicationContext: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.ITEMS],
|
||||||
|
methodIdPromoValueMap: Map<string, number>,
|
||||||
|
allocationOverride?: ApplicationMethodAllocationValues
|
||||||
|
): PromotionTypes.ComputeActions[] {
|
||||||
|
const applicableItems: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.ITEMS] =
|
||||||
|
[]
|
||||||
|
|
||||||
|
if (!itemApplicationContext) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`"items" should be present as an array in the context for computeActions`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const itemContext of itemApplicationContext) {
|
||||||
|
const isPromotionApplicableToItem = areRulesValidForContext(
|
||||||
|
promotion?.application_method?.target_rules!,
|
||||||
|
itemContext
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!isPromotionApplicableToItem) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
applicableItems.push(itemContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
return applyPromotionToItems(
|
||||||
|
promotion,
|
||||||
|
applicableItems,
|
||||||
|
methodIdPromoValueMap,
|
||||||
|
allocationOverride
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPromotionToItems(
|
||||||
|
promotion: PromotionTypes.PromotionDTO,
|
||||||
|
items: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.ITEMS],
|
||||||
|
methodIdPromoValueMap: Map<string, number>,
|
||||||
|
allocationOverride?: ApplicationMethodAllocationValues
|
||||||
|
): PromotionTypes.ComputeActions[] {
|
||||||
|
const { application_method: applicationMethod } = promotion
|
||||||
|
const allocation = applicationMethod?.allocation!
|
||||||
|
const computedActions: PromotionTypes.ComputeActions[] = []
|
||||||
|
|
||||||
|
if (
|
||||||
|
[allocation, allocationOverride].includes(ApplicationMethodAllocation.EACH)
|
||||||
|
) {
|
||||||
|
for (const method of items!) {
|
||||||
|
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
|
||||||
|
const promotionValue = parseFloat(applicationMethod!.value!)
|
||||||
|
const applicableTotal =
|
||||||
|
method.unit_price *
|
||||||
|
Math.min(method.quantity, applicationMethod?.max_quantity!) -
|
||||||
|
appliedPromoValue
|
||||||
|
|
||||||
|
const amount = Math.min(promotionValue, applicableTotal)
|
||||||
|
|
||||||
|
if (amount <= 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
methodIdPromoValueMap.set(method.id, appliedPromoValue + amount)
|
||||||
|
|
||||||
|
computedActions.push({
|
||||||
|
action: "addItemAdjustment",
|
||||||
|
item_id: method.id,
|
||||||
|
amount,
|
||||||
|
code: promotion.code!,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
[allocation, allocationOverride].includes(
|
||||||
|
ApplicationMethodAllocation.ACROSS
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const totalApplicableValue = items!.reduce((acc, method) => {
|
||||||
|
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
|
||||||
|
return (
|
||||||
|
acc +
|
||||||
|
method.unit_price *
|
||||||
|
Math.min(method.quantity, applicationMethod?.max_quantity!) -
|
||||||
|
appliedPromoValue
|
||||||
|
)
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
for (const method of items!) {
|
||||||
|
const promotionValue = parseFloat(applicationMethod!.value!)
|
||||||
|
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
|
||||||
|
|
||||||
|
const applicableTotal =
|
||||||
|
method.unit_price *
|
||||||
|
Math.min(method.quantity, applicationMethod?.max_quantity!) -
|
||||||
|
appliedPromoValue
|
||||||
|
|
||||||
|
// TODO: should we worry about precision here?
|
||||||
|
const applicablePromotionValue =
|
||||||
|
(applicableTotal / totalApplicableValue) * promotionValue
|
||||||
|
|
||||||
|
const amount = Math.min(applicablePromotionValue, applicableTotal)
|
||||||
|
|
||||||
|
if (amount <= 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
computedActions.push({
|
||||||
|
action: "addItemAdjustment",
|
||||||
|
item_id: method.id,
|
||||||
|
amount,
|
||||||
|
code: promotion.code!,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return computedActions
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { PromotionTypes } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
ApplicationMethodAllocation,
|
||||||
|
ApplicationMethodTargetType,
|
||||||
|
} from "@medusajs/utils"
|
||||||
|
import { getComputedActionsForItems } from "./items"
|
||||||
|
|
||||||
|
export function getComputedActionsForOrder(
|
||||||
|
promotion: PromotionTypes.PromotionDTO,
|
||||||
|
itemApplicationContext: PromotionTypes.ComputeActionContext,
|
||||||
|
methodIdPromoValueMap: Map<string, number>
|
||||||
|
): PromotionTypes.ComputeActions[] {
|
||||||
|
return getComputedActionsForItems(
|
||||||
|
promotion,
|
||||||
|
itemApplicationContext[ApplicationMethodTargetType.ITEMS],
|
||||||
|
methodIdPromoValueMap,
|
||||||
|
ApplicationMethodAllocation.ACROSS
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import { PromotionTypes } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
ApplicationMethodAllocation,
|
||||||
|
ApplicationMethodTargetType,
|
||||||
|
MedusaError,
|
||||||
|
} from "@medusajs/utils"
|
||||||
|
import { areRulesValidForContext } from "../validations"
|
||||||
|
|
||||||
|
export function getComputedActionsForShippingMethods(
|
||||||
|
promotion: PromotionTypes.PromotionDTO,
|
||||||
|
shippingMethodApplicationContext: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.SHIPPING_METHODS],
|
||||||
|
methodIdPromoValueMap: Map<string, number>
|
||||||
|
): PromotionTypes.ComputeActions[] {
|
||||||
|
const applicableShippingItems: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.SHIPPING_METHODS] =
|
||||||
|
[]
|
||||||
|
|
||||||
|
if (!shippingMethodApplicationContext) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`"shipping_methods" should be present as an array in the context for computeActions`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const shippingMethodContext of shippingMethodApplicationContext) {
|
||||||
|
const isPromotionApplicableToItem = areRulesValidForContext(
|
||||||
|
promotion.application_method?.target_rules!,
|
||||||
|
shippingMethodContext
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!isPromotionApplicableToItem) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
applicableShippingItems.push(shippingMethodContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
return applyPromotionToShippingMethods(
|
||||||
|
promotion,
|
||||||
|
applicableShippingItems,
|
||||||
|
methodIdPromoValueMap
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPromotionToShippingMethods(
|
||||||
|
promotion: PromotionTypes.PromotionDTO,
|
||||||
|
shippingMethods: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.SHIPPING_METHODS],
|
||||||
|
methodIdPromoValueMap: Map<string, number>
|
||||||
|
): PromotionTypes.ComputeActions[] {
|
||||||
|
const { application_method: applicationMethod } = promotion
|
||||||
|
const allocation = applicationMethod?.allocation!
|
||||||
|
const computedActions: PromotionTypes.ComputeActions[] = []
|
||||||
|
|
||||||
|
if (allocation === ApplicationMethodAllocation.EACH) {
|
||||||
|
for (const method of shippingMethods!) {
|
||||||
|
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
|
||||||
|
const promotionValue = parseFloat(applicationMethod!.value!)
|
||||||
|
const applicableTotal = method.unit_price - appliedPromoValue
|
||||||
|
const amount = Math.min(promotionValue, applicableTotal)
|
||||||
|
|
||||||
|
if (amount <= 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
methodIdPromoValueMap.set(method.id, appliedPromoValue + amount)
|
||||||
|
|
||||||
|
computedActions.push({
|
||||||
|
action: "addShippingMethodAdjustment",
|
||||||
|
shipping_method_id: method.id,
|
||||||
|
amount,
|
||||||
|
code: promotion.code!,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allocation === ApplicationMethodAllocation.ACROSS) {
|
||||||
|
const totalApplicableValue = shippingMethods!.reduce((acc, method) => {
|
||||||
|
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
|
||||||
|
|
||||||
|
return acc + method.unit_price - appliedPromoValue
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
if (totalApplicableValue <= 0) {
|
||||||
|
return computedActions
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const method of shippingMethods!) {
|
||||||
|
const promotionValue = parseFloat(applicationMethod!.value!)
|
||||||
|
const applicableTotal = method.unit_price
|
||||||
|
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
|
||||||
|
|
||||||
|
// TODO: should we worry about precision here?
|
||||||
|
const applicablePromotionValue =
|
||||||
|
(applicableTotal / totalApplicableValue) * promotionValue -
|
||||||
|
appliedPromoValue
|
||||||
|
|
||||||
|
const amount = Math.min(applicablePromotionValue, applicableTotal)
|
||||||
|
|
||||||
|
if (amount <= 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
methodIdPromoValueMap.set(method.id, appliedPromoValue + amount)
|
||||||
|
|
||||||
|
computedActions.push({
|
||||||
|
action: "addShippingMethodAdjustment",
|
||||||
|
shipping_method_id: method.id,
|
||||||
|
amount,
|
||||||
|
code: promotion.code!,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return computedActions
|
||||||
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
export * as ComputeActionUtils from "./compute-actions"
|
||||||
export * from "./validations"
|
export * from "./validations"
|
||||||
@@ -12,8 +12,8 @@ import {
|
|||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
|
|
||||||
export const allowedAllocationTargetTypes: string[] = [
|
export const allowedAllocationTargetTypes: string[] = [
|
||||||
ApplicationMethodTargetType.SHIPPING,
|
ApplicationMethodTargetType.SHIPPING_METHODS,
|
||||||
ApplicationMethodTargetType.ITEM,
|
ApplicationMethodTargetType.ITEMS,
|
||||||
]
|
]
|
||||||
|
|
||||||
export const allowedAllocationTypes: string[] = [
|
export const allowedAllocationTypes: string[] = [
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { PromotionRuleOperatorValues } from "@medusajs/types"
|
import { PromotionRuleDTO, PromotionRuleOperatorValues } from "@medusajs/types"
|
||||||
import { MedusaError, PromotionRuleOperator, isPresent } from "@medusajs/utils"
|
import {
|
||||||
|
MedusaError,
|
||||||
|
PromotionRuleOperator,
|
||||||
|
isPresent,
|
||||||
|
isString,
|
||||||
|
pickValueFromObject,
|
||||||
|
} from "@medusajs/utils"
|
||||||
import { CreatePromotionRuleDTO } from "../../types"
|
import { CreatePromotionRuleDTO } from "../../types"
|
||||||
|
|
||||||
export function validatePromotionRuleAttributes(
|
export function validatePromotionRuleAttributes(
|
||||||
@@ -37,3 +43,62 @@ export function validatePromotionRuleAttributes(
|
|||||||
|
|
||||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, errors.join(", "))
|
throw new MedusaError(MedusaError.Types.INVALID_DATA, errors.join(", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function areRulesValidForContext(
|
||||||
|
rules: PromotionRuleDTO[],
|
||||||
|
context: Record<string, any>
|
||||||
|
): boolean {
|
||||||
|
return rules.every((rule) => {
|
||||||
|
const validRuleValues = rule.values?.map((ruleValue) => ruleValue.value)
|
||||||
|
|
||||||
|
if (!rule.attribute) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const valuesToCheck = pickValueFromObject(rule.attribute, context)
|
||||||
|
|
||||||
|
return evaluateRuleValueCondition(
|
||||||
|
validRuleValues.filter(isString),
|
||||||
|
rule.operator!,
|
||||||
|
valuesToCheck
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function evaluateRuleValueCondition(
|
||||||
|
ruleValues: string[],
|
||||||
|
operator: string,
|
||||||
|
ruleValuesToCheck: string[] | string
|
||||||
|
) {
|
||||||
|
if (!Array.isArray(ruleValuesToCheck)) {
|
||||||
|
ruleValuesToCheck = [ruleValuesToCheck]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ruleValuesToCheck.every((ruleValueToCheck: string) => {
|
||||||
|
if (operator === "in" || operator === "eq") {
|
||||||
|
return ruleValues.some((ruleValue) => ruleValue === ruleValueToCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operator === "ne") {
|
||||||
|
return ruleValues.some((ruleValue) => ruleValue !== ruleValueToCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operator === "gt") {
|
||||||
|
return ruleValues.some((ruleValue) => ruleValue > ruleValueToCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operator === "gte") {
|
||||||
|
return ruleValues.some((ruleValue) => ruleValue >= ruleValueToCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operator === "lt") {
|
||||||
|
return ruleValues.some((ruleValue) => ruleValue < ruleValueToCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operator === "lte") {
|
||||||
|
return ruleValues.some((ruleValue) => ruleValue <= ruleValueToCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"author": "Medusa",
|
"author": "Medusa",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@medusajs/types": "^1.11.8",
|
"@medusajs/types": "^1.11.9",
|
||||||
"cross-env": "^5.2.1",
|
"cross-env": "^5.2.1",
|
||||||
"jest": "^29.6.3",
|
"jest": "^29.6.3",
|
||||||
"rimraf": "^5.0.1",
|
"rimraf": "^5.0.1",
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ export interface CreateCartDTO {
|
|||||||
email?: string
|
email?: string
|
||||||
currency_code: string
|
currency_code: string
|
||||||
|
|
||||||
|
shipping_address_id?: string
|
||||||
|
billing_address_id?: string
|
||||||
|
|
||||||
shipping_address?: CreateAddressDTO | UpdateAddressDTO
|
shipping_address?: CreateAddressDTO | UpdateAddressDTO
|
||||||
billing_address?: CreateAddressDTO | UpdateAddressDTO
|
billing_address?: CreateAddressDTO | UpdateAddressDTO
|
||||||
|
|
||||||
@@ -42,6 +45,9 @@ export interface UpdateCartDTO {
|
|||||||
email?: string
|
email?: string
|
||||||
currency_code?: string
|
currency_code?: string
|
||||||
|
|
||||||
|
shipping_address_id?: string
|
||||||
|
billing_address_id?: string
|
||||||
|
|
||||||
billing_address?: CreateAddressDTO | UpdateAddressDTO
|
billing_address?: CreateAddressDTO | UpdateAddressDTO
|
||||||
shipping_address?: CreateAddressDTO | UpdateAddressDTO
|
shipping_address?: CreateAddressDTO | UpdateAddressDTO
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
|
import { AddressDTO } from "../address"
|
||||||
import { FindConfig } from "../common"
|
import { FindConfig } from "../common"
|
||||||
import { IModuleService } from "../modules-sdk"
|
import { IModuleService } from "../modules-sdk"
|
||||||
import { Context } from "../shared-context"
|
import { Context } from "../shared-context"
|
||||||
import { CartDTO, FilterableCartProps } from "./common"
|
import { CartAddressDTO, CartDTO, FilterableAddressProps, FilterableCartProps } from "./common"
|
||||||
import { CreateCartDTO, UpdateCartDTO } from "./mutations"
|
import {
|
||||||
|
CreateAddressDTO,
|
||||||
|
CreateCartDTO,
|
||||||
|
UpdateAddressDTO,
|
||||||
|
UpdateCartDTO,
|
||||||
|
} from "./mutations"
|
||||||
|
|
||||||
export interface ICartModuleService extends IModuleService {
|
export interface ICartModuleService extends IModuleService {
|
||||||
retrieve(
|
retrieve(
|
||||||
@@ -32,6 +38,33 @@ export interface ICartModuleService extends IModuleService {
|
|||||||
delete(cartIds: string[], sharedContext?: Context): Promise<void>
|
delete(cartIds: string[], sharedContext?: Context): Promise<void>
|
||||||
delete(cartId: string, sharedContext?: Context): Promise<void>
|
delete(cartId: string, sharedContext?: Context): Promise<void>
|
||||||
|
|
||||||
|
listAddresses(
|
||||||
|
filters?: FilterableAddressProps,
|
||||||
|
config?: FindConfig<AddressDTO>,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<CartAddressDTO[]>
|
||||||
|
|
||||||
|
createAddresses(
|
||||||
|
data: CreateAddressDTO[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<CartAddressDTO[]>
|
||||||
|
createAddresses(
|
||||||
|
data: CreateAddressDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<CartAddressDTO>
|
||||||
|
|
||||||
|
updateAddresses(
|
||||||
|
data: UpdateAddressDTO[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<CartAddressDTO[]>
|
||||||
|
updateAddresses(
|
||||||
|
data: UpdateAddressDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<CartAddressDTO>
|
||||||
|
|
||||||
|
deleteAddresses(ids: string[], sharedContext?: Context): Promise<void>
|
||||||
|
deleteAddresses(ids: string, sharedContext?: Context): Promise<void>
|
||||||
|
|
||||||
// addLineItems(data: AddLineItemsDTO, sharedContext?: Context): Promise<CartDTO>
|
// addLineItems(data: AddLineItemsDTO, sharedContext?: Context): Promise<CartDTO>
|
||||||
// addLineItems(
|
// addLineItems(
|
||||||
// data: AddLineItemsDTO[],
|
// data: AddLineItemsDTO[],
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ import { PromotionDTO } from "./promotion"
|
|||||||
import { CreatePromotionRuleDTO, PromotionRuleDTO } from "./promotion-rule"
|
import { CreatePromotionRuleDTO, PromotionRuleDTO } from "./promotion-rule"
|
||||||
|
|
||||||
export type ApplicationMethodTypeValues = "fixed" | "percentage"
|
export type ApplicationMethodTypeValues = "fixed" | "percentage"
|
||||||
export type ApplicationMethodTargetTypeValues = "order" | "shipping" | "item"
|
export type ApplicationMethodTargetTypeValues =
|
||||||
|
| "order"
|
||||||
|
| "shipping_methods"
|
||||||
|
| "items"
|
||||||
export type ApplicationMethodAllocationValues = "each" | "across"
|
export type ApplicationMethodAllocationValues = "each" | "across"
|
||||||
|
|
||||||
export interface ApplicationMethodDTO {
|
export interface ApplicationMethodDTO {
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { BaseFilterable } from "../../dal"
|
||||||
|
|
||||||
|
export type CampaignBudgetTypeValues = "spend" | "usage"
|
||||||
|
|
||||||
|
export interface CampaignBudgetDTO {
|
||||||
|
id: string
|
||||||
|
type?: CampaignBudgetTypeValues
|
||||||
|
limit?: string | null
|
||||||
|
used?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FilterableCampaignBudgetProps
|
||||||
|
extends BaseFilterable<FilterableCampaignBudgetProps> {
|
||||||
|
id?: string[]
|
||||||
|
type?: string[]
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { BaseFilterable } from "../../dal"
|
||||||
|
import { CampaignBudgetDTO } from "./campaign-budget"
|
||||||
|
|
||||||
|
export interface CampaignDTO {
|
||||||
|
id: string
|
||||||
|
name?: string
|
||||||
|
description?: string
|
||||||
|
currency?: string
|
||||||
|
campaign_identifier?: string
|
||||||
|
starts_at?: Date
|
||||||
|
ends_at?: Date
|
||||||
|
budget?: CampaignBudgetDTO
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FilterableCampaignProps
|
||||||
|
extends BaseFilterable<FilterableCampaignProps> {
|
||||||
|
id?: string[]
|
||||||
|
campaign_identifier?: string[]
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
export type ComputeActions =
|
||||||
|
| AddItemAdjustmentAction
|
||||||
|
| RemoveItemAdjustmentAction
|
||||||
|
| AddShippingMethodAdjustment
|
||||||
|
| RemoveShippingMethodAdjustment
|
||||||
|
|
||||||
|
export interface AddItemAdjustmentAction {
|
||||||
|
action: "addItemAdjustment"
|
||||||
|
item_id: string
|
||||||
|
amount: number
|
||||||
|
code: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RemoveItemAdjustmentAction {
|
||||||
|
action: "removeItemAdjustment"
|
||||||
|
adjustment_id: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddShippingMethodAdjustment {
|
||||||
|
action: "addShippingMethodAdjustment"
|
||||||
|
shipping_method_id: string
|
||||||
|
amount: number
|
||||||
|
code: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RemoveShippingMethodAdjustment {
|
||||||
|
action: "removeShippingMethodAdjustment"
|
||||||
|
adjustment_id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComputeActionAdjustmentLine {
|
||||||
|
id: string
|
||||||
|
code: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComputeActionItemLine {
|
||||||
|
id: string
|
||||||
|
quantity: number
|
||||||
|
unit_price: number
|
||||||
|
adjustments?: ComputeActionAdjustmentLine[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComputeActionShippingLine {
|
||||||
|
id: string
|
||||||
|
unit_price: number
|
||||||
|
adjustments?: ComputeActionAdjustmentLine[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComputeActionContext {
|
||||||
|
items?: ComputeActionItemLine[]
|
||||||
|
shipping_methods?: ComputeActionShippingLine[]
|
||||||
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
export * from "./application-method"
|
export * from "./application-method"
|
||||||
|
export * from "./campaign"
|
||||||
|
export * from "./campaign-budget"
|
||||||
|
export * from "./compute-actions"
|
||||||
export * from "./promotion"
|
export * from "./promotion"
|
||||||
export * from "./promotion-rule"
|
export * from "./promotion-rule"
|
||||||
export * from "./promotion-rule-value"
|
export * from "./promotion-rule-value"
|
||||||
@@ -3,10 +3,11 @@ import { PromotionRuleDTO } from "./promotion-rule"
|
|||||||
|
|
||||||
export interface PromotionRuleValueDTO {
|
export interface PromotionRuleValueDTO {
|
||||||
id: string
|
id: string
|
||||||
|
value?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreatePromotionRuleValueDTO {
|
export interface CreatePromotionRuleValueDTO {
|
||||||
value: any
|
value: string
|
||||||
promotion_rule: PromotionRuleDTO
|
promotion_rule: PromotionRuleDTO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { BaseFilterable } from "../../dal"
|
import { BaseFilterable } from "../../dal"
|
||||||
|
import { PromotionRuleValueDTO } from "./promotion-rule-value"
|
||||||
|
|
||||||
export type PromotionRuleOperatorValues =
|
export type PromotionRuleOperatorValues =
|
||||||
| "gt"
|
| "gt"
|
||||||
@@ -11,10 +12,14 @@ export type PromotionRuleOperatorValues =
|
|||||||
|
|
||||||
export interface PromotionRuleDTO {
|
export interface PromotionRuleDTO {
|
||||||
id: string
|
id: string
|
||||||
|
description?: string | null
|
||||||
|
attribute?: string
|
||||||
|
operator?: PromotionRuleOperatorValues
|
||||||
|
values: PromotionRuleValueDTO[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreatePromotionRuleDTO {
|
export interface CreatePromotionRuleDTO {
|
||||||
description?: string
|
description?: string | null
|
||||||
attribute: string
|
attribute: string
|
||||||
operator: PromotionRuleOperatorValues
|
operator: PromotionRuleOperatorValues
|
||||||
values: string[] | string
|
values: string[] | string
|
||||||
@@ -31,4 +36,5 @@ export interface RemovePromotionRuleDTO {
|
|||||||
export interface FilterablePromotionRuleProps
|
export interface FilterablePromotionRuleProps
|
||||||
extends BaseFilterable<FilterablePromotionRuleProps> {
|
extends BaseFilterable<FilterablePromotionRuleProps> {
|
||||||
id?: string[]
|
id?: string[]
|
||||||
|
code?: string[]
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
CreateApplicationMethodDTO,
|
CreateApplicationMethodDTO,
|
||||||
UpdateApplicationMethodDTO,
|
UpdateApplicationMethodDTO,
|
||||||
} from "./application-method"
|
} from "./application-method"
|
||||||
import { CreatePromotionRuleDTO } from "./promotion-rule"
|
import { CreatePromotionRuleDTO, PromotionRuleDTO } from "./promotion-rule"
|
||||||
|
|
||||||
export type PromotionType = "standard" | "buyget"
|
export type PromotionType = "standard" | "buyget"
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ export interface PromotionDTO {
|
|||||||
type?: PromotionType
|
type?: PromotionType
|
||||||
is_automatic?: boolean
|
is_automatic?: boolean
|
||||||
application_method?: ApplicationMethodDTO
|
application_method?: ApplicationMethodDTO
|
||||||
|
rules?: PromotionRuleDTO[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreatePromotionDTO {
|
export interface CreatePromotionDTO {
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from "./common"
|
export * from "./common"
|
||||||
|
export * from "./mutations"
|
||||||
export * from "./service"
|
export * from "./service"
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { CampaignBudgetTypeValues } from "./common"
|
||||||
|
|
||||||
|
export interface CreateCampaignBudgetDTO {
|
||||||
|
type: CampaignBudgetTypeValues
|
||||||
|
limit: number | null
|
||||||
|
used?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateCampaignBudgetDTO {
|
||||||
|
id: string
|
||||||
|
type?: CampaignBudgetTypeValues
|
||||||
|
limit?: number | null
|
||||||
|
used?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateCampaignDTO {
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
currency?: string
|
||||||
|
campaign_identifier: string
|
||||||
|
starts_at: Date
|
||||||
|
ends_at: Date
|
||||||
|
budget?: CreateCampaignBudgetDTO
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateCampaignDTO {
|
||||||
|
id: string
|
||||||
|
name?: string
|
||||||
|
description?: string
|
||||||
|
currency?: string
|
||||||
|
campaign_identifier?: string
|
||||||
|
starts_at?: Date
|
||||||
|
ends_at?: Date
|
||||||
|
budget?: Omit<UpdateCampaignBudgetDTO, "id">
|
||||||
|
}
|
||||||
@@ -2,25 +2,47 @@ import { FindConfig } from "../common"
|
|||||||
import { IModuleService } from "../modules-sdk"
|
import { IModuleService } from "../modules-sdk"
|
||||||
import { Context } from "../shared-context"
|
import { Context } from "../shared-context"
|
||||||
import {
|
import {
|
||||||
|
CampaignDTO,
|
||||||
|
ComputeActionContext,
|
||||||
|
ComputeActions,
|
||||||
CreatePromotionDTO,
|
CreatePromotionDTO,
|
||||||
CreatePromotionRuleDTO,
|
CreatePromotionRuleDTO,
|
||||||
|
FilterableCampaignProps,
|
||||||
FilterablePromotionProps,
|
FilterablePromotionProps,
|
||||||
PromotionDTO,
|
PromotionDTO,
|
||||||
RemovePromotionRuleDTO,
|
RemovePromotionRuleDTO,
|
||||||
UpdatePromotionDTO,
|
UpdatePromotionDTO,
|
||||||
} from "./common"
|
} from "./common"
|
||||||
|
|
||||||
|
import { CreateCampaignDTO, UpdateCampaignDTO } from "./mutations"
|
||||||
|
|
||||||
export interface IPromotionModuleService extends IModuleService {
|
export interface IPromotionModuleService extends IModuleService {
|
||||||
|
computeActions(
|
||||||
|
promotionCodesToApply: string[],
|
||||||
|
applicationContext: ComputeActionContext,
|
||||||
|
options?: Record<string, any>
|
||||||
|
): Promise<ComputeActions[]>
|
||||||
|
|
||||||
create(
|
create(
|
||||||
data: CreatePromotionDTO[],
|
data: CreatePromotionDTO[],
|
||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<PromotionDTO[]>
|
): Promise<PromotionDTO[]>
|
||||||
|
|
||||||
|
create(
|
||||||
|
data: CreatePromotionDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<PromotionDTO>
|
||||||
|
|
||||||
update(
|
update(
|
||||||
data: UpdatePromotionDTO[],
|
data: UpdatePromotionDTO[],
|
||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<PromotionDTO[]>
|
): Promise<PromotionDTO[]>
|
||||||
|
|
||||||
|
update(
|
||||||
|
data: UpdatePromotionDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<PromotionDTO>
|
||||||
|
|
||||||
list(
|
list(
|
||||||
filters?: FilterablePromotionProps,
|
filters?: FilterablePromotionProps,
|
||||||
config?: FindConfig<PromotionDTO>,
|
config?: FindConfig<PromotionDTO>,
|
||||||
@@ -34,6 +56,7 @@ export interface IPromotionModuleService extends IModuleService {
|
|||||||
): Promise<PromotionDTO>
|
): Promise<PromotionDTO>
|
||||||
|
|
||||||
delete(ids: string[], sharedContext?: Context): Promise<void>
|
delete(ids: string[], sharedContext?: Context): Promise<void>
|
||||||
|
delete(ids: string, sharedContext?: Context): Promise<void>
|
||||||
|
|
||||||
addPromotionRules(
|
addPromotionRules(
|
||||||
promotionId: string,
|
promotionId: string,
|
||||||
@@ -58,4 +81,39 @@ export interface IPromotionModuleService extends IModuleService {
|
|||||||
rulesData: RemovePromotionRuleDTO[],
|
rulesData: RemovePromotionRuleDTO[],
|
||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<PromotionDTO>
|
): Promise<PromotionDTO>
|
||||||
|
|
||||||
|
createCampaigns(
|
||||||
|
data: CreateCampaignDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<CampaignDTO>
|
||||||
|
|
||||||
|
createCampaigns(
|
||||||
|
data: CreateCampaignDTO[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<CampaignDTO[]>
|
||||||
|
|
||||||
|
updateCampaigns(
|
||||||
|
data: UpdateCampaignDTO[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<CampaignDTO[]>
|
||||||
|
|
||||||
|
updateCampaigns(
|
||||||
|
data: UpdateCampaignDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<CampaignDTO>
|
||||||
|
|
||||||
|
listCampaigns(
|
||||||
|
filters?: FilterableCampaignProps,
|
||||||
|
config?: FindConfig<CampaignDTO>,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<CampaignDTO[]>
|
||||||
|
|
||||||
|
retrieveCampaign(
|
||||||
|
id: string,
|
||||||
|
config?: FindConfig<CampaignDTO>,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<CampaignDTO>
|
||||||
|
|
||||||
|
deleteCampaigns(ids: string[], sharedContext?: Context): Promise<void>
|
||||||
|
deleteCampaigns(ids: string, sharedContext?: Context): Promise<void>
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import { pickValueFromObject } from "../pick-value-from-object"
|
||||||
|
|
||||||
|
describe("pickValueFromObject", function () {
|
||||||
|
it("should return true or false for different types of data", function () {
|
||||||
|
const expectations = [
|
||||||
|
{
|
||||||
|
input: {
|
||||||
|
1: "attribute.another_attribute",
|
||||||
|
2: {
|
||||||
|
attribute: {
|
||||||
|
another_attribute: "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output: "test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: {
|
||||||
|
1: "attribute.another_attribute.array_attribute",
|
||||||
|
2: {
|
||||||
|
attribute: {
|
||||||
|
another_attribute: [
|
||||||
|
{
|
||||||
|
array_attribute: "test 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
array_attribute: "test 2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output: ["test 1", "test 2"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: {
|
||||||
|
1: "attribute.another_attribute.array_attribute.deep_array_attribute",
|
||||||
|
2: {
|
||||||
|
attribute: {
|
||||||
|
another_attribute: [
|
||||||
|
{
|
||||||
|
array_attribute: [
|
||||||
|
{
|
||||||
|
deep_array_attribute: "test 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep_array_attribute: "test 2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
array_attribute: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output: ["test 1", "test 2"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: {
|
||||||
|
1: "attribute.another_attribute.array_attribute",
|
||||||
|
2: {
|
||||||
|
attribute: {
|
||||||
|
another_attribute: [
|
||||||
|
{
|
||||||
|
array_attribute: [
|
||||||
|
{
|
||||||
|
deep_array_attribute: "test 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep_array_attribute: "test 2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
array_attribute: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output: [
|
||||||
|
{
|
||||||
|
deep_array_attribute: "test 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep_array_attribute: "test 2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: {
|
||||||
|
1: "attribute.missing_attribute",
|
||||||
|
2: {
|
||||||
|
attribute: {
|
||||||
|
another_attribute: "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output: undefined,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
expectations.forEach((expectation) => {
|
||||||
|
expect(
|
||||||
|
pickValueFromObject(expectation.input["1"], expectation.input["2"])
|
||||||
|
).toEqual(expectation.output)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -23,6 +23,7 @@ export * from "./medusa-container"
|
|||||||
export * from "./object-from-string-path"
|
export * from "./object-from-string-path"
|
||||||
export * from "./object-to-string-path"
|
export * from "./object-to-string-path"
|
||||||
export * from "./optional-numeric-serializer"
|
export * from "./optional-numeric-serializer"
|
||||||
|
export * from "./pick-value-from-object"
|
||||||
export * from "./promise-all"
|
export * from "./promise-all"
|
||||||
export * from "./remote-query-object-from-string"
|
export * from "./remote-query-object-from-string"
|
||||||
export * from "./remote-query-object-to-string"
|
export * from "./remote-query-object-to-string"
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { isObject } from "./is-object"
|
||||||
|
|
||||||
|
export function pickValueFromObject(
|
||||||
|
path: string,
|
||||||
|
object: Record<any, any>
|
||||||
|
): any {
|
||||||
|
const segments = path.split(".")
|
||||||
|
let result: any = undefined
|
||||||
|
|
||||||
|
for (const segment of segments) {
|
||||||
|
const segmentsLeft = [...segments].splice(1, segments.length - 1)
|
||||||
|
const segmentOutput = object[segment]
|
||||||
|
|
||||||
|
if (segmentsLeft.length === 0) {
|
||||||
|
result = segmentOutput
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isObject(segmentOutput)) {
|
||||||
|
result = pickValueFromObject(segmentsLeft.join("."), segmentOutput)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(segmentOutput)) {
|
||||||
|
result = segmentOutput
|
||||||
|
.map((segmentOutput_) =>
|
||||||
|
pickValueFromObject(segmentsLeft.join("."), segmentOutput_)
|
||||||
|
)
|
||||||
|
.flat()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
result = segmentOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
EntityManager,
|
EntityManager,
|
||||||
EntitySchema,
|
EntitySchema,
|
||||||
FilterQuery,
|
|
||||||
LoadStrategy,
|
LoadStrategy,
|
||||||
RequiredEntityData,
|
RequiredEntityData,
|
||||||
} from "@mikro-orm/core"
|
} from "@mikro-orm/core"
|
||||||
@@ -17,9 +16,9 @@ import {
|
|||||||
EntityName,
|
EntityName,
|
||||||
FilterQuery as MikroFilterQuery,
|
FilterQuery as MikroFilterQuery,
|
||||||
} from "@mikro-orm/core/typings"
|
} from "@mikro-orm/core/typings"
|
||||||
import { MedusaError, arrayDifference, isString } from "../../common"
|
import { isString, MedusaError } from "../../common"
|
||||||
import { MedusaContext } from "../../decorators"
|
import { MedusaContext } from "../../decorators"
|
||||||
import { InjectTransactionManager, buildQuery } from "../../modules-sdk"
|
import { buildQuery, InjectTransactionManager } from "../../modules-sdk"
|
||||||
import {
|
import {
|
||||||
getSoftDeletedCascadedEntitiesIdsMappedBy,
|
getSoftDeletedCascadedEntitiesIdsMappedBy,
|
||||||
transactionWrapper,
|
transactionWrapper,
|
||||||
@@ -77,10 +76,11 @@ export class MikroOrmBase<T = any> {
|
|||||||
export class MikroOrmBaseRepository<
|
export class MikroOrmBaseRepository<
|
||||||
T extends object = object
|
T extends object = object
|
||||||
> extends MikroOrmBase<T> {
|
> extends MikroOrmBase<T> {
|
||||||
constructor() {
|
constructor(...args: any[]) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
super(...arguments)
|
super(...arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
create(data: unknown[], context?: Context): Promise<T[]> {
|
create(data: unknown[], context?: Context): Promise<T[]> {
|
||||||
throw new Error("Method not implemented.")
|
throw new Error("Method not implemented.")
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ export class MikroOrmBaseRepository<
|
|||||||
throw new Error("Method not implemented.")
|
throw new Error("Method not implemented.")
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(ids: string[], context?: Context): Promise<void> {
|
delete(ids: string[] | object[], context?: Context): Promise<void> {
|
||||||
throw new Error("Method not implemented.")
|
throw new Error("Method not implemented.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,11 +227,25 @@ export function mikroOrmBaseRepositoryFactory<
|
|||||||
TDTos extends { [K in DtoBasedMutationMethods]?: any } = {
|
TDTos extends { [K in DtoBasedMutationMethods]?: any } = {
|
||||||
[K in DtoBasedMutationMethods]?: any
|
[K in DtoBasedMutationMethods]?: any
|
||||||
}
|
}
|
||||||
>(
|
>(entity: EntityClass<T> | EntitySchema<T>) {
|
||||||
entity: EntityClass<T> | EntitySchema<T> | string,
|
|
||||||
primaryKey: string = "id"
|
|
||||||
) {
|
|
||||||
class MikroOrmAbstractBaseRepository_ extends MikroOrmBaseRepository<T> {
|
class MikroOrmAbstractBaseRepository_ extends MikroOrmBaseRepository<T> {
|
||||||
|
// @ts-ignore
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
super(...arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
static buildUniqueCompositeKeyValue(keys: string[], data: object) {
|
||||||
|
return keys.map((k) => data[k]).join("_")
|
||||||
|
}
|
||||||
|
|
||||||
|
static retrievePrimaryKeys(entity: EntityClass<T> | EntitySchema<T>) {
|
||||||
|
return (
|
||||||
|
(entity as EntitySchema<T>).meta?.primaryKeys ??
|
||||||
|
(entity as EntityClass<T>).prototype.__meta.primaryKeys
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
async create(data: TDTos["create"][], context?: Context): Promise<T[]> {
|
async create(data: TDTos["create"][], context?: Context): Promise<T[]> {
|
||||||
const manager = this.getActiveManager<EntityManager>(context)
|
const manager = this.getActiveManager<EntityManager>(context)
|
||||||
|
|
||||||
@@ -250,42 +264,71 @@ export function mikroOrmBaseRepositoryFactory<
|
|||||||
async update(data: TDTos["update"][], context?: Context): Promise<T[]> {
|
async update(data: TDTos["update"][], context?: Context): Promise<T[]> {
|
||||||
const manager = this.getActiveManager<EntityManager>(context)
|
const manager = this.getActiveManager<EntityManager>(context)
|
||||||
|
|
||||||
const primaryKeyValues: string[] = data.map((data_) => data_[primaryKey])
|
const primaryKeys =
|
||||||
const existingEntities = await this.find(
|
MikroOrmAbstractBaseRepository_.retrievePrimaryKeys(entity)
|
||||||
{
|
|
||||||
where: {
|
let primaryKeysCriteria: { [key: string]: any }[] = []
|
||||||
[primaryKey]: {
|
if (primaryKeys.length === 1) {
|
||||||
$in: primaryKeyValues,
|
primaryKeysCriteria.push({
|
||||||
},
|
[primaryKeys[0]]: data.map((d) => d[primaryKeys[0]]),
|
||||||
},
|
})
|
||||||
} as DAL.FindOptions<T>,
|
} else {
|
||||||
context
|
primaryKeysCriteria = data.map((d) => ({
|
||||||
|
$and: primaryKeys.map((key) => ({ [key]: d[key] })),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const allEntities = await Promise.all(
|
||||||
|
primaryKeysCriteria.map(
|
||||||
|
async (criteria) =>
|
||||||
|
await this.find({ where: criteria } as DAL.FindOptions<T>, context)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const missingEntities = arrayDifference(
|
const existingEntities = allEntities.flat()
|
||||||
data.map((d) => d[primaryKey]),
|
|
||||||
existingEntities.map((d: any) => d[primaryKey])
|
const existingEntitiesMap = new Map<string, T>()
|
||||||
|
existingEntities.forEach((entity) => {
|
||||||
|
if (entity) {
|
||||||
|
const key =
|
||||||
|
MikroOrmAbstractBaseRepository_.buildUniqueCompositeKeyValue(
|
||||||
|
primaryKeys,
|
||||||
|
entity
|
||||||
)
|
)
|
||||||
|
existingEntitiesMap.set(key, entity)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const missingEntities = data.filter((data_) => {
|
||||||
|
const key =
|
||||||
|
MikroOrmAbstractBaseRepository_.buildUniqueCompositeKeyValue(
|
||||||
|
primaryKeys,
|
||||||
|
data_
|
||||||
|
)
|
||||||
|
return !existingEntitiesMap.has(key)
|
||||||
|
})
|
||||||
|
|
||||||
if (missingEntities.length) {
|
if (missingEntities.length) {
|
||||||
const entityName = (entity as EntityClass<T>).name ?? entity
|
const entityName = (entity as EntityClass<T>).name ?? entity
|
||||||
|
const missingEntitiesKeys = data.map((data_) =>
|
||||||
|
primaryKeys.map((key) => data_[key]).join(", ")
|
||||||
|
)
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.NOT_FOUND,
|
MedusaError.Types.NOT_FOUND,
|
||||||
`${entityName} with ${[primaryKey]} "${missingEntities.join(
|
`${entityName} with ${primaryKeys.join(
|
||||||
", "
|
", "
|
||||||
)}" not found`
|
)} "${missingEntitiesKeys.join(", ")}" not found`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingEntitiesMap = new Map(
|
|
||||||
existingEntities.map<[string, T]>((entity_: any) => [
|
|
||||||
entity_[primaryKey],
|
|
||||||
entity_,
|
|
||||||
])
|
|
||||||
)
|
|
||||||
|
|
||||||
const entities = data.map((data_) => {
|
const entities = data.map((data_) => {
|
||||||
const existingEntity = existingEntitiesMap.get(data_[primaryKey])!
|
const key =
|
||||||
|
MikroOrmAbstractBaseRepository_.buildUniqueCompositeKeyValue(
|
||||||
|
primaryKeys,
|
||||||
|
data_
|
||||||
|
)
|
||||||
|
const existingEntity = existingEntitiesMap.get(key)!
|
||||||
|
|
||||||
return manager.assign(existingEntity, data_ as RequiredEntityData<T>)
|
return manager.assign(existingEntity, data_ as RequiredEntityData<T>)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -294,15 +337,43 @@ export function mikroOrmBaseRepositoryFactory<
|
|||||||
return entities
|
return entities
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(primaryKeyValues: string[], context?: Context): Promise<void> {
|
async delete(
|
||||||
|
primaryKeyValues: string[] | object[],
|
||||||
|
context?: Context
|
||||||
|
): Promise<void> {
|
||||||
const manager = this.getActiveManager<EntityManager>(context)
|
const manager = this.getActiveManager<EntityManager>(context)
|
||||||
|
|
||||||
await manager.nativeDelete<T>(
|
const primaryKeys =
|
||||||
entity as EntityName<T>,
|
MikroOrmAbstractBaseRepository_.retrievePrimaryKeys(entity)
|
||||||
{ [primaryKey]: { $in: primaryKeyValues } } as unknown as FilterQuery<T>
|
|
||||||
|
let deletionCriteria
|
||||||
|
if (primaryKeys.length > 1) {
|
||||||
|
deletionCriteria = {
|
||||||
|
$or: primaryKeyValues.map((compositeKeyValue) => {
|
||||||
|
const keys = Object.keys(compositeKeyValue)
|
||||||
|
if (!primaryKeys.every((k) => keys.includes(k))) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Composite key must contain all primary key fields: ${primaryKeys.join(
|
||||||
|
", "
|
||||||
|
)}. Found: ${keys}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const criteria: { [key: string]: any } = {}
|
||||||
|
for (const key of primaryKeys) {
|
||||||
|
criteria[key] = compositeKeyValue[key]
|
||||||
|
}
|
||||||
|
return criteria
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deletionCriteria = { [primaryKeys[0]]: { $in: primaryKeyValues } }
|
||||||
|
}
|
||||||
|
|
||||||
|
await manager.nativeDelete<T>(entity as EntityName<T>, deletionCriteria)
|
||||||
|
}
|
||||||
|
|
||||||
async find(options?: DAL.FindOptions<T>, context?: Context): Promise<T[]> {
|
async find(options?: DAL.FindOptions<T>, context?: Context): Promise<T[]> {
|
||||||
const manager = this.getActiveManager<EntityManager>(context)
|
const manager = this.getActiveManager<EntityManager>(context)
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ export enum ApplicationMethodType {
|
|||||||
|
|
||||||
export enum ApplicationMethodTargetType {
|
export enum ApplicationMethodTargetType {
|
||||||
ORDER = "order",
|
ORDER = "order",
|
||||||
SHIPPING = "shipping",
|
SHIPPING_METHODS = "shipping_methods",
|
||||||
ITEM = "item",
|
ITEMS = "items",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ApplicationMethodAllocation {
|
export enum ApplicationMethodAllocation {
|
||||||
@@ -28,3 +28,8 @@ export enum PromotionRuleOperator {
|
|||||||
NE = "ne",
|
NE = "ne",
|
||||||
IN = "in",
|
IN = "in",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum CampaignBudgetType {
|
||||||
|
SPEND = "spend",
|
||||||
|
USAGE = "usage",
|
||||||
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
{"id":"https://github.com/medusajs/medusa/releases/tag/v1.20.0","content":"v1.20.0 is out","isCloseable":true}
|
{}
|
||||||
@@ -1240,7 +1240,7 @@ Notice that as there are two types of flows, you'll be creating the components f
|
|||||||
const api_key = useMemo(() => keys?.[0]?.id || "", [keys])
|
const api_key = useMemo(() => keys?.[0]?.id || "", [keys])
|
||||||
const backendUrl = process.env.MEDUSA_BACKEND_URL === "/" || process.env.MEDUSA_ADMIN_BACKEND_URL === "/" ?
|
const backendUrl = process.env.MEDUSA_BACKEND_URL === "/" || process.env.MEDUSA_ADMIN_BACKEND_URL === "/" ?
|
||||||
location.origin :
|
location.origin :
|
||||||
process.env.MEDUSA_BACKEND_URL || process.env.MEDUSA_ADMIN_BACKEND_URL || "http://location:9000"
|
process.env.MEDUSA_BACKEND_URL || process.env.MEDUSA_ADMIN_BACKEND_URL || "http://localhost:9000"
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLoading && !keys?.length) {
|
if (!isLoading && !keys?.length) {
|
||||||
|
|||||||
+1
-1
@@ -196,7 +196,7 @@ class MyFulfillmentService extends AbstractFulfillmentService {
|
|||||||
// ...
|
// ...
|
||||||
async createFulfillment(
|
async createFulfillment(
|
||||||
data: Record<string, unknown>,
|
data: Record<string, unknown>,
|
||||||
items: LineItem,
|
items: LineItem[],
|
||||||
order: Order,
|
order: Order,
|
||||||
fulfillment: Fulfillment
|
fulfillment: Fulfillment
|
||||||
) {
|
) {
|
||||||
|
|||||||
+1
-1
@@ -194,7 +194,7 @@ class MyFulfillmentService extends AbstractFulfillmentService {
|
|||||||
// ...
|
// ...
|
||||||
async createFulfillment(
|
async createFulfillment(
|
||||||
data: Record<string, unknown>,
|
data: Record<string, unknown>,
|
||||||
items: LineItem,
|
items: LineItem[],
|
||||||
order: Order,
|
order: Order,
|
||||||
fulfillment: Fulfillment
|
fulfillment: Fulfillment
|
||||||
) {
|
) {
|
||||||
|
|||||||
Loaded 100 of 101 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user