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:
Oliver Windall Juhl
2022-03-24 16:47:50 +01:00
committed by GitHub
parent b7f699654b
commit a610805917
60 changed files with 4805 additions and 2021 deletions
@@ -0,0 +1,66 @@
import {
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryColumn,
UpdateDateColumn,
} from "typeorm"
import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column"
import { CustomerGroup } from "./customer-group"
import { DiscountCondition } from "./discount-condition"
@Entity()
export class DiscountConditionCustomerGroup {
@PrimaryColumn()
customer_group_id: string
@PrimaryColumn()
condition_id: string
@ManyToOne(() => CustomerGroup, { onDelete: "CASCADE" })
@JoinColumn({ name: "customer_group_id" })
customer_group?: CustomerGroup
@ManyToOne(() => DiscountCondition, { onDelete: "CASCADE" })
@JoinColumn({ name: "condition_id" })
discount_condition?: DiscountCondition
@CreateDateColumn({ type: resolveDbType("timestamptz") })
created_at: Date
@UpdateDateColumn({ type: resolveDbType("timestamptz") })
updated_at: Date
@DbAwareColumn({ type: "jsonb", nullable: true })
metadata: any
}
/**
* @schema discount_condition_customer_group
* title: "Product Tag Discount Condition"
* description: "Associates a discount condition with a customer group"
* x-resourceId: discount_condition_customer_group
* properties:
* customer_group_id:
* description: "The id of the Product Tag"
* type: string
* condition_id:
* description: "The id of the Discount Condition"
* type: string
* created_at:
* description: "The date with timezone at which the resource was created."
* type: string
* format: date-time
* updated_at:
* description: "The date with timezone at which the resource was last updated."
* type: string
* format: date-time
* deleted_at:
* description: "The date with timezone at which the resource was deleted."
* type: string
* format: date-time
* metadata:
* description: "An optional key-value map with additional information."
* type: object
*/
@@ -0,0 +1,66 @@
import {
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryColumn,
UpdateDateColumn,
} from "typeorm"
import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column"
import { DiscountCondition } from "./discount-condition"
import { ProductCollection } from "./product-collection"
@Entity()
export class DiscountConditionProductCollection {
@PrimaryColumn()
product_collection_id: string
@PrimaryColumn()
condition_id: string
@ManyToOne(() => ProductCollection, { onDelete: "CASCADE" })
@JoinColumn({ name: "product_collection_id" })
product_collection?: ProductCollection
@ManyToOne(() => DiscountCondition, { onDelete: "CASCADE" })
@JoinColumn({ name: "condition_id" })
discount_condition?: DiscountCondition
@CreateDateColumn({ type: resolveDbType("timestamptz") })
created_at: Date
@UpdateDateColumn({ type: resolveDbType("timestamptz") })
updated_at: Date
@DbAwareColumn({ type: "jsonb", nullable: true })
metadata: any
}
/**
* @schema discount_condition_product_collection
* title: "Product Collection Discount Condition"
* description: "Associates a discount condition with a product collection"
* x-resourceId: discount_condition_product_collection
* properties:
* product_collection_id:
* description: "The id of the Product Collection"
* type: string
* condition_id:
* description: "The id of the Discount Condition"
* type: string
* created_at:
* description: "The date with timezone at which the resource was created."
* type: string
* format: date-time
* updated_at:
* description: "The date with timezone at which the resource was last updated."
* type: string
* format: date-time
* deleted_at:
* description: "The date with timezone at which the resource was deleted."
* type: string
* format: date-time
* metadata:
* description: "An optional key-value map with additional information."
* type: object
*/
@@ -0,0 +1,66 @@
import {
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryColumn,
UpdateDateColumn,
} from "typeorm"
import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column"
import { DiscountCondition } from "./discount-condition"
import { ProductTag } from "./product-tag"
@Entity()
export class DiscountConditionProductTag {
@PrimaryColumn()
product_tag_id: string
@PrimaryColumn()
condition_id: string
@ManyToOne(() => ProductTag, { onDelete: "CASCADE" })
@JoinColumn({ name: "product_tag_id" })
product_tag?: ProductTag
@ManyToOne(() => DiscountCondition, { onDelete: "CASCADE" })
@JoinColumn({ name: "condition_id" })
discount_condition?: DiscountCondition
@CreateDateColumn({ type: resolveDbType("timestamptz") })
created_at: Date
@UpdateDateColumn({ type: resolveDbType("timestamptz") })
updated_at: Date
@DbAwareColumn({ type: "jsonb", nullable: true })
metadata: any
}
/**
* @schema discount_condition_product_tag
* title: "Product Tag Discount Condition"
* description: "Associates a discount condition with a product tag"
* x-resourceId: discount_condition_product_tag
* properties:
* product_tag_id:
* description: "The id of the Product Tag"
* type: string
* condition_id:
* description: "The id of the Discount Condition"
* type: string
* created_at:
* description: "The date with timezone at which the resource was created."
* type: string
* format: date-time
* updated_at:
* description: "The date with timezone at which the resource was last updated."
* type: string
* format: date-time
* deleted_at:
* description: "The date with timezone at which the resource was deleted."
* type: string
* format: date-time
* metadata:
* description: "An optional key-value map with additional information."
* type: object
*/
@@ -0,0 +1,66 @@
import {
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryColumn,
UpdateDateColumn,
} from "typeorm"
import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column"
import { DiscountCondition } from "./discount-condition"
import { ProductType } from "./product-type"
@Entity()
export class DiscountConditionProductType {
@PrimaryColumn()
product_type_id: string
@PrimaryColumn()
condition_id: string
@ManyToOne(() => ProductType, { onDelete: "CASCADE" })
@JoinColumn({ name: "product_type_id" })
product_type?: ProductType
@ManyToOne(() => DiscountCondition, { onDelete: "CASCADE" })
@JoinColumn({ name: "condition_id" })
discount_condition?: DiscountCondition
@CreateDateColumn({ type: resolveDbType("timestamptz") })
created_at: Date
@UpdateDateColumn({ type: resolveDbType("timestamptz") })
updated_at: Date
@DbAwareColumn({ type: "jsonb", nullable: true })
metadata: any
}
/**
* @schema discount_condition_product_type
* title: "Product Type Discount Condition"
* description: "Associates a discount condition with a product type"
* x-resourceId: discount_condition_product
* properties:
* product_type_id:
* description: "The id of the Product Type"
* type: string
* condition_id:
* description: "The id of the Discount Condition"
* type: string
* created_at:
* description: "The date with timezone at which the resource was created."
* type: string
* format: date-time
* updated_at:
* description: "The date with timezone at which the resource was last updated."
* type: string
* format: date-time
* deleted_at:
* description: "The date with timezone at which the resource was deleted."
* type: string
* format: date-time
* metadata:
* description: "An optional key-value map with additional information."
* type: object
*/
@@ -0,0 +1,66 @@
import {
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryColumn,
UpdateDateColumn,
} from "typeorm"
import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column"
import { DiscountCondition } from "./discount-condition"
import { Product } from "./product"
@Entity()
export class DiscountConditionProduct {
@PrimaryColumn()
product_id: string
@PrimaryColumn()
condition_id: string
@ManyToOne(() => Product, { onDelete: "CASCADE" })
@JoinColumn({ name: "product_id" })
product?: Product
@ManyToOne(() => DiscountCondition, { onDelete: "CASCADE" })
@JoinColumn({ name: "condition_id" })
discount_condition?: DiscountCondition
@CreateDateColumn({ type: resolveDbType("timestamptz") })
created_at: Date
@UpdateDateColumn({ type: resolveDbType("timestamptz") })
updated_at: Date
@DbAwareColumn({ type: "jsonb", nullable: true })
metadata: any
}
/**
* @schema discount_condition_product
* title: "Product Discount Condition"
* description: "Associates a discount condition with a product"
* x-resourceId: discount_condition_product
* properties:
* product_id:
* description: "The id of the Product"
* type: string
* condition_id:
* description: "The id of the Discount Condition"
* type: string
* created_at:
* description: "The date with timezone at which the resource was created."
* type: string
* format: date-time
* updated_at:
* description: "The date with timezone at which the resource was last updated."
* type: string
* format: date-time
* deleted_at:
* description: "The date with timezone at which the resource was deleted."
* type: string
* format: date-time
* metadata:
* description: "An optional key-value map with additional information."
* type: object
*/
@@ -0,0 +1,186 @@
import {
BeforeInsert,
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
Index,
JoinColumn,
JoinTable,
ManyToMany,
ManyToOne,
PrimaryColumn,
Unique,
UpdateDateColumn,
} from "typeorm"
import { ulid } from "ulid"
import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column"
import { CustomerGroup } from "./customer-group"
import { DiscountRule } from "./discount-rule"
import { Product } from "./product"
import { ProductCollection } from "./product-collection"
import { ProductTag } from "./product-tag"
import { ProductType } from "./product-type"
export enum DiscountConditionType {
PRODUCTS = "products",
PRODUCT_TYPES = "product_types",
PRODUCT_COLLECTIONS = "product_collections",
PRODUCT_TAGS = "product_tags",
CUSTOMER_GROUPS = "customer_groups",
}
export enum DiscountConditionOperator {
IN = "in",
NOT_IN = "not_in",
}
@Entity()
@Unique("dctypeuniq", ["type", "operator", "discount_rule_id"])
export class DiscountCondition {
@PrimaryColumn()
id: string
@DbAwareColumn({
type: "enum",
enum: DiscountConditionType,
})
type: DiscountConditionType
@DbAwareColumn({
type: "enum",
enum: DiscountConditionOperator,
})
operator: DiscountConditionOperator
@Index()
@Column()
discount_rule_id: string
@ManyToOne(() => DiscountRule, (dr) => dr.conditions)
@JoinColumn({ name: "discount_rule_id" })
discount_rule: DiscountRule
@ManyToMany(() => Product)
@JoinTable({
name: "discount_condition_product",
joinColumn: {
name: "condition_id",
referencedColumnName: "id",
},
inverseJoinColumn: {
name: "product_id",
referencedColumnName: "id",
},
})
products: Product[]
@ManyToMany(() => ProductType)
@JoinTable({
name: "discount_condition_product_type",
joinColumn: {
name: "condition_id",
referencedColumnName: "id",
},
inverseJoinColumn: {
name: "product_type_id",
referencedColumnName: "id",
},
})
product_types: ProductType[]
@ManyToMany(() => ProductTag)
@JoinTable({
name: "discount_condition_product_tag",
joinColumn: {
name: "condition_id",
referencedColumnName: "id",
},
inverseJoinColumn: {
name: "product_tag_id",
referencedColumnName: "id",
},
})
product_tags: ProductTag[]
@ManyToMany(() => ProductCollection)
@JoinTable({
name: "discount_condition_product_collection",
joinColumn: {
name: "condition_id",
referencedColumnName: "id",
},
inverseJoinColumn: {
name: "product_collection_id",
referencedColumnName: "id",
},
})
product_collections: ProductCollection[]
@ManyToMany(() => CustomerGroup)
@JoinTable({
name: "discount_condition_customer_group",
joinColumn: {
name: "condition_id",
referencedColumnName: "id",
},
inverseJoinColumn: {
name: "customer_group_id",
referencedColumnName: "id",
},
})
customer_groups: CustomerGroup[]
@CreateDateColumn({ type: resolveDbType("timestamptz") })
created_at: Date
@UpdateDateColumn({ type: resolveDbType("timestamptz") })
updated_at: Date
@DeleteDateColumn({ type: resolveDbType("timestamptz") })
deleted_at: Date
@DbAwareColumn({ type: "jsonb", nullable: true })
metadata: any
@BeforeInsert()
private beforeInsert() {
const id = ulid()
this.id = `discon_${id}`
}
}
/**
* @schema discount_condition
* title: "Discount Condition"
* description: "Holds rule conditions for when a discount is applicable"
* x-resourceId: discount_condition
* properties:
* id:
* description: "The id of the Discount Condition. Will be prefixed by `discon_`."
* type: string
* type:
* description: "The type of the Condition"
* type: string
* enum:
* - products
* - product_types
* - product_collections
* - product_tags
* - customer_groups
* created_at:
* description: "The date with timezone at which the resource was created."
* type: string
* format: date-time
* update_at:
* description: "The date with timezone at which the resource was last updated."
* type: string
* format: date-time
* deleted_at:
* description: "The date with timezone at which the resource was deleted."
* type: string
* format: date-time
* metadata:
* description: "An optional key-value map with additional information."
* type: object
*/
+12 -26
View File
@@ -1,19 +1,16 @@
import {
Entity,
BeforeInsert,
CreateDateColumn,
UpdateDateColumn,
DeleteDateColumn,
Index,
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
OneToMany,
PrimaryColumn,
ManyToMany,
JoinTable,
UpdateDateColumn,
} from "typeorm"
import { ulid } from "ulid"
import { resolveDbType, DbAwareColumn } from "../utils/db-aware-column"
import { Product } from "./product"
import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column"
import { DiscountCondition } from "./discount-condition"
export enum DiscountRuleType {
FIXED = "fixed",
@@ -50,19 +47,8 @@ export class DiscountRule {
})
allocation: AllocationType
@ManyToMany(() => Product, { cascade: true })
@JoinTable({
name: "discount_rule_products",
joinColumn: {
name: "discount_rule_id",
referencedColumnName: "id",
},
inverseJoinColumn: {
name: "product_id",
referencedColumnName: "id",
},
})
valid_for: Product[]
@OneToMany(() => DiscountCondition, (conditions) => conditions.discount_rule)
conditions: DiscountCondition[]
@CreateDateColumn({ type: resolveDbType("timestamptz") })
created_at: Date
@@ -111,11 +97,11 @@ export class DiscountRule {
* enum:
* - total
* - item
* valid_for:
* description: "A set of Products that the discount can be used for."
* conditions:
* description: "A set of conditions that can be used to limit when the discount can be used"
* type: array
* items:
* $ref: "#/components/schemas/product"
* $ref: "#/components/schemas/discount_condition"
* created_at:
* description: "The date with timezone at which the resource was created."
* type: string
+9 -11
View File
@@ -1,21 +1,19 @@
import {
Entity,
BeforeInsert,
DeleteDateColumn,
CreateDateColumn,
UpdateDateColumn,
Index,
Column,
PrimaryColumn,
CreateDateColumn,
DeleteDateColumn,
Entity,
Index,
JoinColumn,
JoinTable,
ManyToMany,
ManyToOne,
OneToOne,
JoinTable,
JoinColumn,
PrimaryColumn,
UpdateDateColumn,
} from "typeorm"
import { ulid } from "ulid"
import { resolveDbType, DbAwareColumn } from "../utils/db-aware-column"
import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column"
import { DiscountRule } from "./discount-rule"
import { Region } from "./region"