feat: Add DiscountConditions (#1230)
* feat: Add DiscountCondition entity + Join table per relation (#1146) * feat: Convert DiscountService to TypeScript (#1149) * feat: Add DiscountRepository + bulk insert and remove (#1156) * feat: Add `conditions` to payload in `POST /discounts` and `POST /discounts/:id` (#1170) * feat: Add DiscountRuleCondition entity * fix relation * fix join key * Add discount rule condition repo * add join table per relation * Convert DiscountService to TypeScript * feat: Add DiscountConditionRepository * Add migration + remove use of valid_for * revert changes to files, not done yet * init work on create discount endpoint * Add conditions to create discount endpoint * Add conditions to update discount endpoint * Add unique constraint to discount condition * integration tests passing * fix imports of models * fix tests (excluding totals calculations) * Fix commented code * add unique constraint on discount condition * Add generic way of generating retrieve configs * Requested changes + ExactlyOne validator * Remove isLocal flag from error handler * Use postgres error constant * remove commented code * feat: Add `isValidForProduct` to check if Discount is valid for a given Product (#1172) * feat: Add `canApplyForCustomer` to check if Discount is valid for customer groups (#1212) * feat: Add `calculateDiscountForLineItem` (#1224) * feat: Adds discount condition test factory (#1228) * Remove use of valid_for * Tests passing * Remove valid_for form relations * Add integration tests for applying discounts to cart
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
import { Connection } from "typeorm"
|
||||
import faker from "faker"
|
||||
import { Cart } from "@medusajs/medusa"
|
||||
|
||||
import { RegionFactoryData, simpleRegionFactory } from "./simple-region-factory"
|
||||
import {
|
||||
LineItemFactoryData,
|
||||
simpleLineItemFactory,
|
||||
} from "./simple-line-item-factory"
|
||||
import faker from "faker"
|
||||
import { Connection } from "typeorm"
|
||||
import {
|
||||
AddressFactoryData,
|
||||
simpleAddressFactory,
|
||||
} from "./simple-address-factory"
|
||||
import { simpleCustomerFactory } from "./simple-customer-factory"
|
||||
import {
|
||||
LineItemFactoryData,
|
||||
simpleLineItemFactory,
|
||||
} from "./simple-line-item-factory"
|
||||
import { RegionFactoryData, simpleRegionFactory } from "./simple-region-factory"
|
||||
import {
|
||||
ShippingMethodFactoryData,
|
||||
simpleShippingMethodFactory,
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
|
||||
export type CartFactoryData = {
|
||||
id?: string
|
||||
customer?: string | { email: string }
|
||||
region?: RegionFactoryData | string
|
||||
email?: string | null
|
||||
line_items?: LineItemFactoryData[]
|
||||
@@ -43,6 +44,22 @@ export const simpleCartFactory = async (
|
||||
const region = await simpleRegionFactory(connection, data.region)
|
||||
regionId = region.id
|
||||
}
|
||||
|
||||
let customerId: string
|
||||
if (typeof data.customer === "string") {
|
||||
customerId = data.customer
|
||||
} else {
|
||||
if (data?.customer?.email) {
|
||||
const customer = await simpleCustomerFactory(connection, data.customer)
|
||||
customerId = customer.id
|
||||
} else if (data.email) {
|
||||
const customer = await simpleCustomerFactory(connection, {
|
||||
email: data.email,
|
||||
})
|
||||
customerId = customer.id
|
||||
}
|
||||
}
|
||||
|
||||
const address = await simpleAddressFactory(connection, data.shipping_address)
|
||||
|
||||
const id = data.id || `simple-cart-${Math.random() * 1000}`
|
||||
@@ -51,6 +68,7 @@ export const simpleCartFactory = async (
|
||||
email:
|
||||
typeof data.email !== "undefined" ? data.email : faker.internet.email(),
|
||||
region_id: regionId,
|
||||
customer_id: customerId,
|
||||
shipping_address_id: address.id,
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Customer } from "@medusajs/medusa"
|
||||
import faker from "faker"
|
||||
import { Connection } from "typeorm"
|
||||
import {
|
||||
CustomerGroupFactoryData,
|
||||
simpleCustomerGroupFactory,
|
||||
} from "./simple-customer-group-factory"
|
||||
|
||||
export type CustomerFactoryData = {
|
||||
id?: string
|
||||
email?: string
|
||||
groups?: CustomerGroupFactoryData[]
|
||||
}
|
||||
|
||||
export const simpleCustomerFactory = async (
|
||||
connection: Connection,
|
||||
data: CustomerFactoryData = {},
|
||||
seed?: number
|
||||
): Promise<Customer> => {
|
||||
if (typeof seed !== "undefined") {
|
||||
faker.seed(seed)
|
||||
}
|
||||
|
||||
const manager = connection.manager
|
||||
|
||||
const customerId = data.id || `simple-customer-${Math.random() * 1000}`
|
||||
const c = manager.create(Customer, {
|
||||
id: customerId,
|
||||
email: data.email,
|
||||
})
|
||||
|
||||
const customer = await manager.save(c)
|
||||
|
||||
if (data.groups) {
|
||||
const groups = []
|
||||
for (const g of data.groups) {
|
||||
const created = await simpleCustomerGroupFactory(connection, g)
|
||||
groups.push(created)
|
||||
}
|
||||
|
||||
customer.groups = groups
|
||||
await manager.save(customer)
|
||||
}
|
||||
|
||||
return customer
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { CustomerGroup } from "@medusajs/medusa"
|
||||
import faker from "faker"
|
||||
import { Connection } from "typeorm"
|
||||
|
||||
export type CustomerGroupFactoryData = {
|
||||
id?: string
|
||||
name?: string
|
||||
}
|
||||
|
||||
export const simpleCustomerGroupFactory = async (
|
||||
connection: Connection,
|
||||
data: CustomerGroupFactoryData = {},
|
||||
seed?: number
|
||||
): Promise<CustomerGroup> => {
|
||||
if (typeof seed !== "undefined") {
|
||||
faker.seed(seed)
|
||||
}
|
||||
|
||||
const manager = connection.manager
|
||||
|
||||
const customerGroupId =
|
||||
data.id || `simple-customer-group-${Math.random() * 1000}`
|
||||
const c = manager.create(CustomerGroup, {
|
||||
id: customerGroupId,
|
||||
name: data.name,
|
||||
})
|
||||
|
||||
const group = await manager.save(c)
|
||||
|
||||
return group
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import {
|
||||
DiscountCondition,
|
||||
DiscountConditionOperator,
|
||||
DiscountConditionType,
|
||||
} from "@medusajs/medusa/dist/models/discount-condition"
|
||||
import { DiscountConditionCustomerGroup } from "@medusajs/medusa/dist/models/discount-condition-customer-group"
|
||||
import { DiscountConditionProduct } from "@medusajs/medusa/dist/models/discount-condition-product"
|
||||
import { DiscountConditionProductCollection } from "@medusajs/medusa/dist/models/discount-condition-product-collection"
|
||||
import { DiscountConditionProductTag } from "@medusajs/medusa/dist/models/discount-condition-product-tag"
|
||||
import { DiscountConditionProductType } from "@medusajs/medusa/dist/models/discount-condition-product-type"
|
||||
import { DiscountConditionJoinTableForeignKey } from "@medusajs/medusa/dist/repositories/discount-condition"
|
||||
import faker from "faker"
|
||||
import { Connection } from "typeorm"
|
||||
|
||||
export type DiscuntConditionFactoryData = {
|
||||
rule_id: string
|
||||
type: DiscountConditionType
|
||||
operator: DiscountConditionOperator
|
||||
products: string[]
|
||||
product_collections: string[]
|
||||
product_types: string[]
|
||||
product_tags: string[]
|
||||
customer_groups: string[]
|
||||
}
|
||||
|
||||
const getJoinTableResourceIdentifiers = (type: string) => {
|
||||
let conditionTable: any
|
||||
let resourceKey
|
||||
|
||||
switch (type) {
|
||||
case DiscountConditionType.PRODUCTS: {
|
||||
resourceKey = DiscountConditionJoinTableForeignKey.PRODUCT_ID
|
||||
conditionTable = DiscountConditionProduct
|
||||
break
|
||||
}
|
||||
case DiscountConditionType.PRODUCT_TYPES: {
|
||||
resourceKey = DiscountConditionJoinTableForeignKey.PRODUCT_TYPE_ID
|
||||
conditionTable = DiscountConditionProductType
|
||||
break
|
||||
}
|
||||
case DiscountConditionType.PRODUCT_COLLECTIONS: {
|
||||
resourceKey = DiscountConditionJoinTableForeignKey.PRODUCT_COLLECTION_ID
|
||||
conditionTable = DiscountConditionProductCollection
|
||||
break
|
||||
}
|
||||
case DiscountConditionType.PRODUCT_TAGS: {
|
||||
resourceKey = DiscountConditionJoinTableForeignKey.PRODUCT_TAG_ID
|
||||
|
||||
conditionTable = DiscountConditionProductTag
|
||||
break
|
||||
}
|
||||
case DiscountConditionType.CUSTOMER_GROUPS: {
|
||||
resourceKey = DiscountConditionJoinTableForeignKey.CUSTOMER_GROUP_ID
|
||||
conditionTable = DiscountConditionCustomerGroup
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return {
|
||||
resourceKey,
|
||||
conditionTable,
|
||||
}
|
||||
}
|
||||
|
||||
export const simpleDiscountConditionFactory = async (
|
||||
connection: Connection,
|
||||
data: DiscuntConditionFactoryData,
|
||||
seed?: number
|
||||
): Promise<void> => {
|
||||
if (typeof seed !== "undefined") {
|
||||
faker.seed(seed)
|
||||
}
|
||||
|
||||
const manager = connection.manager
|
||||
|
||||
let resources = []
|
||||
|
||||
if (data.products) {
|
||||
resources = data.products
|
||||
}
|
||||
if (data.product_collections) {
|
||||
resources = data.product_collections
|
||||
}
|
||||
if (data.product_types) {
|
||||
resources = data.product_types
|
||||
}
|
||||
if (data.product_tags) {
|
||||
resources = data.product_tags
|
||||
}
|
||||
if (data.customer_groups) {
|
||||
resources = data.customer_groups
|
||||
}
|
||||
|
||||
const condToSave = manager.create(DiscountCondition, {
|
||||
type: data.type,
|
||||
operator: data.operator,
|
||||
discount_rule_id: data.rule_id,
|
||||
})
|
||||
|
||||
const { conditionTable, resourceKey } = getJoinTableResourceIdentifiers(
|
||||
data.type
|
||||
)
|
||||
|
||||
const condition = await manager.save(condToSave)
|
||||
|
||||
for (const resourceCond of resources) {
|
||||
const toSave = manager.create(conditionTable, {
|
||||
[resourceKey]: resourceCond,
|
||||
condition_id: condition.id,
|
||||
})
|
||||
|
||||
await manager.save(toSave)
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,21 @@
|
||||
import { Connection } from "typeorm"
|
||||
import faker from "faker"
|
||||
import {
|
||||
AllocationType,
|
||||
Discount,
|
||||
DiscountRule,
|
||||
DiscountRuleType,
|
||||
AllocationType,
|
||||
} from "@medusajs/medusa"
|
||||
import faker from "faker"
|
||||
import { Connection } from "typeorm"
|
||||
import {
|
||||
DiscuntConditionFactoryData,
|
||||
simpleDiscountConditionFactory,
|
||||
} from "./simple-discount-condition-factory"
|
||||
|
||||
export type DiscountRuleFactoryData = {
|
||||
type?: DiscountRuleType
|
||||
value?: number
|
||||
allocation?: AllocationType
|
||||
conditions: DiscuntConditionFactoryData[]
|
||||
}
|
||||
|
||||
export type DiscountFactoryData = {
|
||||
@@ -41,6 +46,16 @@ export const simpleDiscountFactory = async (
|
||||
|
||||
const dRule = await manager.save(ruleToSave)
|
||||
|
||||
if (data?.rule?.conditions) {
|
||||
for (const condition of data.rule.conditions) {
|
||||
await simpleDiscountConditionFactory(
|
||||
connection,
|
||||
{ ...condition, rule_id: dRule.id },
|
||||
1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const toSave = manager.create(Discount, {
|
||||
id: data.id,
|
||||
is_dynamic: data.is_dynamic ?? false,
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { Connection } from "typeorm"
|
||||
import faker from "faker"
|
||||
import {
|
||||
ShippingProfileType,
|
||||
ShippingProfile,
|
||||
Product,
|
||||
ProductType,
|
||||
ProductOption,
|
||||
ProductTag,
|
||||
ProductType,
|
||||
ShippingProfile,
|
||||
ShippingProfileType,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
import faker from "faker"
|
||||
import { Connection } from "typeorm"
|
||||
import {
|
||||
simpleProductVariantFactory,
|
||||
ProductVariantFactoryData,
|
||||
simpleProductVariantFactory,
|
||||
} from "./simple-product-variant-factory"
|
||||
|
||||
export type ProductFactoryData = {
|
||||
@@ -19,6 +19,7 @@ export type ProductFactoryData = {
|
||||
status?: string
|
||||
title?: string
|
||||
type?: string
|
||||
tags?: string[]
|
||||
options?: { id: string; title: string }[]
|
||||
variants?: ProductVariantFactoryData[]
|
||||
}
|
||||
@@ -42,27 +43,40 @@ export const simpleProductFactory = async (
|
||||
type: ShippingProfileType.GIFT_CARD,
|
||||
})
|
||||
|
||||
let typeId: string
|
||||
const prodId = data.id || `simple-product-${Math.random() * 1000}`
|
||||
const productToCreate = {
|
||||
id: prodId,
|
||||
title: data.title || faker.commerce.productName(),
|
||||
is_giftcard: data.is_giftcard || false,
|
||||
discountable: !data.is_giftcard,
|
||||
tags: [],
|
||||
profile_id: data.is_giftcard ? gcProfile.id : defaultProfile.id,
|
||||
}
|
||||
|
||||
if (typeof data.tags !== "undefined") {
|
||||
for (let i = 0; i < data.tags.length; i++) {
|
||||
const createdTag = manager.create(ProductTag, {
|
||||
id: `tag-${Math.random() * 1000}`,
|
||||
value: data.tags[i],
|
||||
})
|
||||
|
||||
const tagRes = await manager.save(createdTag)
|
||||
|
||||
productToCreate.tags.push(tagRes)
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof data.type !== "undefined") {
|
||||
const toSave = manager.create(ProductType, {
|
||||
value: data.type,
|
||||
})
|
||||
const res = await manager.save(toSave)
|
||||
typeId = res.id
|
||||
productToCreate["type_id"] = res.id
|
||||
}
|
||||
|
||||
const prodId = data.id || `simple-product-${Math.random() * 1000}`
|
||||
const toSave = manager.create(Product, {
|
||||
id: prodId,
|
||||
type_id: typeId,
|
||||
status: data.status,
|
||||
title: data.title || faker.commerce.productName(),
|
||||
is_giftcard: data.is_giftcard || false,
|
||||
discountable: !data.is_giftcard,
|
||||
profile_id: data.is_giftcard ? gcProfile.id : defaultProfile.id,
|
||||
})
|
||||
const toSave = manager.create(Product, productToCreate)
|
||||
|
||||
const product = await manager.save(toSave)
|
||||
await manager.save(toSave)
|
||||
|
||||
const optionId = `${prodId}-option`
|
||||
const options = data.options || [{ id: optionId, title: "Size" }]
|
||||
@@ -97,5 +111,5 @@ export const simpleProductFactory = async (
|
||||
await simpleProductVariantFactory(connection, factoryData)
|
||||
}
|
||||
|
||||
return product
|
||||
return await manager.findOne(Product, { id: prodId }, { relations: ["tags"] })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user