chore(): Reorganize modules (#7210)
**What** Move all modules to the modules directory
This commit is contained in:
committed by
GitHub
parent
7a351eef09
commit
4eae25e1ef
@@ -0,0 +1,10 @@
|
||||
import {
|
||||
moduleDefinition,
|
||||
revertMigration,
|
||||
runMigrations,
|
||||
} from "./module-definition"
|
||||
|
||||
export default moduleDefinition
|
||||
export { revertMigration, runMigrations }
|
||||
|
||||
export * from "./initialize"
|
||||
@@ -0,0 +1,31 @@
|
||||
import {
|
||||
ExternalModuleDeclaration,
|
||||
InternalModuleDeclaration,
|
||||
MODULE_PACKAGE_NAMES,
|
||||
MedusaModule,
|
||||
Modules,
|
||||
} from "@medusajs/modules-sdk"
|
||||
import { IPromotionModuleService, ModulesSdkTypes } from "@medusajs/types"
|
||||
import { moduleDefinition } from "../module-definition"
|
||||
import { InitializeModuleInjectableDependencies } from "../types"
|
||||
|
||||
export const initialize = async (
|
||||
options?:
|
||||
| ModulesSdkTypes.ModuleServiceInitializeOptions
|
||||
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
|
||||
| ExternalModuleDeclaration
|
||||
| InternalModuleDeclaration,
|
||||
injectedDependencies?: InitializeModuleInjectableDependencies
|
||||
): Promise<IPromotionModuleService> => {
|
||||
const loaded = await MedusaModule.bootstrap<IPromotionModuleService>({
|
||||
moduleKey: Modules.PROMOTION,
|
||||
defaultPath: MODULE_PACKAGE_NAMES[Modules.PROMOTION],
|
||||
declaration: options as
|
||||
| InternalModuleDeclaration
|
||||
| ExternalModuleDeclaration,
|
||||
injectedDependencies,
|
||||
moduleExports: moduleDefinition,
|
||||
})
|
||||
|
||||
return loaded[Modules.PROMOTION]
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import { generateLinkableKeysMap } from "@medusajs/utils"
|
||||
import { Campaign, Promotion, PromotionRule } from "@models"
|
||||
|
||||
export const LinkableKeys = {
|
||||
promotion_id: Promotion.name,
|
||||
campaign_id: Campaign.name,
|
||||
promotion_rule_id: PromotionRule.name,
|
||||
}
|
||||
|
||||
export const entityNameToLinkableKeysMap = generateLinkableKeysMap(LinkableKeys)
|
||||
|
||||
export const joinerConfig: ModuleJoinerConfig = {
|
||||
serviceName: Modules.PROMOTION,
|
||||
primaryKeys: ["id"],
|
||||
linkableKeys: LinkableKeys,
|
||||
alias: [
|
||||
{
|
||||
name: ["promotion", "promotions"],
|
||||
args: {
|
||||
entity: Promotion.name,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["campaign", "campaigns"],
|
||||
args: {
|
||||
entity: Campaign.name,
|
||||
methodSuffix: "Campaigns",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["promotion_rule", "promotion_rules"],
|
||||
args: {
|
||||
entity: PromotionRule.name,
|
||||
methodSuffix: "PromotionRules",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
InternalModuleDeclaration,
|
||||
LoaderOptions,
|
||||
Modules,
|
||||
} from "@medusajs/modules-sdk"
|
||||
import { ModulesSdkTypes } from "@medusajs/types"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import { EntitySchema } from "@mikro-orm/core"
|
||||
import * as PromotionModels from "../models"
|
||||
|
||||
export default async (
|
||||
{
|
||||
options,
|
||||
container,
|
||||
logger,
|
||||
}: LoaderOptions<
|
||||
| ModulesSdkTypes.ModuleServiceInitializeOptions
|
||||
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
|
||||
>,
|
||||
moduleDeclaration?: InternalModuleDeclaration
|
||||
): Promise<void> => {
|
||||
const entities = Object.values(PromotionModels) as unknown as EntitySchema[]
|
||||
const pathToMigrations = __dirname + "/../migrations"
|
||||
|
||||
await ModulesSdkUtils.mikroOrmConnectionLoader({
|
||||
moduleName: Modules.PROMOTION,
|
||||
entities,
|
||||
container,
|
||||
options,
|
||||
moduleDeclaration,
|
||||
logger,
|
||||
pathToMigrations,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import * as ModuleModels from "@models"
|
||||
import * as ModuleRepositories from "@repositories"
|
||||
import * as ModuleServices from "@services"
|
||||
|
||||
export default ModulesSdkUtils.moduleContainerLoaderFactory({
|
||||
moduleModels: ModuleModels,
|
||||
moduleRepositories: ModuleRepositories,
|
||||
moduleServices: ModuleServices,
|
||||
})
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./connection"
|
||||
export * from "./container"
|
||||
@@ -0,0 +1,924 @@
|
||||
{
|
||||
"namespaces": ["public"],
|
||||
"name": "public",
|
||||
"tables": [
|
||||
{
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"currency": {
|
||||
"name": "currency",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"campaign_identifier": {
|
||||
"name": "campaign_identifier",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"starts_at": {
|
||||
"name": "starts_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"ends_at": {
|
||||
"name": "ends_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
}
|
||||
},
|
||||
"name": "promotion_campaign",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "IDX_campaign_identifier_unique",
|
||||
"columnNames": ["campaign_identifier"],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"keyName": "promotion_campaign_pkey",
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"enumItems": ["spend", "usage"],
|
||||
"mappedType": "enum"
|
||||
},
|
||||
"campaign_id": {
|
||||
"name": "campaign_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"limit": {
|
||||
"name": "limit",
|
||||
"type": "numeric",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "decimal"
|
||||
},
|
||||
"raw_limit": {
|
||||
"name": "raw_limit",
|
||||
"type": "jsonb",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "json"
|
||||
},
|
||||
"used": {
|
||||
"name": "used",
|
||||
"type": "numeric",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "decimal"
|
||||
},
|
||||
"raw_used": {
|
||||
"name": "raw_used",
|
||||
"type": "jsonb",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "json"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
}
|
||||
},
|
||||
"name": "promotion_campaign_budget",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": ["type"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_campaign_budget_type",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": ["campaign_id"],
|
||||
"composite": false,
|
||||
"keyName": "promotion_campaign_budget_campaign_id_unique",
|
||||
"primary": false,
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"keyName": "promotion_campaign_budget_pkey",
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {
|
||||
"promotion_campaign_budget_campaign_id_foreign": {
|
||||
"constraintName": "promotion_campaign_budget_campaign_id_foreign",
|
||||
"columnNames": ["campaign_id"],
|
||||
"localTableName": "public.promotion_campaign_budget",
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.promotion_campaign",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"code": {
|
||||
"name": "code",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"campaign_id": {
|
||||
"name": "campaign_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"is_automatic": {
|
||||
"name": "is_automatic",
|
||||
"type": "boolean",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"default": "false",
|
||||
"mappedType": "boolean"
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"enumItems": ["standard", "buyget"],
|
||||
"mappedType": "enum"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
}
|
||||
},
|
||||
"name": "promotion",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": ["code"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_promotion_code",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": ["type"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_promotion_type",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_promotion_code_unique",
|
||||
"columnNames": ["code"],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"keyName": "promotion_pkey",
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {
|
||||
"promotion_campaign_id_foreign": {
|
||||
"constraintName": "promotion_campaign_id_foreign",
|
||||
"columnNames": ["campaign_id"],
|
||||
"localTableName": "public.promotion",
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.promotion_campaign",
|
||||
"deleteRule": "set null"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "numeric",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "decimal"
|
||||
},
|
||||
"raw_value": {
|
||||
"name": "raw_value",
|
||||
"type": "jsonb",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "json"
|
||||
},
|
||||
"max_quantity": {
|
||||
"name": "max_quantity",
|
||||
"type": "numeric",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "decimal"
|
||||
},
|
||||
"apply_to_quantity": {
|
||||
"name": "apply_to_quantity",
|
||||
"type": "numeric",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "decimal"
|
||||
},
|
||||
"buy_rules_min_quantity": {
|
||||
"name": "buy_rules_min_quantity",
|
||||
"type": "numeric",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "decimal"
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"enumItems": ["fixed", "percentage"],
|
||||
"mappedType": "enum"
|
||||
},
|
||||
"target_type": {
|
||||
"name": "target_type",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"enumItems": ["order", "shipping_methods", "items"],
|
||||
"mappedType": "enum"
|
||||
},
|
||||
"allocation": {
|
||||
"name": "allocation",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"enumItems": ["each", "across"],
|
||||
"mappedType": "enum"
|
||||
},
|
||||
"promotion_id": {
|
||||
"name": "promotion_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
}
|
||||
},
|
||||
"name": "promotion_application_method",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": ["type"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_application_method_type",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": ["target_type"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_application_method_target_type",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": ["allocation"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_application_method_allocation",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": ["promotion_id"],
|
||||
"composite": false,
|
||||
"keyName": "promotion_application_method_promotion_id_unique",
|
||||
"primary": false,
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"keyName": "promotion_application_method_pkey",
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {
|
||||
"promotion_application_method_promotion_id_foreign": {
|
||||
"constraintName": "promotion_application_method_promotion_id_foreign",
|
||||
"columnNames": ["promotion_id"],
|
||||
"localTableName": "public.promotion_application_method",
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.promotion",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"attribute": {
|
||||
"name": "attribute",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"operator": {
|
||||
"name": "operator",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"enumItems": ["gte", "lte", "gt", "lt", "eq", "ne", "in"],
|
||||
"mappedType": "enum"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
}
|
||||
},
|
||||
"name": "promotion_rule",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": ["attribute"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_promotion_rule_attribute",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"columnNames": ["operator"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_promotion_rule_operator",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"keyName": "promotion_rule_pkey",
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
"promotion_id": {
|
||||
"name": "promotion_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"promotion_rule_id": {
|
||||
"name": "promotion_rule_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
}
|
||||
},
|
||||
"name": "promotion_promotion_rule",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "promotion_promotion_rule_pkey",
|
||||
"columnNames": ["promotion_id", "promotion_rule_id"],
|
||||
"composite": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {
|
||||
"promotion_promotion_rule_promotion_id_foreign": {
|
||||
"constraintName": "promotion_promotion_rule_promotion_id_foreign",
|
||||
"columnNames": ["promotion_id"],
|
||||
"localTableName": "public.promotion_promotion_rule",
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.promotion",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"promotion_promotion_rule_promotion_rule_id_foreign": {
|
||||
"constraintName": "promotion_promotion_rule_promotion_rule_id_foreign",
|
||||
"columnNames": ["promotion_rule_id"],
|
||||
"localTableName": "public.promotion_promotion_rule",
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.promotion_rule",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
"application_method_id": {
|
||||
"name": "application_method_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"promotion_rule_id": {
|
||||
"name": "promotion_rule_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
}
|
||||
},
|
||||
"name": "application_method_target_rules",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "application_method_target_rules_pkey",
|
||||
"columnNames": ["application_method_id", "promotion_rule_id"],
|
||||
"composite": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {
|
||||
"application_method_target_rules_application_method_id_foreign": {
|
||||
"constraintName": "application_method_target_rules_application_method_id_foreign",
|
||||
"columnNames": ["application_method_id"],
|
||||
"localTableName": "public.application_method_target_rules",
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.promotion_application_method",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"application_method_target_rules_promotion_rule_id_foreign": {
|
||||
"constraintName": "application_method_target_rules_promotion_rule_id_foreign",
|
||||
"columnNames": ["promotion_rule_id"],
|
||||
"localTableName": "public.application_method_target_rules",
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.promotion_rule",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
"application_method_id": {
|
||||
"name": "application_method_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"promotion_rule_id": {
|
||||
"name": "promotion_rule_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
}
|
||||
},
|
||||
"name": "application_method_buy_rules",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "application_method_buy_rules_pkey",
|
||||
"columnNames": ["application_method_id", "promotion_rule_id"],
|
||||
"composite": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {
|
||||
"application_method_buy_rules_application_method_id_foreign": {
|
||||
"constraintName": "application_method_buy_rules_application_method_id_foreign",
|
||||
"columnNames": ["application_method_id"],
|
||||
"localTableName": "public.application_method_buy_rules",
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.promotion_application_method",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"application_method_buy_rules_promotion_rule_id_foreign": {
|
||||
"constraintName": "application_method_buy_rules_promotion_rule_id_foreign",
|
||||
"columnNames": ["promotion_rule_id"],
|
||||
"localTableName": "public.application_method_buy_rules",
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.promotion_rule",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"promotion_rule_id": {
|
||||
"name": "promotion_rule_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
}
|
||||
},
|
||||
"name": "promotion_rule_value",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": ["promotion_rule_id"],
|
||||
"composite": false,
|
||||
"keyName": "IDX_promotion_rule_promotion_rule_value_id",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"keyName": "promotion_rule_value_pkey",
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {
|
||||
"promotion_rule_value_promotion_rule_id_foreign": {
|
||||
"constraintName": "promotion_rule_value_promotion_rule_id_foreign",
|
||||
"columnNames": ["promotion_rule_id"],
|
||||
"localTableName": "public.promotion_rule_value",
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.promotion_rule",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import { Migration } from "@mikro-orm/migrations"
|
||||
|
||||
export class Migration20240227120221 extends Migration {
|
||||
async up(): Promise<void> {
|
||||
this.addSql(
|
||||
'create table if not exists "promotion_campaign" ("id" text not null, "name" text not null, "description" text null, "currency" text null, "campaign_identifier" text not null, "starts_at" timestamptz null, "ends_at" timestamptz null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "promotion_campaign_pkey" primary key ("id"));'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table if exists "promotion_campaign" add constraint "IDX_campaign_identifier_unique" unique ("campaign_identifier");'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'create table if not exists "promotion_campaign_budget" ("id" text not null, "type" text check ("type" in (\'spend\', \'usage\')) not null, "campaign_id" text not null, "limit" numeric null, "raw_limit" jsonb null, "used" numeric null, "raw_used" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "promotion_campaign_budget_pkey" primary key ("id"));'
|
||||
)
|
||||
this.addSql(
|
||||
'create index if not exists "IDX_campaign_budget_type" on "promotion_campaign_budget" ("type");'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table if exists "promotion_campaign_budget" add constraint "promotion_campaign_budget_campaign_id_unique" unique ("campaign_id");'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'create table if not exists "promotion" ("id" text not null, "code" text not null, "campaign_id" text null, "is_automatic" boolean not null default false, "type" text check ("type" in (\'standard\', \'buyget\')) not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "promotion_pkey" primary key ("id"));'
|
||||
)
|
||||
this.addSql(
|
||||
'create index if not exists "IDX_promotion_code" on "promotion" ("code");'
|
||||
)
|
||||
this.addSql(
|
||||
'create index if not exists "IDX_promotion_type" on "promotion" ("type");'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table if exists "promotion" add constraint "IDX_promotion_code_unique" unique ("code");'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'create table if not exists "promotion_application_method" ("id" text not null, "value" numeric null, "raw_value" jsonb null, "max_quantity" numeric null, "apply_to_quantity" numeric null, "buy_rules_min_quantity" numeric null, "type" text check ("type" in (\'fixed\', \'percentage\')) not null, "target_type" text check ("target_type" in (\'order\', \'shipping_methods\', \'items\')) not null, "allocation" text check ("allocation" in (\'each\', \'across\')) null, "promotion_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "promotion_application_method_pkey" primary key ("id"));'
|
||||
)
|
||||
this.addSql(
|
||||
'create index if not exists "IDX_application_method_type" on "promotion_application_method" ("type");'
|
||||
)
|
||||
this.addSql(
|
||||
'create index if not exists "IDX_application_method_target_type" on "promotion_application_method" ("target_type");'
|
||||
)
|
||||
this.addSql(
|
||||
'create index if not exists "IDX_application_method_allocation" on "promotion_application_method" ("allocation");'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table if exists "promotion_application_method" add constraint "promotion_application_method_promotion_id_unique" unique ("promotion_id");'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'create table if not exists "promotion_rule" ("id" text not null, "description" text null, "attribute" text not null, "operator" text check ("operator" in (\'gte\', \'lte\', \'gt\', \'lt\', \'eq\', \'ne\', \'in\')) not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "promotion_rule_pkey" primary key ("id"));'
|
||||
)
|
||||
this.addSql(
|
||||
'create index if not exists "IDX_promotion_rule_attribute" on "promotion_rule" ("attribute");'
|
||||
)
|
||||
this.addSql(
|
||||
'create index if not exists "IDX_promotion_rule_operator" on "promotion_rule" ("operator");'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'create table if not exists "promotion_promotion_rule" ("promotion_id" text not null, "promotion_rule_id" text not null, constraint "promotion_promotion_rule_pkey" primary key ("promotion_id", "promotion_rule_id"));'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'create table if not exists "application_method_target_rules" ("application_method_id" text not null, "promotion_rule_id" text not null, constraint "application_method_target_rules_pkey" primary key ("application_method_id", "promotion_rule_id"));'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'create table if not exists "application_method_buy_rules" ("application_method_id" text not null, "promotion_rule_id" text not null, constraint "application_method_buy_rules_pkey" primary key ("application_method_id", "promotion_rule_id"));'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'create table if not exists "promotion_rule_value" ("id" text not null, "promotion_rule_id" text not null, "value" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "promotion_rule_value_pkey" primary key ("id"));'
|
||||
)
|
||||
this.addSql(
|
||||
'create index if not exists "IDX_promotion_rule_promotion_rule_value_id" on "promotion_rule_value" ("promotion_rule_id");'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'alter table if exists "promotion_campaign_budget" add constraint "promotion_campaign_budget_campaign_id_foreign" foreign key ("campaign_id") references "promotion_campaign" ("id") on update cascade;'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'alter table if exists "promotion" add constraint "promotion_campaign_id_foreign" foreign key ("campaign_id") references "promotion_campaign" ("id") on delete set null;'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'alter table if exists "promotion_application_method" add constraint "promotion_application_method_promotion_id_foreign" foreign key ("promotion_id") references "promotion" ("id") on update cascade on delete cascade;'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'alter table if exists "promotion_promotion_rule" add constraint "promotion_promotion_rule_promotion_id_foreign" foreign key ("promotion_id") references "promotion" ("id") on update cascade on delete cascade;'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table if exists "promotion_promotion_rule" add constraint "promotion_promotion_rule_promotion_rule_id_foreign" foreign key ("promotion_rule_id") references "promotion_rule" ("id") on update cascade on delete cascade;'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'alter table if exists "application_method_target_rules" add constraint "application_method_target_rules_application_method_id_foreign" foreign key ("application_method_id") references "promotion_application_method" ("id") on update cascade on delete cascade;'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table if exists "application_method_target_rules" add constraint "application_method_target_rules_promotion_rule_id_foreign" foreign key ("promotion_rule_id") references "promotion_rule" ("id") on update cascade on delete cascade;'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'alter table if exists "application_method_buy_rules" add constraint "application_method_buy_rules_application_method_id_foreign" foreign key ("application_method_id") references "promotion_application_method" ("id") on update cascade on delete cascade;'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table if exists "application_method_buy_rules" add constraint "application_method_buy_rules_promotion_rule_id_foreign" foreign key ("promotion_rule_id") references "promotion_rule" ("id") on update cascade on delete cascade;'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'alter table if exists "promotion_rule_value" add constraint "promotion_rule_value_promotion_rule_id_foreign" foreign key ("promotion_rule_id") references "promotion_rule" ("id") on update cascade on delete cascade;'
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import {
|
||||
ApplicationMethodAllocationValues,
|
||||
ApplicationMethodTargetTypeValues,
|
||||
ApplicationMethodTypeValues,
|
||||
BigNumberRawValue,
|
||||
DAL,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
BigNumber,
|
||||
DALUtils,
|
||||
MikroOrmBigNumberProperty,
|
||||
PromotionUtils,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Collection,
|
||||
Entity,
|
||||
Enum,
|
||||
Filter,
|
||||
Index,
|
||||
ManyToMany,
|
||||
OnInit,
|
||||
OneToOne,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import Promotion from "./promotion"
|
||||
import PromotionRule from "./promotion-rule"
|
||||
|
||||
type OptionalFields =
|
||||
| "value"
|
||||
| "max_quantity"
|
||||
| "apply_to_quantity"
|
||||
| "buy_rules_min_quantity"
|
||||
| "allocation"
|
||||
| DAL.SoftDeletableEntityDateColumns
|
||||
|
||||
@Entity({ tableName: "promotion_application_method" })
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class ApplicationMethod {
|
||||
[OptionalProps]?: OptionalFields
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id!: string
|
||||
|
||||
@MikroOrmBigNumberProperty({ nullable: true })
|
||||
value: BigNumber | number | null = null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
raw_value: BigNumberRawValue | null = null
|
||||
|
||||
@Property({ columnType: "numeric", nullable: true, serializer: Number })
|
||||
max_quantity?: number | null = null
|
||||
|
||||
@Property({ columnType: "numeric", nullable: true, serializer: Number })
|
||||
apply_to_quantity?: number | null = null
|
||||
|
||||
@Property({ columnType: "numeric", nullable: true, serializer: Number })
|
||||
buy_rules_min_quantity?: number | null = null
|
||||
|
||||
@Index({ name: "IDX_application_method_type" })
|
||||
@Enum(() => PromotionUtils.ApplicationMethodType)
|
||||
type: ApplicationMethodTypeValues
|
||||
|
||||
@Index({ name: "IDX_application_method_target_type" })
|
||||
@Enum(() => PromotionUtils.ApplicationMethodTargetType)
|
||||
target_type: ApplicationMethodTargetTypeValues
|
||||
|
||||
@Index({ name: "IDX_application_method_allocation" })
|
||||
@Enum({
|
||||
items: () => PromotionUtils.ApplicationMethodAllocation,
|
||||
nullable: true,
|
||||
})
|
||||
allocation?: ApplicationMethodAllocationValues
|
||||
|
||||
@OneToOne({
|
||||
entity: () => Promotion,
|
||||
onDelete: "cascade",
|
||||
})
|
||||
promotion: Promotion
|
||||
|
||||
@ManyToMany(() => PromotionRule, "method_target_rules", {
|
||||
owner: true,
|
||||
pivotTable: "application_method_target_rules",
|
||||
cascade: ["soft-remove"] as any,
|
||||
})
|
||||
target_rules = new Collection<PromotionRule>(this)
|
||||
|
||||
@ManyToMany(() => PromotionRule, "method_buy_rules", {
|
||||
owner: true,
|
||||
pivotTable: "application_method_buy_rules",
|
||||
cascade: ["soft-remove"] as any,
|
||||
})
|
||||
buy_rules = new Collection<PromotionRule>(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 = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "proappmet")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "proappmet")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
BigNumberRawValue,
|
||||
CampaignBudgetTypeValues,
|
||||
DAL,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
BigNumber,
|
||||
DALUtils,
|
||||
MikroOrmBigNumberProperty,
|
||||
PromotionUtils,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
Enum,
|
||||
Filter,
|
||||
Index,
|
||||
OnInit,
|
||||
OneToOne,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import Campaign from "./campaign"
|
||||
|
||||
type OptionalFields =
|
||||
| "description"
|
||||
| "limit"
|
||||
| "used"
|
||||
| DAL.SoftDeletableEntityDateColumns
|
||||
|
||||
@Entity({ tableName: "promotion_campaign_budget" })
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
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 | null = null
|
||||
|
||||
@MikroOrmBigNumberProperty({ nullable: true })
|
||||
limit: BigNumber | number | null = null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
raw_limit: BigNumberRawValue | null = null
|
||||
|
||||
@MikroOrmBigNumberProperty({ nullable: true })
|
||||
used: BigNumber | number | null = null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
raw_used: BigNumberRawValue | null = null
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "probudg")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "probudg")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import { DAL } from "@medusajs/types"
|
||||
import { DALUtils, Searchable, generateEntityId } from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Collection,
|
||||
Entity,
|
||||
Filter,
|
||||
OnInit,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
Unique,
|
||||
} from "@mikro-orm/core"
|
||||
import CampaignBudget from "./campaign-budget"
|
||||
import Promotion from "./promotion"
|
||||
|
||||
type OptionalRelations = "budget"
|
||||
type OptionalFields =
|
||||
| "description"
|
||||
| "currency"
|
||||
| "starts_at"
|
||||
| "ends_at"
|
||||
| DAL.SoftDeletableEntityDateColumns
|
||||
|
||||
@Entity({ tableName: "promotion_campaign" })
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class Campaign {
|
||||
[OptionalProps]?: OptionalFields | OptionalRelations
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id!: string
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text" })
|
||||
name: string
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
description: string | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
currency: string | null = 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 = null
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
})
|
||||
ends_at: Date | null = null
|
||||
|
||||
@OneToOne({
|
||||
entity: () => CampaignBudget,
|
||||
mappedBy: (cb) => cb.campaign,
|
||||
cascade: ["soft-remove"] as any,
|
||||
nullable: true,
|
||||
})
|
||||
budget: CampaignBudget | null = 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 = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "procamp")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "procamp")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
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 PromotionRule } from "./promotion-rule"
|
||||
export { default as PromotionRuleValue } from "./promotion-rule-value"
|
||||
@@ -0,0 +1,56 @@
|
||||
import { DALUtils, generateEntityId } from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
Filter,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import PromotionRule from "./promotion-rule"
|
||||
|
||||
@Entity({ tableName: "promotion_rule_value" })
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class PromotionRuleValue {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id!: string
|
||||
|
||||
@ManyToOne(() => PromotionRule, {
|
||||
onDelete: "cascade",
|
||||
fieldName: "promotion_rule_id",
|
||||
index: "IDX_promotion_rule_promotion_rule_value_id",
|
||||
})
|
||||
promotion_rule: PromotionRule
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
value: string
|
||||
|
||||
@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 = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "prorulval")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "prorulval")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { DAL, PromotionRuleOperatorValues } from "@medusajs/types"
|
||||
import { DALUtils, PromotionUtils, generateEntityId } from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Cascade,
|
||||
Collection,
|
||||
Entity,
|
||||
Enum,
|
||||
Filter,
|
||||
Index,
|
||||
ManyToMany,
|
||||
OnInit,
|
||||
OneToMany,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import ApplicationMethod from "./application-method"
|
||||
import Promotion from "./promotion"
|
||||
import PromotionRuleValue from "./promotion-rule-value"
|
||||
|
||||
type OptionalFields = "description" | DAL.SoftDeletableEntityDateColumns
|
||||
type OptionalRelations = "values" | "promotions"
|
||||
|
||||
@Entity({ tableName: "promotion_rule" })
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class PromotionRule {
|
||||
[OptionalProps]?: OptionalFields | OptionalRelations
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id!: string
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
description: string | null = null
|
||||
|
||||
@Index({ name: "IDX_promotion_rule_attribute" })
|
||||
@Property({ columnType: "text" })
|
||||
attribute: string
|
||||
|
||||
@Index({ name: "IDX_promotion_rule_operator" })
|
||||
@Enum(() => PromotionUtils.PromotionRuleOperator)
|
||||
operator: PromotionRuleOperatorValues
|
||||
|
||||
@OneToMany(() => PromotionRuleValue, (prv) => prv.promotion_rule, {
|
||||
cascade: [Cascade.REMOVE],
|
||||
})
|
||||
values = new Collection<PromotionRuleValue>(this)
|
||||
|
||||
@ManyToMany(() => Promotion, (promotion) => promotion.rules)
|
||||
promotions = new Collection<Promotion>(this)
|
||||
|
||||
@ManyToMany(
|
||||
() => ApplicationMethod,
|
||||
(applicationMethod) => applicationMethod.target_rules
|
||||
)
|
||||
method_target_rules = new Collection<ApplicationMethod>(this)
|
||||
|
||||
@ManyToMany(
|
||||
() => ApplicationMethod,
|
||||
(applicationMethod) => applicationMethod.buy_rules
|
||||
)
|
||||
method_buy_rules = new Collection<ApplicationMethod>(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 = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "prorul")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "prorul")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { DAL, PromotionTypeValues } from "@medusajs/types"
|
||||
import {
|
||||
DALUtils,
|
||||
PromotionUtils,
|
||||
Searchable,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Collection,
|
||||
Entity,
|
||||
Enum,
|
||||
Filter,
|
||||
Index,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
OneToOne,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
Unique,
|
||||
} from "@mikro-orm/core"
|
||||
import ApplicationMethod from "./application-method"
|
||||
import Campaign from "./campaign"
|
||||
import PromotionRule from "./promotion-rule"
|
||||
|
||||
type OptionalFields = "is_automatic" | DAL.SoftDeletableEntityDateColumns
|
||||
type OptionalRelations = "application_method" | "campaign"
|
||||
|
||||
@Entity({ tableName: "promotion" })
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class Promotion {
|
||||
[OptionalProps]?: OptionalFields | OptionalRelations
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id!: string
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text" })
|
||||
@Index({ name: "IDX_promotion_code" })
|
||||
@Unique({
|
||||
name: "IDX_promotion_code_unique",
|
||||
properties: ["code"],
|
||||
})
|
||||
code: string
|
||||
|
||||
@Searchable()
|
||||
@ManyToOne(() => Campaign, {
|
||||
fieldName: "campaign_id",
|
||||
nullable: true,
|
||||
cascade: ["soft-remove"] as any,
|
||||
})
|
||||
campaign: Campaign | null = null
|
||||
|
||||
@Property({ columnType: "boolean", default: false })
|
||||
is_automatic: boolean = false
|
||||
|
||||
@Index({ name: "IDX_promotion_type" })
|
||||
@Enum(() => PromotionUtils.PromotionType)
|
||||
type: PromotionTypeValues
|
||||
|
||||
@OneToOne({
|
||||
entity: () => ApplicationMethod,
|
||||
mappedBy: (am) => am.promotion,
|
||||
cascade: ["soft-remove"] as any,
|
||||
})
|
||||
application_method: ApplicationMethod
|
||||
|
||||
@ManyToMany(() => PromotionRule, "promotions", {
|
||||
owner: true,
|
||||
pivotTable: "promotion_promotion_rule",
|
||||
cascade: ["soft-remove"] as any,
|
||||
})
|
||||
rules = new Collection<PromotionRule>(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 = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "promo")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "promo")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ModuleExports } from "@medusajs/types"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import * as Models from "@models"
|
||||
import { PromotionModuleService } from "@services"
|
||||
import loadConnection from "./loaders/connection"
|
||||
import loadContainer from "./loaders/container"
|
||||
|
||||
const migrationScriptOptions = {
|
||||
moduleName: Modules.PROMOTION,
|
||||
models: Models,
|
||||
pathToMigrations: __dirname + "/migrations",
|
||||
}
|
||||
|
||||
export const runMigrations = ModulesSdkUtils.buildMigrationScript(
|
||||
migrationScriptOptions
|
||||
)
|
||||
export const revertMigration = ModulesSdkUtils.buildRevertMigrationScript(
|
||||
migrationScriptOptions
|
||||
)
|
||||
|
||||
const service = PromotionModuleService
|
||||
const loaders = [loadContainer, loadConnection] as any
|
||||
|
||||
export const moduleDefinition: ModuleExports = {
|
||||
service,
|
||||
loaders,
|
||||
runMigrations,
|
||||
revertMigration,
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import { Context } from "@medusajs/types"
|
||||
import { DALUtils } from "@medusajs/utils"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { Campaign, Promotion } from "@models"
|
||||
import { CreateCampaignDTO, UpdateCampaignDTO } from "@types"
|
||||
|
||||
export class CampaignRepository extends DALUtils.mikroOrmBaseRepositoryFactory<Campaign>(
|
||||
Campaign
|
||||
) {
|
||||
async create(
|
||||
data: CreateCampaignDTO[],
|
||||
context: Context = {}
|
||||
): Promise<Campaign[]> {
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
const promotionIdsToUpsert: string[] = []
|
||||
const campaignIdentifierPromotionsMap = new Map<string, string[]>()
|
||||
|
||||
data.forEach((campaignData) => {
|
||||
const campaignPromotionIds =
|
||||
campaignData.promotions?.map((p) => p.id) || []
|
||||
|
||||
promotionIdsToUpsert.push(...campaignPromotionIds)
|
||||
|
||||
campaignIdentifierPromotionsMap.set(
|
||||
campaignData.campaign_identifier,
|
||||
campaignPromotionIds
|
||||
)
|
||||
|
||||
delete campaignData.promotions
|
||||
})
|
||||
|
||||
const existingPromotions = await manager.find(Promotion, {
|
||||
id: promotionIdsToUpsert,
|
||||
})
|
||||
|
||||
const existingPromotionsMap = new Map<string, Promotion>(
|
||||
existingPromotions.map((promotion) => [promotion.id, promotion])
|
||||
)
|
||||
|
||||
const createdCampaigns = await super.create(data, context)
|
||||
|
||||
for (const createdCampaign of createdCampaigns) {
|
||||
const campaignPromotionIds =
|
||||
campaignIdentifierPromotionsMap.get(
|
||||
createdCampaign.campaign_identifier
|
||||
) || []
|
||||
|
||||
for (const campaignPromotionId of campaignPromotionIds) {
|
||||
const promotion = existingPromotionsMap.get(campaignPromotionId)
|
||||
|
||||
if (!promotion) {
|
||||
continue
|
||||
}
|
||||
|
||||
createdCampaign.promotions.add(promotion)
|
||||
}
|
||||
}
|
||||
|
||||
return createdCampaigns
|
||||
}
|
||||
|
||||
async update(
|
||||
data: { entity: Campaign; update: UpdateCampaignDTO }[],
|
||||
context: Context = {}
|
||||
): Promise<Campaign[]> {
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
const promotionIdsToUpsert: string[] = []
|
||||
const campaignIds: string[] = []
|
||||
const campaignPromotionIdsMap = new Map<string, string[]>()
|
||||
|
||||
data.forEach(({ update: campaignData }) => {
|
||||
const campaignPromotionIds = campaignData.promotions?.map((p) => p.id)
|
||||
|
||||
campaignIds.push(campaignData.id)
|
||||
|
||||
if (campaignPromotionIds) {
|
||||
promotionIdsToUpsert.push(...campaignPromotionIds)
|
||||
campaignPromotionIdsMap.set(campaignData.id, campaignPromotionIds)
|
||||
}
|
||||
|
||||
delete campaignData.promotions
|
||||
})
|
||||
|
||||
const existingCampaigns = await manager.find(
|
||||
Campaign,
|
||||
{ id: campaignIds },
|
||||
{ populate: ["promotions"] }
|
||||
)
|
||||
|
||||
const promotionIds = existingCampaigns
|
||||
.map((campaign) => campaign.promotions?.map((p) => p.id))
|
||||
.flat(1)
|
||||
.concat(promotionIdsToUpsert)
|
||||
|
||||
const existingPromotions = await manager.find(Promotion, {
|
||||
id: promotionIds,
|
||||
})
|
||||
|
||||
const existingCampaignsMap = new Map<string, Campaign>(
|
||||
existingCampaigns.map((campaign) => [campaign.id, campaign])
|
||||
)
|
||||
|
||||
const existingPromotionsMap = new Map<string, Promotion>(
|
||||
existingPromotions.map((promotion) => [promotion.id, promotion])
|
||||
)
|
||||
|
||||
const updatedCampaigns = await super.update(data, context)
|
||||
|
||||
for (const updatedCampaign of updatedCampaigns) {
|
||||
const upsertPromotionIds = campaignPromotionIdsMap.get(updatedCampaign.id)
|
||||
|
||||
if (!upsertPromotionIds) {
|
||||
continue
|
||||
}
|
||||
|
||||
const existingPromotionIds = (
|
||||
existingCampaignsMap.get(updatedCampaign.id)?.promotions || []
|
||||
).map((p) => p.id)
|
||||
|
||||
for (const existingPromotionId of existingPromotionIds) {
|
||||
const promotion = existingPromotionsMap.get(existingPromotionId)
|
||||
|
||||
if (!promotion) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!upsertPromotionIds.includes(existingPromotionId)) {
|
||||
updatedCampaign.promotions.remove(promotion)
|
||||
}
|
||||
}
|
||||
|
||||
for (const promotionIdToAdd of upsertPromotionIds) {
|
||||
const promotion = existingPromotionsMap.get(promotionIdToAdd)
|
||||
|
||||
if (!promotion) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (existingPromotionIds.includes(promotionIdToAdd)) {
|
||||
continue
|
||||
} else {
|
||||
updatedCampaign.promotions.add(promotion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updatedCampaigns
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
|
||||
export { CampaignRepository } from "./campaign"
|
||||
@@ -0,0 +1 @@
|
||||
export { default as PromotionModuleService } from "./promotion-module"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
import {
|
||||
ApplicationMethodAllocationValues,
|
||||
ApplicationMethodTargetTypeValues,
|
||||
ApplicationMethodTypeValues,
|
||||
PromotionDTO,
|
||||
} from "@medusajs/types"
|
||||
|
||||
import { Promotion } from "@models"
|
||||
|
||||
export interface CreateApplicationMethodDTO {
|
||||
type: ApplicationMethodTypeValues
|
||||
target_type: ApplicationMethodTargetTypeValues
|
||||
allocation?: ApplicationMethodAllocationValues
|
||||
value?: number
|
||||
promotion: Promotion | string | PromotionDTO
|
||||
max_quantity?: number | null
|
||||
buy_rules_min_quantity?: number | null
|
||||
apply_to_quantity?: number | null
|
||||
}
|
||||
|
||||
export interface UpdateApplicationMethodDTO {
|
||||
id: string
|
||||
type?: ApplicationMethodTypeValues
|
||||
target_type?: ApplicationMethodTargetTypeValues
|
||||
allocation?: ApplicationMethodAllocationValues
|
||||
value?: number
|
||||
promotion?: Promotion | string | PromotionDTO
|
||||
max_quantity?: number | null
|
||||
buy_rules_min_quantity?: number | null
|
||||
apply_to_quantity?: number | null
|
||||
}
|
||||
@@ -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,23 @@
|
||||
import { PromotionDTO } from "@medusajs/types"
|
||||
import { Promotion } from "@models"
|
||||
|
||||
export interface CreateCampaignDTO {
|
||||
name: string
|
||||
description?: string
|
||||
currency?: string
|
||||
campaign_identifier: string
|
||||
starts_at: Date
|
||||
ends_at: Date
|
||||
promotions?: (PromotionDTO | Promotion)[]
|
||||
}
|
||||
|
||||
export interface UpdateCampaignDTO {
|
||||
id: string
|
||||
name?: string
|
||||
description?: string
|
||||
currency?: string
|
||||
campaign_identifier?: string
|
||||
starts_at?: Date
|
||||
ends_at?: Date
|
||||
promotions?: (PromotionDTO | Promotion)[]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Logger } from "@medusajs/types"
|
||||
|
||||
export type InitializeModuleInjectableDependencies = {
|
||||
logger?: Logger
|
||||
}
|
||||
|
||||
export * from "./application-method"
|
||||
export * from "./campaign"
|
||||
export * from "./campaign-budget"
|
||||
export * from "./promotion"
|
||||
export * from "./promotion-rule"
|
||||
export * from "./promotion-rule-value"
|
||||
@@ -0,0 +1,13 @@
|
||||
import { PromotionRuleDTO } from "@medusajs/types"
|
||||
import { PromotionRule } from "@models"
|
||||
|
||||
export interface CreatePromotionRuleValueDTO {
|
||||
value: any
|
||||
promotion_rule: string | PromotionRuleDTO | PromotionRule
|
||||
}
|
||||
|
||||
export interface UpdatePromotionRuleValueDTO {
|
||||
id: string
|
||||
value: any
|
||||
promotion_rule: string | PromotionRuleDTO | PromotionRule
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { PromotionRuleOperatorValues } from "@medusajs/types"
|
||||
|
||||
export interface CreatePromotionRuleDTO {
|
||||
description?: string | null
|
||||
attribute: string
|
||||
operator: PromotionRuleOperatorValues
|
||||
}
|
||||
|
||||
export interface UpdatePromotionRuleDTO {
|
||||
id: string
|
||||
}
|
||||
|
||||
export enum ApplicationMethodRuleTypes {
|
||||
TARGET_RULES = "target_rules",
|
||||
BUY_RULES = "buy_rules",
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { PromotionTypeValues } from "@medusajs/types"
|
||||
|
||||
export interface CreatePromotionDTO {
|
||||
code: string
|
||||
type: PromotionTypeValues
|
||||
is_automatic?: boolean
|
||||
campaign?: string
|
||||
}
|
||||
|
||||
export interface UpdatePromotionDTO {
|
||||
id: string
|
||||
code?: string
|
||||
type?: PromotionTypeValues
|
||||
is_automatic?: boolean
|
||||
campaign?: string
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { PromotionTypes } from "@medusajs/types"
|
||||
import {
|
||||
ApplicationMethodTargetType,
|
||||
ComputedActions,
|
||||
MedusaError,
|
||||
PromotionType,
|
||||
isPresent,
|
||||
} from "@medusajs/utils"
|
||||
import { areRulesValidForContext } from "../validations"
|
||||
import { computeActionForBudgetExceeded } from "./usage"
|
||||
|
||||
// TODO: calculations should eventually move to a totals util outside of the module
|
||||
export function getComputedActionsForBuyGet(
|
||||
promotion: PromotionTypes.PromotionDTO,
|
||||
itemsContext: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.ITEMS],
|
||||
methodIdPromoValueMap: Map<string, number>
|
||||
): PromotionTypes.ComputeActions[] {
|
||||
const buyRulesMinQuantity =
|
||||
promotion.application_method?.buy_rules_min_quantity
|
||||
const applyToQuantity = promotion.application_method?.apply_to_quantity
|
||||
const buyRules = promotion.application_method?.buy_rules
|
||||
const targetRules = promotion.application_method?.target_rules
|
||||
const computedActions: PromotionTypes.ComputeActions[] = []
|
||||
|
||||
if (!itemsContext) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`"items" should be present as an array in the context to compute actions`
|
||||
)
|
||||
}
|
||||
|
||||
if (!Array.isArray(buyRules) || !Array.isArray(targetRules)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const validQuantity = itemsContext
|
||||
.filter((item) => areRulesValidForContext(buyRules, item))
|
||||
.reduce((acc, next) => acc + next.quantity, 0)
|
||||
|
||||
if (
|
||||
!buyRulesMinQuantity ||
|
||||
!applyToQuantity ||
|
||||
buyRulesMinQuantity > validQuantity
|
||||
) {
|
||||
return []
|
||||
}
|
||||
|
||||
const validItemsForTargetRules = itemsContext
|
||||
.filter((item) => areRulesValidForContext(targetRules, item))
|
||||
.filter((item) => isPresent(item.subtotal) && isPresent(item.quantity))
|
||||
.sort((a, b) => {
|
||||
const aPrice = a.subtotal / a.quantity
|
||||
const bPrice = b.subtotal / b.quantity
|
||||
|
||||
return bPrice - aPrice
|
||||
})
|
||||
|
||||
let remainingQtyToApply = applyToQuantity
|
||||
|
||||
for (const method of validItemsForTargetRules) {
|
||||
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
|
||||
const multiplier = Math.min(method.quantity, remainingQtyToApply)
|
||||
const amount = (method.subtotal / method.quantity) * multiplier
|
||||
const newRemainingQtyToApply = remainingQtyToApply - multiplier
|
||||
|
||||
if (newRemainingQtyToApply < 0 || amount <= 0) {
|
||||
break
|
||||
} else {
|
||||
remainingQtyToApply = newRemainingQtyToApply
|
||||
}
|
||||
|
||||
const budgetExceededAction = computeActionForBudgetExceeded(
|
||||
promotion,
|
||||
amount
|
||||
)
|
||||
|
||||
if (budgetExceededAction) {
|
||||
computedActions.push(budgetExceededAction)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
methodIdPromoValueMap.set(method.id, appliedPromoValue + amount)
|
||||
|
||||
computedActions.push({
|
||||
action: ComputedActions.ADD_ITEM_ADJUSTMENT,
|
||||
item_id: method.id,
|
||||
amount,
|
||||
code: promotion.code!,
|
||||
})
|
||||
}
|
||||
|
||||
return computedActions
|
||||
}
|
||||
|
||||
export function sortByBuyGetType(a, b) {
|
||||
if (a.type === PromotionType.BUYGET && b.type !== PromotionType.BUYGET) {
|
||||
return -1
|
||||
} else if (
|
||||
a.type !== PromotionType.BUYGET &&
|
||||
b.type === PromotionType.BUYGET
|
||||
) {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./buy-get"
|
||||
export * from "./line-items"
|
||||
export * from "./usage"
|
||||
@@ -0,0 +1,180 @@
|
||||
import {
|
||||
ApplicationMethodAllocationValues,
|
||||
PromotionTypes,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
ApplicationMethodAllocation,
|
||||
ComputedActions,
|
||||
MedusaError,
|
||||
ApplicationMethodTargetType as TargetType,
|
||||
calculateAdjustmentAmountFromPromotion,
|
||||
} from "@medusajs/utils"
|
||||
import { areRulesValidForContext } from "../validations"
|
||||
import { computeActionForBudgetExceeded } from "./usage"
|
||||
|
||||
function validateContext(
|
||||
contextKey: string,
|
||||
context: PromotionTypes.ComputeActionContext[TargetType]
|
||||
) {
|
||||
if (!context) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`"${contextKey}" should be present as an array in the context for computeActions`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function getComputedActionsForItems(
|
||||
promotion: PromotionTypes.PromotionDTO,
|
||||
items: PromotionTypes.ComputeActionContext[TargetType.ITEMS],
|
||||
appliedPromotionsMap: Map<string, number>,
|
||||
allocationOverride?: ApplicationMethodAllocationValues
|
||||
): PromotionTypes.ComputeActions[] {
|
||||
validateContext("items", items)
|
||||
|
||||
return applyPromotionToItems(
|
||||
promotion,
|
||||
items,
|
||||
appliedPromotionsMap,
|
||||
allocationOverride
|
||||
)
|
||||
}
|
||||
|
||||
export function getComputedActionsForShippingMethods(
|
||||
promotion: PromotionTypes.PromotionDTO,
|
||||
shippingMethods: PromotionTypes.ComputeActionContext[TargetType.SHIPPING_METHODS],
|
||||
appliedPromotionsMap: Map<string, number>
|
||||
): PromotionTypes.ComputeActions[] {
|
||||
validateContext("shipping_methods", shippingMethods)
|
||||
|
||||
return applyPromotionToItems(promotion, shippingMethods, appliedPromotionsMap)
|
||||
}
|
||||
|
||||
export function getComputedActionsForOrder(
|
||||
promotion: PromotionTypes.PromotionDTO,
|
||||
itemApplicationContext: PromotionTypes.ComputeActionContext,
|
||||
methodIdPromoValueMap: Map<string, number>
|
||||
): PromotionTypes.ComputeActions[] {
|
||||
return getComputedActionsForItems(
|
||||
promotion,
|
||||
itemApplicationContext[TargetType.ITEMS],
|
||||
methodIdPromoValueMap,
|
||||
ApplicationMethodAllocation.ACROSS
|
||||
)
|
||||
}
|
||||
|
||||
function applyPromotionToItems(
|
||||
promotion: PromotionTypes.PromotionDTO,
|
||||
items:
|
||||
| PromotionTypes.ComputeActionContext[TargetType.ITEMS]
|
||||
| PromotionTypes.ComputeActionContext[TargetType.SHIPPING_METHODS],
|
||||
appliedPromotionsMap: Map<string, number>,
|
||||
allocationOverride?: ApplicationMethodAllocationValues
|
||||
): PromotionTypes.ComputeActions[] {
|
||||
const { application_method: applicationMethod } = promotion
|
||||
const allocation = applicationMethod?.allocation! || allocationOverride
|
||||
const computedActions: PromotionTypes.ComputeActions[] = []
|
||||
const applicableItems = getValidItemsForPromotion(items, promotion)
|
||||
const target = applicationMethod?.target_type
|
||||
|
||||
const isTargetShippingMethod = target === TargetType.SHIPPING_METHODS
|
||||
const isTargetLineItems = target === TargetType.ITEMS
|
||||
const isTargetOrder = target === TargetType.ORDER
|
||||
|
||||
let lineItemsTotal = 0
|
||||
|
||||
if (allocation === ApplicationMethodAllocation.ACROSS) {
|
||||
lineItemsTotal = applicableItems.reduce(
|
||||
(acc, item) =>
|
||||
acc + item.subtotal - (appliedPromotionsMap.get(item.id) ?? 0),
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
for (const item of applicableItems!) {
|
||||
const appliedPromoValue = appliedPromotionsMap.get(item.id) ?? 0
|
||||
const maxQuantity = isTargetShippingMethod
|
||||
? 1
|
||||
: applicationMethod?.max_quantity!
|
||||
|
||||
if (isTargetShippingMethod) {
|
||||
item.quantity = 1
|
||||
}
|
||||
|
||||
const amount = calculateAdjustmentAmountFromPromotion(
|
||||
item,
|
||||
{
|
||||
value: applicationMethod?.value ?? 0,
|
||||
applied_value: appliedPromoValue,
|
||||
max_quantity: maxQuantity,
|
||||
type: applicationMethod?.type!,
|
||||
allocation,
|
||||
},
|
||||
lineItemsTotal
|
||||
)
|
||||
|
||||
if (amount <= 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
const budgetExceededAction = computeActionForBudgetExceeded(
|
||||
promotion,
|
||||
amount
|
||||
)
|
||||
|
||||
if (budgetExceededAction) {
|
||||
computedActions.push(budgetExceededAction)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
appliedPromotionsMap.set(item.id, appliedPromoValue + amount)
|
||||
|
||||
if (isTargetLineItems || isTargetOrder) {
|
||||
computedActions.push({
|
||||
action: ComputedActions.ADD_ITEM_ADJUSTMENT,
|
||||
item_id: item.id,
|
||||
amount,
|
||||
code: promotion.code!,
|
||||
})
|
||||
}
|
||||
|
||||
if (isTargetShippingMethod) {
|
||||
computedActions.push({
|
||||
action: ComputedActions.ADD_SHIPPING_METHOD_ADJUSTMENT,
|
||||
shipping_method_id: item.id,
|
||||
amount,
|
||||
code: promotion.code!,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return computedActions
|
||||
}
|
||||
|
||||
function getValidItemsForPromotion(
|
||||
items:
|
||||
| PromotionTypes.ComputeActionContext[TargetType.ITEMS]
|
||||
| PromotionTypes.ComputeActionContext[TargetType.SHIPPING_METHODS],
|
||||
promotion: PromotionTypes.PromotionDTO
|
||||
) {
|
||||
const isTargetShippingMethod =
|
||||
promotion.application_method?.target_type === TargetType.SHIPPING_METHODS
|
||||
|
||||
return (
|
||||
items?.filter((item) => {
|
||||
const isSubtotalPresent = "subtotal" in item
|
||||
const isQuantityPresent = "quantity" in item
|
||||
const isPromotionApplicableToItem = areRulesValidForContext(
|
||||
promotion?.application_method?.target_rules!,
|
||||
item
|
||||
)
|
||||
|
||||
return (
|
||||
isPromotionApplicableToItem &&
|
||||
(isQuantityPresent || isTargetShippingMethod) &&
|
||||
isSubtotalPresent
|
||||
)
|
||||
}) || []
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import { PromotionTypes } from "@medusajs/types"
|
||||
import {
|
||||
ApplicationMethodAllocation,
|
||||
ApplicationMethodTargetType,
|
||||
ApplicationMethodType,
|
||||
ComputedActions,
|
||||
MedusaError,
|
||||
} from "@medusajs/utils"
|
||||
import { areRulesValidForContext } from "../validations"
|
||||
import { computeActionForBudgetExceeded } from "./usage"
|
||||
|
||||
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!) {
|
||||
if (!method.subtotal) {
|
||||
continue
|
||||
}
|
||||
|
||||
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
|
||||
let promotionValue = applicationMethod?.value ?? 0
|
||||
const applicableTotal = method.subtotal - appliedPromoValue
|
||||
|
||||
if (applicationMethod?.type === ApplicationMethodType.PERCENTAGE) {
|
||||
promotionValue = (promotionValue / 100) * applicableTotal
|
||||
}
|
||||
|
||||
const amount = Math.min(promotionValue, applicableTotal)
|
||||
|
||||
if (amount <= 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
const budgetExceededAction = computeActionForBudgetExceeded(
|
||||
promotion,
|
||||
amount
|
||||
)
|
||||
|
||||
if (budgetExceededAction) {
|
||||
computedActions.push(budgetExceededAction)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
methodIdPromoValueMap.set(method.id, appliedPromoValue + amount)
|
||||
|
||||
computedActions.push({
|
||||
action: ComputedActions.ADD_SHIPPING_METHOD_ADJUSTMENT,
|
||||
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.subtotal ?? 0) - appliedPromoValue
|
||||
}, 0)
|
||||
|
||||
if (totalApplicableValue <= 0) {
|
||||
return computedActions
|
||||
}
|
||||
|
||||
for (const method of shippingMethods!) {
|
||||
if (!method.subtotal) {
|
||||
continue
|
||||
}
|
||||
|
||||
const promotionValue = applicationMethod?.value ?? 0
|
||||
const applicableTotal = method.subtotal
|
||||
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
|
||||
|
||||
// TODO: should we worry about precision here?
|
||||
let applicablePromotionValue =
|
||||
(applicableTotal / totalApplicableValue) * promotionValue -
|
||||
appliedPromoValue
|
||||
|
||||
if (applicationMethod?.type === ApplicationMethodType.PERCENTAGE) {
|
||||
applicablePromotionValue =
|
||||
(promotionValue / 100) * (applicableTotal - appliedPromoValue)
|
||||
}
|
||||
|
||||
const amount = Math.min(applicablePromotionValue, applicableTotal)
|
||||
|
||||
if (amount <= 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
const budgetExceededAction = computeActionForBudgetExceeded(
|
||||
promotion,
|
||||
amount
|
||||
)
|
||||
|
||||
if (budgetExceededAction) {
|
||||
computedActions.push(budgetExceededAction)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
methodIdPromoValueMap.set(method.id, appliedPromoValue + amount)
|
||||
|
||||
computedActions.push({
|
||||
action: ComputedActions.ADD_SHIPPING_METHOD_ADJUSTMENT,
|
||||
shipping_method_id: method.id,
|
||||
amount,
|
||||
code: promotion.code!,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return computedActions
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import {
|
||||
CampaignBudgetExceededAction,
|
||||
ComputeActions,
|
||||
PromotionDTO,
|
||||
} from "@medusajs/types"
|
||||
import { CampaignBudgetType, ComputedActions } from "@medusajs/utils"
|
||||
|
||||
export function canRegisterUsage(computedAction: ComputeActions): boolean {
|
||||
return (
|
||||
[
|
||||
ComputedActions.ADD_ITEM_ADJUSTMENT,
|
||||
ComputedActions.ADD_SHIPPING_METHOD_ADJUSTMENT,
|
||||
] as string[]
|
||||
).includes(computedAction.action)
|
||||
}
|
||||
|
||||
export function computeActionForBudgetExceeded(
|
||||
promotion: PromotionDTO,
|
||||
amount: number
|
||||
): CampaignBudgetExceededAction | void {
|
||||
const campaignBudget = promotion.campaign?.budget
|
||||
|
||||
if (!campaignBudget) {
|
||||
return
|
||||
}
|
||||
|
||||
const campaignBudgetUsed = campaignBudget.used ?? 0
|
||||
const totalUsed =
|
||||
campaignBudget.type === CampaignBudgetType.SPEND
|
||||
? campaignBudgetUsed + amount
|
||||
: campaignBudgetUsed + 1
|
||||
|
||||
if (campaignBudget.limit && totalUsed > campaignBudget.limit) {
|
||||
return {
|
||||
action: ComputedActions.CAMPAIGN_BUDGET_EXCEEDED,
|
||||
code: promotion.code!,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * as ComputeActionUtils from "./compute-actions"
|
||||
export * from "./validations"
|
||||
@@ -0,0 +1,137 @@
|
||||
import {
|
||||
ApplicationMethodAllocation,
|
||||
ApplicationMethodTargetType,
|
||||
ApplicationMethodType,
|
||||
isDefined,
|
||||
isPresent,
|
||||
MedusaError,
|
||||
PromotionType,
|
||||
} from "@medusajs/utils"
|
||||
import { Promotion } from "@models"
|
||||
import { CreateApplicationMethodDTO, UpdateApplicationMethodDTO } from "@types"
|
||||
|
||||
export const allowedAllocationTargetTypes: string[] = [
|
||||
ApplicationMethodTargetType.SHIPPING_METHODS,
|
||||
ApplicationMethodTargetType.ITEMS,
|
||||
]
|
||||
|
||||
export const allowedAllocationTypes: string[] = [
|
||||
ApplicationMethodAllocation.ACROSS,
|
||||
ApplicationMethodAllocation.EACH,
|
||||
]
|
||||
|
||||
export const allowedAllocationForQuantity: string[] = [
|
||||
ApplicationMethodAllocation.EACH,
|
||||
]
|
||||
|
||||
export function validateApplicationMethodAttributes(
|
||||
data: UpdateApplicationMethodDTO | CreateApplicationMethodDTO,
|
||||
promotion: Promotion
|
||||
) {
|
||||
const applicationMethod = promotion?.application_method || {}
|
||||
const buyRulesMinQuantity =
|
||||
data.buy_rules_min_quantity || applicationMethod?.buy_rules_min_quantity
|
||||
const applyToQuantity =
|
||||
data.apply_to_quantity || applicationMethod?.apply_to_quantity
|
||||
const targetType = data.target_type || applicationMethod?.target_type
|
||||
const type = data.type || applicationMethod?.type
|
||||
const applicationMethodType = data.type || applicationMethod?.type
|
||||
const value = data.value || applicationMethod.value
|
||||
const maxQuantity = data.max_quantity || applicationMethod.max_quantity
|
||||
const allocation = data.allocation || applicationMethod.allocation
|
||||
const allTargetTypes: string[] = Object.values(ApplicationMethodTargetType)
|
||||
|
||||
if (
|
||||
type === ApplicationMethodType.PERCENTAGE &&
|
||||
(typeof value !== "number" || value <= 0 || value > 100)
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Application Method value should be a percentage number between 0 and 100`
|
||||
)
|
||||
}
|
||||
|
||||
if (promotion?.type === PromotionType.BUYGET) {
|
||||
if (!isPresent(applyToQuantity)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`apply_to_quantity is a required field for Promotion type of ${PromotionType.BUYGET}`
|
||||
)
|
||||
}
|
||||
|
||||
if (!isPresent(buyRulesMinQuantity)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`buy_rules_min_quantity is a required field for Promotion type of ${PromotionType.BUYGET}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
allocation === ApplicationMethodAllocation.ACROSS &&
|
||||
isPresent(maxQuantity)
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`application_method.max_quantity is not allowed to be set for allocation (${ApplicationMethodAllocation.ACROSS})`
|
||||
)
|
||||
}
|
||||
|
||||
if (!allTargetTypes.includes(targetType)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`application_method.target_type should be one of ${allTargetTypes.join(
|
||||
", "
|
||||
)}`
|
||||
)
|
||||
}
|
||||
|
||||
const allTypes: string[] = Object.values(ApplicationMethodType)
|
||||
|
||||
if (!allTypes.includes(applicationMethodType)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`application_method.type should be one of ${allTypes.join(", ")}`
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
allowedAllocationTargetTypes.includes(targetType) &&
|
||||
!allowedAllocationTypes.includes(allocation || "")
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`application_method.allocation should be either '${allowedAllocationTypes.join(
|
||||
" OR "
|
||||
)}' when application_method.target_type is either '${allowedAllocationTargetTypes.join(
|
||||
" OR "
|
||||
)}'`
|
||||
)
|
||||
}
|
||||
|
||||
const allAllocationTypes: string[] = Object.values(
|
||||
ApplicationMethodAllocation
|
||||
)
|
||||
|
||||
if (allocation && !allAllocationTypes.includes(allocation)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`application_method.allocation should be one of ${allAllocationTypes.join(
|
||||
", "
|
||||
)}`
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
allocation &&
|
||||
allowedAllocationForQuantity.includes(allocation) &&
|
||||
!isDefined(maxQuantity)
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`application_method.max_quantity is required when application_method.allocation is '${allowedAllocationForQuantity.join(
|
||||
" OR "
|
||||
)}'`
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./application-method"
|
||||
export * from "./promotion-rule"
|
||||
@@ -0,0 +1,104 @@
|
||||
import { PromotionRuleDTO, PromotionRuleOperatorValues } from "@medusajs/types"
|
||||
import {
|
||||
isPresent,
|
||||
isString,
|
||||
MedusaError,
|
||||
pickValueFromObject,
|
||||
PromotionRuleOperator,
|
||||
} from "@medusajs/utils"
|
||||
import { CreatePromotionRuleDTO } from "@types"
|
||||
|
||||
export function validatePromotionRuleAttributes(
|
||||
promotionRulesData: CreatePromotionRuleDTO[]
|
||||
) {
|
||||
const errors: string[] = []
|
||||
|
||||
for (const promotionRuleData of promotionRulesData) {
|
||||
if (!isPresent(promotionRuleData.attribute)) {
|
||||
errors.push("rules[].attribute is a required field")
|
||||
}
|
||||
|
||||
if (!isPresent(promotionRuleData.operator)) {
|
||||
errors.push("rules[].operator is a required field")
|
||||
}
|
||||
|
||||
if (isPresent(promotionRuleData.operator)) {
|
||||
const allowedOperators: PromotionRuleOperatorValues[] = Object.values(
|
||||
PromotionRuleOperator
|
||||
)
|
||||
|
||||
if (!allowedOperators.includes(promotionRuleData.operator)) {
|
||||
errors.push(
|
||||
`rules[].operator (${
|
||||
promotionRuleData.operator
|
||||
}) is invalid. It should be one of ${allowedOperators.join(", ")}`
|
||||
)
|
||||
}
|
||||
} else {
|
||||
errors.push("rules[].operator is a required field")
|
||||
}
|
||||
}
|
||||
|
||||
if (!errors.length) return
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user