feat(medusa): Add endpoints specific to DiscountConditions (#1355)
This commit is contained in:
@@ -14,3 +14,128 @@ Object {
|
||||
"type": "duplicate_error",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`/admin/discounts POST /admin/discounts/:id/conditions fails if more than one condition type is provided: Only one of products, customer_groups is allowed, Only one of customer_groups, products is allowed 1`] = `"Request failed with status code 400"`;
|
||||
|
||||
exports[`/admin/discounts POST /admin/discounts/:id/conditions should create a condition 1`] = `
|
||||
Object {
|
||||
"code": "TEST",
|
||||
"created_at": Any<String>,
|
||||
"deleted_at": null,
|
||||
"ends_at": null,
|
||||
"id": "test-discount",
|
||||
"is_disabled": false,
|
||||
"is_dynamic": false,
|
||||
"metadata": null,
|
||||
"parent_discount": null,
|
||||
"parent_discount_id": null,
|
||||
"regions": Array [],
|
||||
"rule": Object {
|
||||
"allocation": "total",
|
||||
"conditions": Array [
|
||||
Object {
|
||||
"created_at": Any<String>,
|
||||
"deleted_at": null,
|
||||
"discount_rule_id": Any<String>,
|
||||
"id": Any<String>,
|
||||
"metadata": null,
|
||||
"operator": "in",
|
||||
"type": "products",
|
||||
"updated_at": Any<String>,
|
||||
},
|
||||
],
|
||||
"created_at": Any<String>,
|
||||
"deleted_at": null,
|
||||
"description": null,
|
||||
"id": Any<String>,
|
||||
"metadata": null,
|
||||
"type": "percentage",
|
||||
"updated_at": Any<String>,
|
||||
"value": 10,
|
||||
},
|
||||
"rule_id": Any<String>,
|
||||
"starts_at": Any<String>,
|
||||
"updated_at": Any<String>,
|
||||
"usage_count": 0,
|
||||
"usage_limit": null,
|
||||
"valid_duration": null,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`/admin/discounts POST /admin/discounts/:id/conditions throws if discount does not exist: Discount with id does-not-exist was not found 1`] = `"Request failed with status code 404"`;
|
||||
|
||||
exports[`/admin/discounts POST /admin/discounts/:id/conditions/:condition_id should update a condition 1`] = `
|
||||
Object {
|
||||
"code": "TEST",
|
||||
"created_at": Any<String>,
|
||||
"deleted_at": null,
|
||||
"ends_at": null,
|
||||
"id": "test-discount",
|
||||
"is_disabled": false,
|
||||
"is_dynamic": false,
|
||||
"metadata": null,
|
||||
"parent_discount_id": null,
|
||||
"rule": Object {
|
||||
"allocation": "total",
|
||||
"conditions": Array [
|
||||
Object {
|
||||
"created_at": Any<String>,
|
||||
"deleted_at": null,
|
||||
"discount_rule_id": Any<String>,
|
||||
"id": Any<String>,
|
||||
"metadata": null,
|
||||
"operator": "in",
|
||||
"products": Array [
|
||||
Object {
|
||||
"collection_id": null,
|
||||
"created_at": Any<String>,
|
||||
"deleted_at": null,
|
||||
"description": null,
|
||||
"discountable": true,
|
||||
"external_id": null,
|
||||
"handle": null,
|
||||
"height": null,
|
||||
"hs_code": null,
|
||||
"id": "test-product",
|
||||
"is_giftcard": false,
|
||||
"length": null,
|
||||
"material": null,
|
||||
"metadata": null,
|
||||
"mid_code": null,
|
||||
"origin_country": null,
|
||||
"profile_id": Any<String>,
|
||||
"status": "draft",
|
||||
"subtitle": null,
|
||||
"thumbnail": null,
|
||||
"title": "Practical Frozen Fish",
|
||||
"type_id": Any<String>,
|
||||
"updated_at": Any<String>,
|
||||
"weight": null,
|
||||
"width": null,
|
||||
},
|
||||
],
|
||||
"type": "products",
|
||||
"updated_at": Any<String>,
|
||||
},
|
||||
],
|
||||
"created_at": Any<String>,
|
||||
"deleted_at": null,
|
||||
"description": null,
|
||||
"id": Any<String>,
|
||||
"metadata": null,
|
||||
"type": "percentage",
|
||||
"updated_at": Any<String>,
|
||||
"value": 10,
|
||||
},
|
||||
"rule_id": Any<String>,
|
||||
"starts_at": Any<String>,
|
||||
"updated_at": Any<String>,
|
||||
"usage_count": 0,
|
||||
"usage_limit": null,
|
||||
"valid_duration": null,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`/admin/discounts POST /admin/discounts/:id/conditions/:condition_id throws if condition does not exist: DiscountCondition with id does-not-exist was not found for Discount test-discount 1`] = `"Request failed with status code 404"`;
|
||||
|
||||
exports[`/admin/discounts POST /admin/discounts/:id/conditions/:condition_id throws if discount does not exist: Discount with id does-not-exist was not found 1`] = `"Request failed with status code 404"`;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const path = require("path")
|
||||
const {
|
||||
Region,
|
||||
DiscountRule,
|
||||
Discount,
|
||||
Customer,
|
||||
@@ -12,7 +11,6 @@ const { useApi } = require("../../../helpers/use-api")
|
||||
const { initDb, useDb } = require("../../../helpers/use-db")
|
||||
const adminSeeder = require("../../helpers/admin-seeder")
|
||||
const discountSeeder = require("../../helpers/discount-seeder")
|
||||
const { exportAllDeclaration } = require("@babel/types")
|
||||
const { simpleProductFactory } = require("../../factories")
|
||||
const {
|
||||
simpleDiscountFactory,
|
||||
@@ -433,7 +431,6 @@ describe("/admin/discounts", () => {
|
||||
await adminSeeder(dbConnection)
|
||||
await discountSeeder(dbConnection)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
throw err
|
||||
}
|
||||
})
|
||||
@@ -1468,41 +1465,37 @@ describe("/admin/discounts", () => {
|
||||
describe("POST /admin/discounts/:discount_id/dynamic-codes", () => {
|
||||
beforeEach(async () => {
|
||||
const manager = dbConnection.manager
|
||||
try {
|
||||
await adminSeeder(dbConnection)
|
||||
await manager.insert(DiscountRule, {
|
||||
id: "test-discount-rule",
|
||||
description: "Dynamic rule",
|
||||
type: "percentage",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
})
|
||||
await manager.insert(Discount, {
|
||||
id: "test-discount",
|
||||
code: "DYNAMIC",
|
||||
is_dynamic: true,
|
||||
is_disabled: false,
|
||||
rule_id: "test-discount-rule",
|
||||
valid_duration: "P2Y",
|
||||
})
|
||||
await manager.insert(DiscountRule, {
|
||||
id: "test-discount-rule1",
|
||||
description: "Dynamic rule",
|
||||
type: "percentage",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
})
|
||||
await manager.insert(Discount, {
|
||||
id: "test-discount1",
|
||||
code: "DYNAMICCode",
|
||||
is_dynamic: true,
|
||||
is_disabled: false,
|
||||
rule_id: "test-discount-rule1",
|
||||
})
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
throw err
|
||||
}
|
||||
|
||||
await adminSeeder(dbConnection)
|
||||
await manager.insert(DiscountRule, {
|
||||
id: "test-discount-rule",
|
||||
description: "Dynamic rule",
|
||||
type: "percentage",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
})
|
||||
await manager.insert(Discount, {
|
||||
id: "test-discount",
|
||||
code: "DYNAMIC",
|
||||
is_dynamic: true,
|
||||
is_disabled: false,
|
||||
rule_id: "test-discount-rule",
|
||||
valid_duration: "P2Y",
|
||||
})
|
||||
await manager.insert(DiscountRule, {
|
||||
id: "test-discount-rule1",
|
||||
description: "Dynamic rule",
|
||||
type: "percentage",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
})
|
||||
await manager.insert(Discount, {
|
||||
id: "test-discount1",
|
||||
code: "DYNAMICCode",
|
||||
is_dynamic: true,
|
||||
is_disabled: false,
|
||||
rule_id: "test-discount-rule1",
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
@@ -1554,7 +1547,7 @@ describe("/admin/discounts", () => {
|
||||
}
|
||||
)
|
||||
.catch((err) => {
|
||||
// console.log(err)
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
@@ -1566,4 +1559,477 @@ describe("/admin/discounts", () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("DELETE /admin/discounts/:id/conditions/:condition_id", () => {
|
||||
beforeEach(async () => {
|
||||
const manager = dbConnection.manager
|
||||
await adminSeeder(dbConnection)
|
||||
|
||||
await manager.insert(DiscountRule, {
|
||||
id: "test-discount-rule-fixed",
|
||||
description: "Test discount rule",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
})
|
||||
|
||||
const prod = await simpleProductFactory(dbConnection, { type: "pants" })
|
||||
|
||||
await simpleDiscountFactory(dbConnection, {
|
||||
id: "test-discount",
|
||||
code: "TEST",
|
||||
rule: {
|
||||
type: "percentage",
|
||||
value: "10",
|
||||
allocation: "total",
|
||||
conditions: [
|
||||
{
|
||||
type: "products",
|
||||
operator: "in",
|
||||
products: [prod.id],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
await db.teardown()
|
||||
})
|
||||
|
||||
it("should delete condition", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const group = await dbConnection.manager.insert(CustomerGroup, {
|
||||
id: "customer-group-1",
|
||||
name: "vip-customers",
|
||||
})
|
||||
|
||||
await dbConnection.manager.insert(Customer, {
|
||||
id: "cus_1234",
|
||||
email: "oli@email.com",
|
||||
groups: [group],
|
||||
})
|
||||
|
||||
await simpleDiscountFactory(dbConnection, {
|
||||
id: "test-discount",
|
||||
code: "TEST",
|
||||
rule: {
|
||||
type: "percentage",
|
||||
value: "10",
|
||||
allocation: "total",
|
||||
conditions: [
|
||||
{
|
||||
id: "test-condition",
|
||||
type: "customer_groups",
|
||||
operator: "in",
|
||||
customer_groups: ["customer-group-1"],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const response = await api
|
||||
.delete("/admin/discounts/test-discount/conditions/test-condition", {
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
const disc = response.data
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(disc).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "test-condition",
|
||||
deleted: true,
|
||||
object: "discount-condition",
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should not fail if condition does not exist", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const response = await api
|
||||
.delete("/admin/discounts/test-discount/conditions/test-condition", {
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
const disc = response.data
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(disc).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "test-condition",
|
||||
deleted: true,
|
||||
object: "discount-condition",
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail if discount does not exist", async () => {
|
||||
const api = useApi()
|
||||
|
||||
try {
|
||||
await api.delete(
|
||||
"/admin/discounts/not-exist/conditions/test-condition",
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
expect(error.message).toMatchSnapshot(
|
||||
"Discount with id not-exist was not found"
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /admin/discounts/:id/conditions", () => {
|
||||
beforeEach(async () => {
|
||||
const manager = dbConnection.manager
|
||||
await adminSeeder(dbConnection)
|
||||
|
||||
await manager.insert(DiscountRule, {
|
||||
id: "test-discount-rule-fixed",
|
||||
description: "Test discount rule",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
})
|
||||
|
||||
await simpleDiscountFactory(dbConnection, {
|
||||
id: "test-discount",
|
||||
code: "TEST",
|
||||
rule: {
|
||||
type: "percentage",
|
||||
value: "10",
|
||||
allocation: "total",
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
await db.teardown()
|
||||
})
|
||||
|
||||
it("should create a condition", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const prod = await simpleProductFactory(dbConnection, { type: "pants" })
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
"/admin/discounts/test-discount/conditions",
|
||||
{
|
||||
operator: "in",
|
||||
products: [prod.id],
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
const disc = response.data.discount
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(disc).toMatchSnapshot({
|
||||
id: "test-discount",
|
||||
code: "TEST",
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
rule_id: expect.any(String),
|
||||
starts_at: expect.any(String),
|
||||
rule: {
|
||||
id: expect.any(String),
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
conditions: [
|
||||
{
|
||||
id: expect.any(String),
|
||||
discount_rule_id: expect.any(String),
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("fails if more than one condition type is provided", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const prod = await simpleProductFactory(dbConnection, { type: "pants" })
|
||||
|
||||
const group = await dbConnection.manager.insert(CustomerGroup, {
|
||||
id: "customer-group-1",
|
||||
name: "vip-customers",
|
||||
})
|
||||
|
||||
await dbConnection.manager.insert(Customer, {
|
||||
id: "cus_1234",
|
||||
email: "oli@email.com",
|
||||
groups: [group],
|
||||
})
|
||||
|
||||
try {
|
||||
await api.post(
|
||||
"/admin/discounts/test-discount/conditions",
|
||||
{
|
||||
operator: "in",
|
||||
products: [prod.id],
|
||||
customer_groups: ["customer-group-1"],
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
expect(error.message).toMatchSnapshot(
|
||||
"Only one of products, customer_groups is allowed, Only one of customer_groups, products is allowed"
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("throws if discount does not exist", async () => {
|
||||
expect.assertions(1)
|
||||
|
||||
const api = useApi()
|
||||
|
||||
const prod2 = await simpleProductFactory(dbConnection, { type: "pants" })
|
||||
|
||||
try {
|
||||
await api.post(
|
||||
"/admin/discounts/does-not-exist/conditions/test-condition",
|
||||
{
|
||||
products: [prod2.id],
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
expect(error.message).toMatchSnapshot(
|
||||
"Discount with id does-not-exist was not found"
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /admin/discounts/:id/conditions/:condition_id", () => {
|
||||
beforeEach(async () => {
|
||||
const manager = dbConnection.manager
|
||||
await adminSeeder(dbConnection)
|
||||
|
||||
await manager.insert(DiscountRule, {
|
||||
id: "test-discount-rule-fixed",
|
||||
description: "Test discount rule",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
})
|
||||
|
||||
const prod = await simpleProductFactory(dbConnection, { type: "pants" })
|
||||
|
||||
await simpleDiscountFactory(dbConnection, {
|
||||
id: "test-discount",
|
||||
code: "TEST",
|
||||
rule: {
|
||||
type: "percentage",
|
||||
value: "10",
|
||||
allocation: "total",
|
||||
conditions: [
|
||||
{
|
||||
id: "test-condition",
|
||||
type: "products",
|
||||
operator: "in",
|
||||
products: [prod.id],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
await simpleDiscountFactory(dbConnection, {
|
||||
id: "test-discount-2",
|
||||
code: "TEST2",
|
||||
rule: {
|
||||
type: "percentage",
|
||||
value: "10",
|
||||
allocation: "total",
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
await db.teardown()
|
||||
})
|
||||
|
||||
it("should update a condition", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const prod2 = await simpleProductFactory(dbConnection, {
|
||||
id: "test-product",
|
||||
type: "pants",
|
||||
})
|
||||
|
||||
const discount = await api
|
||||
.get("/admin/discounts/test-discount", {
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
const cond = discount.data.discount.rule.conditions[0]
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
`/admin/discounts/test-discount/conditions/${cond.id}?expand=rule,rule.conditions,rule.conditions.products`,
|
||||
{
|
||||
products: [prod2.id],
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
const disc = response.data.discount
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(disc).toMatchSnapshot({
|
||||
id: "test-discount",
|
||||
code: "TEST",
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
rule_id: expect.any(String),
|
||||
starts_at: expect.any(String),
|
||||
rule: {
|
||||
id: expect.any(String),
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
conditions: [
|
||||
{
|
||||
id: expect.any(String),
|
||||
discount_rule_id: expect.any(String),
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
products: [
|
||||
{
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
profile_id: expect.any(String),
|
||||
type_id: expect.any(String),
|
||||
id: "test-product",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("throws if condition does not exist", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const prod2 = await simpleProductFactory(dbConnection, { type: "pants" })
|
||||
|
||||
try {
|
||||
await api.post(
|
||||
"/admin/discounts/test-discount/conditions/does-not-exist",
|
||||
{
|
||||
products: [prod2.id],
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
expect(error.message).toMatchSnapshot(
|
||||
"DiscountCondition with id does-not-exist was not found for Discount test-discount"
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("throws if discount does not exist", async () => {
|
||||
expect.assertions(1)
|
||||
|
||||
const api = useApi()
|
||||
|
||||
const prod2 = await simpleProductFactory(dbConnection, { type: "pants" })
|
||||
|
||||
try {
|
||||
await api.post(
|
||||
"/admin/discounts/does-not-exist/conditions/test-condition",
|
||||
{
|
||||
products: [prod2.id],
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
expect(error.message).toMatchSnapshot(
|
||||
"Discount with id does-not-exist was not found"
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("throws if condition does not belong to discount", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const prod2 = await simpleProductFactory(dbConnection, { type: "pants" })
|
||||
|
||||
try {
|
||||
await api.post(
|
||||
"/admin/discounts/test-discount-2/conditions/test-condition",
|
||||
{
|
||||
products: [prod2.id],
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
expect(error.message).toMatchSnapshot(
|
||||
"DiscountCondition with id test-condition was not found for Discount test-discount-2"
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@ import faker from "faker"
|
||||
import { Connection } from "typeorm"
|
||||
|
||||
export type DiscuntConditionFactoryData = {
|
||||
id?: string
|
||||
rule_id: string
|
||||
type: DiscountConditionType
|
||||
operator: DiscountConditionOperator
|
||||
@@ -93,11 +94,17 @@ export const simpleDiscountConditionFactory = async (
|
||||
resources = data.customer_groups
|
||||
}
|
||||
|
||||
const condToSave = manager.create(DiscountCondition, {
|
||||
const toCreate = {
|
||||
type: data.type,
|
||||
operator: data.operator,
|
||||
discount_rule_id: data.rule_id,
|
||||
})
|
||||
}
|
||||
|
||||
if (data.id) {
|
||||
toCreate["id"] = data.id
|
||||
}
|
||||
|
||||
const condToSave = manager.create(DiscountCondition, toCreate)
|
||||
|
||||
const { conditionTable, resourceKey } = getJoinTableResourceIdentifiers(
|
||||
data.type
|
||||
|
||||
@@ -2,8 +2,11 @@ import {
|
||||
AdminDiscountsDeleteRes,
|
||||
AdminDiscountsListRes,
|
||||
AdminDiscountsRes,
|
||||
AdminGetDiscountParams,
|
||||
AdminGetDiscountsParams,
|
||||
AdminPostDiscountsDiscountConditions,
|
||||
AdminPostDiscountsDiscountConditionsCondition,
|
||||
AdminPostDiscountsDiscountConditionsConditionParams,
|
||||
AdminPostDiscountsDiscountConditionsParams,
|
||||
AdminPostDiscountsDiscountDynamicCodesReq,
|
||||
AdminPostDiscountsDiscountReq,
|
||||
AdminPostDiscountsReq,
|
||||
@@ -133,6 +136,57 @@ class AdminDiscountsResource extends BaseResource {
|
||||
const path = `/admin/discounts/${id}/regions/${regionId}`
|
||||
return this.client.request("DELETE", path, {}, {}, customHeaders)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description creates a discount condition
|
||||
*/
|
||||
createCondition(
|
||||
discountId: string,
|
||||
payload: AdminPostDiscountsDiscountConditions,
|
||||
query: AdminPostDiscountsDiscountConditionsParams = {},
|
||||
customHeaders: Record<string, any> = {}
|
||||
): ResponsePromise<AdminDiscountsRes> {
|
||||
let path = `/admin/discounts/${discountId}/conditions`
|
||||
|
||||
if (query) {
|
||||
const queryString = qs.stringify(query)
|
||||
path = `/admin/discounts/${discountId}/conditions?${queryString}`
|
||||
}
|
||||
|
||||
return this.client.request("POST", path, payload, {}, customHeaders)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Updates a discount condition
|
||||
*/
|
||||
updateCondition(
|
||||
discountId: string,
|
||||
conditionId: string,
|
||||
payload: AdminPostDiscountsDiscountConditionsCondition,
|
||||
query: AdminPostDiscountsDiscountConditionsConditionParams = {},
|
||||
customHeaders: Record<string, any> = {}
|
||||
): ResponsePromise<AdminDiscountsRes> {
|
||||
let path = `/admin/discounts/${discountId}/conditions/${conditionId}`
|
||||
|
||||
if (query) {
|
||||
const queryString = qs.stringify(query)
|
||||
path = `/admin/discounts/${discountId}/conditions/${conditionId}?${queryString}`
|
||||
}
|
||||
|
||||
return this.client.request("POST", path, payload, {}, customHeaders)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Removes a condition from a discount
|
||||
*/
|
||||
deleteCondition(
|
||||
discountId: string,
|
||||
conditionId: string,
|
||||
customHeaders: Record<string, any> = {}
|
||||
): ResponsePromise<AdminDiscountsDeleteRes> {
|
||||
const path = `/admin/discounts/${discountId}/conditions/${conditionId}`
|
||||
return this.client.request("DELETE", path, {}, {}, customHeaders)
|
||||
}
|
||||
}
|
||||
|
||||
export default AdminDiscountsResource
|
||||
|
||||
@@ -478,7 +478,14 @@
|
||||
"created_at": "2021-03-16T21:24:16.872Z",
|
||||
"updated_at": "2021-03-16T21:24:16.872Z",
|
||||
"deleted_at": null,
|
||||
"metadata": null
|
||||
"metadata": null,
|
||||
"conditions": [
|
||||
{
|
||||
"id": "cnd_01F0YESMVJXKX8XZ6Q9Z6Q6Z6",
|
||||
"type": "products",
|
||||
"operator": "in"
|
||||
}
|
||||
]
|
||||
},
|
||||
"is_disabled": false,
|
||||
"parent_discount_id": null,
|
||||
|
||||
@@ -762,6 +762,43 @@ export const adminHandlers = [
|
||||
)
|
||||
}),
|
||||
|
||||
rest.post("/admin/discounts/:id/conditions", (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
discount: {
|
||||
...fixtures.get("discount"),
|
||||
},
|
||||
})
|
||||
)
|
||||
}),
|
||||
|
||||
rest.post("/admin/discounts/:id/conditions/:conditionId", (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
discount: {
|
||||
...fixtures.get("discount"),
|
||||
},
|
||||
})
|
||||
)
|
||||
}),
|
||||
|
||||
rest.delete(
|
||||
"/admin/discounts/:id/conditions/:conditionId",
|
||||
(req, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
id: req.params.conditionId,
|
||||
object: "discount-condition",
|
||||
deleted: true,
|
||||
discount: fixtures.get("discount"),
|
||||
})
|
||||
)
|
||||
}
|
||||
),
|
||||
|
||||
rest.get("/admin/draft-orders/", (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import {
|
||||
AdminDiscountsDeleteRes,
|
||||
AdminDiscountsRes,
|
||||
AdminPostDiscountsDiscountConditions,
|
||||
AdminPostDiscountsDiscountConditionsCondition,
|
||||
AdminPostDiscountsDiscountDynamicCodesReq,
|
||||
AdminPostDiscountsDiscountReq,
|
||||
AdminPostDiscountsReq,
|
||||
AdminPostDiscountsReq
|
||||
} from "@medusajs/medusa"
|
||||
import { Response } from "@medusajs/medusa-js"
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from "react-query"
|
||||
@@ -115,3 +117,51 @@ export const useAdminDeleteDynamicDiscountCode = (
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export const useAdminDiscountCreateCondition = (
|
||||
discountId: string,
|
||||
options?: UseMutationOptions<
|
||||
Response<AdminDiscountsRes>,
|
||||
Error,
|
||||
AdminPostDiscountsDiscountConditions
|
||||
>
|
||||
) => {
|
||||
const { client } = useMedusa()
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation(
|
||||
(payload: AdminPostDiscountsDiscountConditions) =>
|
||||
client.admin.discounts.createCondition(discountId, payload),
|
||||
buildOptions(queryClient, adminDiscountKeys.detail(discountId), options)
|
||||
)
|
||||
}
|
||||
|
||||
export const useAdminDiscountUpdateCondition = (
|
||||
discountId: string,
|
||||
conditionId: string,
|
||||
options?: UseMutationOptions<
|
||||
Response<AdminDiscountsRes>,
|
||||
Error,
|
||||
AdminPostDiscountsDiscountConditionsCondition
|
||||
>
|
||||
) => {
|
||||
const { client } = useMedusa()
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation(
|
||||
(payload: AdminPostDiscountsDiscountConditionsCondition) =>
|
||||
client.admin.discounts.updateCondition(discountId, conditionId, payload),
|
||||
buildOptions(queryClient, adminDiscountKeys.detail(discountId), options)
|
||||
)
|
||||
}
|
||||
|
||||
export const useAdminDiscountRemoveCondition = (
|
||||
discountId: string,
|
||||
options?: UseMutationOptions<Response<AdminDiscountsDeleteRes>, Error, string>
|
||||
) => {
|
||||
const { client } = useMedusa()
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation(
|
||||
(conditionId: string) =>
|
||||
client.admin.discounts.deleteCondition(discountId, conditionId),
|
||||
buildOptions(queryClient, adminDiscountKeys.detail(discountId), options)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DiscountConditionOperator } from "@medusajs/medusa"
|
||||
import { renderHook } from "@testing-library/react-hooks"
|
||||
import { fixtures } from "../../../../mocks/data"
|
||||
import {
|
||||
@@ -6,7 +7,10 @@ import {
|
||||
useAdminDeleteDiscount,
|
||||
useAdminDeleteDynamicDiscountCode,
|
||||
useAdminDiscountAddRegion,
|
||||
useAdminDiscountCreateCondition,
|
||||
useAdminDiscountRemoveCondition,
|
||||
useAdminDiscountRemoveRegion,
|
||||
useAdminDiscountUpdateCondition,
|
||||
useAdminUpdateDiscount,
|
||||
} from "../../../../src/"
|
||||
import { createWrapper } from "../../../utils"
|
||||
@@ -184,3 +188,68 @@ describe("useAdminDeleteDynamicDiscountCode hook", () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("useAdminDiscountCreateCondition hook", () => {
|
||||
test("Creates a condition from a discount", async () => {
|
||||
const id = fixtures.get("discount").id
|
||||
const conditionId = fixtures.get("discount").rule.conditions[0].id
|
||||
|
||||
const { result, waitFor } = renderHook(
|
||||
() => useAdminDiscountCreateCondition(id),
|
||||
{
|
||||
wrapper: createWrapper(),
|
||||
}
|
||||
)
|
||||
|
||||
result.current.mutate({
|
||||
operator: DiscountConditionOperator.IN,
|
||||
products: ["test-product"],
|
||||
})
|
||||
|
||||
await waitFor(() => result.current.isSuccess)
|
||||
|
||||
expect(result.current.data.response.status).toEqual(200)
|
||||
expect(result.current.data.discount).toEqual(fixtures.get("discount"))
|
||||
})
|
||||
})
|
||||
describe("useAdminDiscountUpdateCondition hook", () => {
|
||||
test("Updates condition for discount", async () => {
|
||||
const id = fixtures.get("discount").id
|
||||
const conditionId = fixtures.get("discount").rule.conditions[0].id
|
||||
|
||||
const { result, waitFor } = renderHook(
|
||||
() => useAdminDiscountUpdateCondition(id, conditionId),
|
||||
{
|
||||
wrapper: createWrapper(),
|
||||
}
|
||||
)
|
||||
|
||||
result.current.mutate({ products: ["test-product"] })
|
||||
|
||||
await waitFor(() => result.current.isSuccess)
|
||||
|
||||
expect(result.current.data.response.status).toEqual(200)
|
||||
expect(result.current.data.discount).toEqual(fixtures.get("discount"))
|
||||
})
|
||||
})
|
||||
|
||||
describe("useAdminDiscountRemoveCondition hook", () => {
|
||||
test("removes a condition from a discount", async () => {
|
||||
const id = fixtures.get("discount").id
|
||||
const conditionId = fixtures.get("discount").rule.conditions[0].id
|
||||
|
||||
const { result, waitFor } = renderHook(
|
||||
() => useAdminDiscountRemoveCondition(id),
|
||||
{
|
||||
wrapper: createWrapper(),
|
||||
}
|
||||
)
|
||||
|
||||
result.current.mutate(conditionId)
|
||||
|
||||
await waitFor(() => result.current.isSuccess)
|
||||
|
||||
expect(result.current.data.response.status).toEqual(200)
|
||||
expect(result.current.data.id).toEqual(conditionId)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import { IsOptional, IsString } from "class-validator"
|
||||
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
|
||||
import { Discount } from "../../../../models/discount"
|
||||
import { DiscountConditionOperator } from "../../../../models/discount-condition"
|
||||
import { DiscountService } from "../../../../services"
|
||||
import DiscountConditionService from "../../../../services/discount-condition"
|
||||
import { AdminUpsertConditionsReq } from "../../../../types/discount"
|
||||
import { getRetrieveConfig } from "../../../../utils/get-query-config"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
/**
|
||||
* @oas [post] /discounts/{discount_id}/conditions
|
||||
* operationId: "PostDiscountsDiscountConditions"
|
||||
* summary: "Creates a DiscountCondition"
|
||||
* x-authenticated: true
|
||||
* parameters:
|
||||
* - (path) discount_id=* {string} The id of the Product.
|
||||
* - (query) expand {string} (Comma separated) Which fields should be expanded in each product of the result.
|
||||
* - (query) fields {string} (Comma separated) Which fields should be included in each product of the result.
|
||||
* description: "Creates a DiscountCondition"
|
||||
* requestBody:
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* operator:
|
||||
* description: Operator of the condition
|
||||
* type: string
|
||||
* items:
|
||||
* properties:
|
||||
* products:
|
||||
* type: array
|
||||
* description: list of products
|
||||
* product_types:
|
||||
* type: array
|
||||
* description: list of product types
|
||||
* product_collections:
|
||||
* type: array
|
||||
* description: list of product collections
|
||||
* product_tags:
|
||||
* type: array
|
||||
* description: list of product tags
|
||||
* customer_groups:
|
||||
* type: array
|
||||
* description: list of customer_groups
|
||||
* tags:
|
||||
* - Discount
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* discount:
|
||||
* $ref: "#/components/schemas/discount"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { discount_id } = req.params
|
||||
|
||||
const validatedCondition = await validator(
|
||||
AdminPostDiscountsDiscountConditions,
|
||||
req.body
|
||||
)
|
||||
|
||||
const validatedParams = await validator(
|
||||
AdminPostDiscountsDiscountConditionsParams,
|
||||
req.query
|
||||
)
|
||||
|
||||
const conditionService: DiscountConditionService = req.scope.resolve(
|
||||
"discountConditionService"
|
||||
)
|
||||
const discountService: DiscountService = req.scope.resolve("discountService")
|
||||
|
||||
let discount = await discountService.retrieve(discount_id)
|
||||
|
||||
await conditionService.upsertCondition({
|
||||
...validatedCondition,
|
||||
rule_id: discount.rule_id,
|
||||
})
|
||||
|
||||
const config = getRetrieveConfig<Discount>(
|
||||
defaultAdminDiscountsFields,
|
||||
defaultAdminDiscountsRelations,
|
||||
validatedParams?.fields?.split(",") as (keyof Discount)[],
|
||||
validatedParams?.expand?.split(",")
|
||||
)
|
||||
|
||||
discount = await discountService.retrieve(discount.id, config)
|
||||
|
||||
res.status(200).json({ discount })
|
||||
}
|
||||
|
||||
export class AdminPostDiscountsDiscountConditions extends AdminUpsertConditionsReq {
|
||||
@IsString()
|
||||
operator: DiscountConditionOperator
|
||||
}
|
||||
|
||||
export class AdminPostDiscountsDiscountConditionsParams {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
expand?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
fields?: string
|
||||
}
|
||||
@@ -83,8 +83,6 @@ import { AdminPostDiscountsDiscountParams } from "./update-discount"
|
||||
export default async (req, res) => {
|
||||
const validated = await validator(AdminPostDiscountsReq, req.body)
|
||||
|
||||
console.log(validated.rule.conditions)
|
||||
|
||||
const validatedParams = await validator(
|
||||
AdminPostDiscountsDiscountParams,
|
||||
req.query
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import { IsOptional, IsString } from "class-validator"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
|
||||
import { Discount } from "../../../../models"
|
||||
import { DiscountService } from "../../../../services"
|
||||
import DiscountConditionService from "../../../../services/discount-condition"
|
||||
import { getRetrieveConfig } from "../../../../utils/get-query-config"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
|
||||
/**
|
||||
* @oas [delete] /discounts/{discount_id}/conditions/{condition_id}
|
||||
* operationId: "DeleteDiscountsDiscountConditionsCondition"
|
||||
* summary: "Delete a DiscountCondition"
|
||||
* description: "Deletes a DiscountCondition"
|
||||
* x-authenticated: true
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Discount
|
||||
* - (path) condition_id=* {string} The id of the DiscountCondition
|
||||
* tags:
|
||||
* - Discount
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* id:
|
||||
* type: string
|
||||
* description: The id of the deleted DiscountCondition
|
||||
* object:
|
||||
* type: string
|
||||
* description: The type of the object that was deleted.
|
||||
* deleted:
|
||||
* type: boolean
|
||||
* discount:
|
||||
* type: object
|
||||
* description: The Discount to which the condition used to belong
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { discount_id, condition_id } = req.params
|
||||
|
||||
const validatedParams = await validator(
|
||||
AdminDeleteDiscountsDiscountConditionsConditionParams,
|
||||
req.query
|
||||
)
|
||||
|
||||
const conditionService: DiscountConditionService = req.scope.resolve(
|
||||
"discountConditionService"
|
||||
)
|
||||
|
||||
const condition = await conditionService
|
||||
.retrieve(condition_id)
|
||||
.catch(() => void 0)
|
||||
|
||||
if (!condition) {
|
||||
// resolves idempotently in case of non-existing condition
|
||||
return res.json({
|
||||
id: condition_id,
|
||||
object: "discount-condition",
|
||||
deleted: true,
|
||||
})
|
||||
}
|
||||
|
||||
const discountService: DiscountService = req.scope.resolve("discountService")
|
||||
|
||||
let discount = await discountService.retrieve(discount_id, {
|
||||
relations: ["rule", "rule.conditions"],
|
||||
})
|
||||
|
||||
const existsOnDiscount = discount.rule.conditions.some(
|
||||
(c) => c.id === condition_id
|
||||
)
|
||||
|
||||
if (!existsOnDiscount) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Condition with id ${condition_id} does not belong to Discount with id ${discount_id}`
|
||||
)
|
||||
}
|
||||
|
||||
await conditionService.delete(condition_id)
|
||||
|
||||
const config = getRetrieveConfig<Discount>(
|
||||
defaultAdminDiscountsFields,
|
||||
defaultAdminDiscountsRelations,
|
||||
validatedParams?.fields?.split(",") as (keyof Discount)[],
|
||||
validatedParams?.expand?.split(",")
|
||||
)
|
||||
|
||||
discount = await discountService.retrieve(discount_id, config)
|
||||
|
||||
res.json({
|
||||
id: condition_id,
|
||||
object: "discount-condition",
|
||||
deleted: true,
|
||||
discount,
|
||||
})
|
||||
}
|
||||
|
||||
export class AdminDeleteDiscountsDiscountConditionsConditionParams {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
expand?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
fields?: string
|
||||
}
|
||||
@@ -49,6 +49,20 @@ export default (app) => {
|
||||
middlewares.wrap(require("./remove-region").default)
|
||||
)
|
||||
|
||||
// Discount condition management
|
||||
route.post(
|
||||
"/:discount_id/conditions/:condition_id",
|
||||
middlewares.wrap(require("./update-condition").default)
|
||||
)
|
||||
route.post(
|
||||
"/:discount_id/conditions",
|
||||
middlewares.wrap(require("./create-condition").default)
|
||||
)
|
||||
route.delete(
|
||||
"/:discount_id/conditions/:condition_id",
|
||||
middlewares.wrap(require("./delete-condition").default)
|
||||
)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
@@ -88,12 +102,15 @@ export type AdminDiscountsListRes = PaginatedResponse & {
|
||||
}
|
||||
|
||||
export * from "./add-region"
|
||||
export * from "./create-condition"
|
||||
export * from "./create-discount"
|
||||
export * from "./create-dynamic-code"
|
||||
export * from "./delete-condition"
|
||||
export * from "./delete-discount"
|
||||
export * from "./delete-dynamic-code"
|
||||
export * from "./get-discount"
|
||||
export * from "./get-discount-by-code"
|
||||
export * from "./list-discounts"
|
||||
export * from "./remove-region"
|
||||
export * from "./update-condition"
|
||||
export * from "./update-discount"
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import { IsOptional, IsString } from "class-validator"
|
||||
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
|
||||
import { Discount } from "../../../../models/discount"
|
||||
import { DiscountService } from "../../../../services"
|
||||
import DiscountConditionService from "../../../../services/discount-condition"
|
||||
import { AdminUpsertConditionsReq } from "../../../../types/discount"
|
||||
import { getRetrieveConfig } from "../../../../utils/get-query-config"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
/**
|
||||
* @oas [post] /discounts/{discount_id}/conditions/{condition_id}
|
||||
* operationId: "PostDiscountsDiscountConditionsCondition"
|
||||
* summary: "Updates a DiscountCondition"
|
||||
* x-authenticated: true
|
||||
* parameters:
|
||||
* - (path) discount_id=* {string} The id of the Product.
|
||||
* - (query) expand {string} (Comma separated) Which fields should be expanded in each product of the result.
|
||||
* - (query) fields {string} (Comma separated) Which fields should be included in each product of the result.
|
||||
* description: "Updates a DiscountCondition"
|
||||
* requestBody:
|
||||
* content:
|
||||
* application/json:
|
||||
* required:
|
||||
* - id
|
||||
* schema:
|
||||
* properties:
|
||||
* items:
|
||||
* properties:
|
||||
* products:
|
||||
* type: array
|
||||
* description: list of products
|
||||
* product_types:
|
||||
* type: array
|
||||
* description: list of product types
|
||||
* product_collections:
|
||||
* type: array
|
||||
* description: list of product collections
|
||||
* product_tags:
|
||||
* type: array
|
||||
* description: list of product tags
|
||||
* customer_groups:
|
||||
* type: array
|
||||
* description: list of customer_groups
|
||||
* tags:
|
||||
* - Discount
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* discount:
|
||||
* $ref: "#/components/schemas/discount"
|
||||
*/
|
||||
|
||||
export default async (req, res) => {
|
||||
const { discount_id, condition_id } = req.params
|
||||
|
||||
const validatedCondition = await validator(
|
||||
AdminPostDiscountsDiscountConditionsCondition,
|
||||
req.body
|
||||
)
|
||||
|
||||
const validatedParams = await validator(
|
||||
AdminPostDiscountsDiscountConditionsConditionParams,
|
||||
req.query
|
||||
)
|
||||
|
||||
const conditionService: DiscountConditionService = req.scope.resolve(
|
||||
"discountConditionService"
|
||||
)
|
||||
|
||||
const condition = await conditionService.retrieve(condition_id)
|
||||
|
||||
const discountService: DiscountService = req.scope.resolve("discountService")
|
||||
|
||||
let discount = await discountService.retrieve(discount_id)
|
||||
|
||||
const updateObj = {
|
||||
...validatedCondition,
|
||||
rule_id: discount.rule_id,
|
||||
id: condition.id,
|
||||
}
|
||||
|
||||
await conditionService.upsertCondition(updateObj)
|
||||
|
||||
const config = getRetrieveConfig<Discount>(
|
||||
defaultAdminDiscountsFields,
|
||||
defaultAdminDiscountsRelations,
|
||||
validatedParams?.fields?.split(",") as (keyof Discount)[],
|
||||
validatedParams?.expand?.split(",")
|
||||
)
|
||||
|
||||
discount = await discountService.retrieve(discount.id, config)
|
||||
|
||||
res.status(200).json({ discount })
|
||||
}
|
||||
|
||||
export class AdminPostDiscountsDiscountConditionsCondition extends AdminUpsertConditionsReq {}
|
||||
|
||||
export class AdminPostDiscountsDiscountConditionsConditionParams {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
expand?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
fields?: string
|
||||
}
|
||||
@@ -145,6 +145,10 @@ export class DiscountCondition {
|
||||
|
||||
@BeforeInsert()
|
||||
private beforeInsert() {
|
||||
if (this.id) {
|
||||
return
|
||||
}
|
||||
|
||||
const id = ulid()
|
||||
this.id = `discon_${id}`
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
Not,
|
||||
Repository,
|
||||
} from "typeorm"
|
||||
import { Discount } from "../models"
|
||||
import {
|
||||
DiscountCondition,
|
||||
DiscountConditionOperator,
|
||||
@@ -35,6 +36,21 @@ type DiscountConditionResourceType = EntityTarget<
|
||||
|
||||
@EntityRepository(DiscountCondition)
|
||||
export class DiscountConditionRepository extends Repository<DiscountCondition> {
|
||||
async findOneWithDiscount(
|
||||
conditionId: string,
|
||||
discountId: string
|
||||
): Promise<(DiscountCondition & { discount: Discount }) | undefined> {
|
||||
return (await this.createQueryBuilder("condition")
|
||||
.leftJoinAndMapOne(
|
||||
"condition.discount",
|
||||
Discount,
|
||||
"discount",
|
||||
`condition.discount_rule_id = discount.rule_id and discount.id = :discId and condition.id = :dcId`,
|
||||
{ discId: discountId, dcId: conditionId }
|
||||
)
|
||||
.getOne()) as (DiscountCondition & { discount: Discount }) | undefined
|
||||
}
|
||||
|
||||
getJoinTableResourceIdentifiers(type: string): {
|
||||
joinTable: string
|
||||
resourceKey: string
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { flatten, groupBy, map, merge } from "lodash"
|
||||
import { EntityRepository, Repository, FindManyOptions } from "typeorm"
|
||||
import { EntityRepository, FindManyOptions, Repository } from "typeorm"
|
||||
import { Order } from "../models/order"
|
||||
|
||||
@EntityRepository(Order)
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { EventBusService } from "."
|
||||
import { DiscountCondition, DiscountConditionType } from "../models"
|
||||
import { DiscountConditionRepository } from "../repositories/discount-condition"
|
||||
import { FindConfig } from "../types/common"
|
||||
import { UpsertDiscountConditionInput } from "../types/discount"
|
||||
import { PostgresError } from "../utils/exception-formatter"
|
||||
|
||||
/**
|
||||
* Provides layer to manipulate discount conditions.
|
||||
* @implements {BaseService}
|
||||
*/
|
||||
class DiscountConditionService extends BaseService {
|
||||
protected readonly manager_: EntityManager
|
||||
protected readonly discountConditionRepository_: typeof DiscountConditionRepository
|
||||
protected readonly eventBus_: EventBusService
|
||||
protected transactionManager_?: EntityManager
|
||||
|
||||
constructor({ manager, discountConditionRepository, eventBusService }) {
|
||||
super()
|
||||
|
||||
this.manager_ = manager
|
||||
this.discountConditionRepository_ = discountConditionRepository
|
||||
this.eventBus_ = eventBusService
|
||||
}
|
||||
|
||||
withTransaction(transactionManager: EntityManager): DiscountConditionService {
|
||||
if (!transactionManager) {
|
||||
return this
|
||||
}
|
||||
|
||||
const cloned = new DiscountConditionService({
|
||||
manager: transactionManager,
|
||||
discountConditionRepository: this.discountConditionRepository_,
|
||||
eventBusService: this.eventBus_,
|
||||
})
|
||||
|
||||
cloned.transactionManager_ = transactionManager
|
||||
|
||||
return cloned
|
||||
}
|
||||
|
||||
async retrieve(
|
||||
conditionId: string,
|
||||
config?: FindConfig<DiscountCondition>
|
||||
): Promise<DiscountCondition | never> {
|
||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const conditionRepo = manager.getCustomRepository(
|
||||
this.discountConditionRepository_
|
||||
)
|
||||
|
||||
const query = this.buildQuery_({ id: conditionId }, config)
|
||||
|
||||
const condition = await conditionRepo.findOne(query)
|
||||
|
||||
if (!condition) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`DiscountCondition with id ${conditionId} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
return condition
|
||||
})
|
||||
}
|
||||
|
||||
protected static resolveConditionType_(data: UpsertDiscountConditionInput):
|
||||
| {
|
||||
type: DiscountConditionType
|
||||
resource_ids: string[]
|
||||
}
|
||||
| undefined {
|
||||
switch (true) {
|
||||
case !!data.products?.length:
|
||||
return {
|
||||
type: DiscountConditionType.PRODUCTS,
|
||||
resource_ids: data.products!,
|
||||
}
|
||||
case !!data.product_collections?.length:
|
||||
return {
|
||||
type: DiscountConditionType.PRODUCT_COLLECTIONS,
|
||||
resource_ids: data.product_collections!,
|
||||
}
|
||||
case !!data.product_types?.length:
|
||||
return {
|
||||
type: DiscountConditionType.PRODUCT_TYPES,
|
||||
resource_ids: data.product_types!,
|
||||
}
|
||||
case !!data.product_tags?.length:
|
||||
return {
|
||||
type: DiscountConditionType.PRODUCT_TAGS,
|
||||
resource_ids: data.product_tags!,
|
||||
}
|
||||
case !!data.customer_groups?.length:
|
||||
return {
|
||||
type: DiscountConditionType.CUSTOMER_GROUPS,
|
||||
resource_ids: data.customer_groups!,
|
||||
}
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
async upsertCondition(data: UpsertDiscountConditionInput): Promise<void> {
|
||||
let resolvedConditionType
|
||||
|
||||
return await this.atomicPhase_(
|
||||
async (manager: EntityManager) => {
|
||||
resolvedConditionType =
|
||||
DiscountConditionService.resolveConditionType_(data)
|
||||
|
||||
if (!resolvedConditionType) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Missing one of products, collections, tags, types or customer groups in data`
|
||||
)
|
||||
}
|
||||
|
||||
const discountConditionRepo: DiscountConditionRepository =
|
||||
manager.getCustomRepository(this.discountConditionRepository_)
|
||||
|
||||
if (data.id) {
|
||||
return await discountConditionRepo.addConditionResources(
|
||||
data.id,
|
||||
resolvedConditionType.resource_ids,
|
||||
resolvedConditionType.type,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
const created = discountConditionRepo.create({
|
||||
discount_rule_id: data.rule_id,
|
||||
operator: data.operator,
|
||||
type: resolvedConditionType.type,
|
||||
})
|
||||
|
||||
const discountCondition = await discountConditionRepo.save(created)
|
||||
|
||||
return await discountConditionRepo.addConditionResources(
|
||||
discountCondition.id,
|
||||
resolvedConditionType.resource_ids,
|
||||
resolvedConditionType.type
|
||||
)
|
||||
},
|
||||
async (err: { code: string }) => {
|
||||
if (err.code === PostgresError.DUPLICATE_ERROR) {
|
||||
// A unique key constraint failed meaning the combination of
|
||||
// discount rule id, type, and operator already exists in the db.
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.DUPLICATE_ERROR,
|
||||
`Discount Condition with operator '${data.operator}' and type '${resolvedConditionType?.type}' already exist on a Discount Rule`
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async delete(discountConditionId: string): Promise<DiscountCondition> {
|
||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const conditionRepo = manager.getCustomRepository(
|
||||
this.discountConditionRepository_
|
||||
)
|
||||
|
||||
const condition = await conditionRepo.findOne({
|
||||
where: { id: discountConditionId },
|
||||
})
|
||||
|
||||
if (!condition) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
return await conditionRepo.remove(condition)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default DiscountConditionService
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
} from "."
|
||||
import { Cart } from "../models/cart"
|
||||
import { Discount } from "../models/discount"
|
||||
import { DiscountConditionType } from "../models/discount-condition"
|
||||
import {
|
||||
AllocationType as DiscountAllocation,
|
||||
DiscountRule,
|
||||
@@ -28,10 +27,10 @@ import {
|
||||
CreateDynamicDiscountInput,
|
||||
FilterableDiscountProps,
|
||||
UpdateDiscountInput,
|
||||
UpsertDiscountConditionInput,
|
||||
} from "../types/discount"
|
||||
import { isFuture, isPast } from "../utils/date-helpers"
|
||||
import { formatException, PostgresError } from "../utils/exception-formatter"
|
||||
import { formatException } from "../utils/exception-formatter"
|
||||
import DiscountConditionService from "./discount-condition"
|
||||
|
||||
/**
|
||||
* Provides layer to manipulate discounts.
|
||||
@@ -43,6 +42,7 @@ class DiscountService extends BaseService {
|
||||
private discountRuleRepository_: typeof DiscountRuleRepository
|
||||
private giftCardRepository_: typeof GiftCardRepository
|
||||
private discountConditionRepository_: typeof DiscountConditionRepository
|
||||
private discountConditionService_: DiscountConditionService
|
||||
private totalsService_: TotalsService
|
||||
private productService_: ProductService
|
||||
private regionService_: RegionService
|
||||
@@ -54,6 +54,7 @@ class DiscountService extends BaseService {
|
||||
discountRuleRepository,
|
||||
giftCardRepository,
|
||||
discountConditionRepository,
|
||||
discountConditionService,
|
||||
totalsService,
|
||||
productService,
|
||||
regionService,
|
||||
@@ -77,6 +78,9 @@ class DiscountService extends BaseService {
|
||||
/** @private @const {DiscountConditionRepository} */
|
||||
this.discountConditionRepository_ = discountConditionRepository
|
||||
|
||||
/** @private @const {DiscountConditionRepository} */
|
||||
this.discountConditionService_ = discountConditionService
|
||||
|
||||
/** @private @const {TotalsService} */
|
||||
this.totalsService_ = totalsService
|
||||
|
||||
@@ -104,6 +108,7 @@ class DiscountService extends BaseService {
|
||||
discountRuleRepository: this.discountRuleRepository_,
|
||||
giftCardRepository: this.giftCardRepository_,
|
||||
discountConditionRepository: this.discountConditionRepository_,
|
||||
discountConditionService: this.discountConditionService_,
|
||||
totalsService: this.totalsService_,
|
||||
productService: this.productService_,
|
||||
regionService: this.regionService_,
|
||||
@@ -263,9 +268,13 @@ class DiscountService extends BaseService {
|
||||
const result = await discountRepo.save(created)
|
||||
|
||||
if (conditions?.length) {
|
||||
for (const cond of conditions) {
|
||||
await this.upsertDiscountCondition_(result.id, cond)
|
||||
}
|
||||
await Promise.all(
|
||||
conditions.map(async (cond) => {
|
||||
await this.discountConditionService_
|
||||
.withTransaction(manager)
|
||||
.upsertCondition({ rule_id: result.rule_id, ...cond })
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -382,9 +391,13 @@ class DiscountService extends BaseService {
|
||||
}
|
||||
|
||||
if (conditions?.length) {
|
||||
for (const cond of conditions) {
|
||||
await this.upsertDiscountCondition_(discount.id, cond)
|
||||
}
|
||||
await Promise.all(
|
||||
conditions.map(async (cond) => {
|
||||
await this.discountConditionService_
|
||||
.withTransaction(manager)
|
||||
.upsertCondition({ rule_id: discount.rule_id, ...cond })
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (regions) {
|
||||
@@ -573,103 +586,6 @@ class DiscountService extends BaseService {
|
||||
})
|
||||
}
|
||||
|
||||
resolveConditionType_(data: UpsertDiscountConditionInput):
|
||||
| {
|
||||
type: DiscountConditionType
|
||||
resource_ids: string[]
|
||||
}
|
||||
| undefined {
|
||||
switch (true) {
|
||||
case !!data.products?.length:
|
||||
return {
|
||||
type: DiscountConditionType.PRODUCTS,
|
||||
resource_ids: data.products!,
|
||||
}
|
||||
case !!data.product_collections?.length:
|
||||
return {
|
||||
type: DiscountConditionType.PRODUCT_COLLECTIONS,
|
||||
resource_ids: data.product_collections!,
|
||||
}
|
||||
case !!data.product_types?.length:
|
||||
return {
|
||||
type: DiscountConditionType.PRODUCT_TYPES,
|
||||
resource_ids: data.product_types!,
|
||||
}
|
||||
case !!data.product_tags?.length:
|
||||
return {
|
||||
type: DiscountConditionType.PRODUCT_TAGS,
|
||||
resource_ids: data.product_tags!,
|
||||
}
|
||||
case !!data.customer_groups?.length:
|
||||
return {
|
||||
type: DiscountConditionType.CUSTOMER_GROUPS,
|
||||
resource_ids: data.customer_groups!,
|
||||
}
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
async upsertDiscountCondition_(
|
||||
discountId: string,
|
||||
data: UpsertDiscountConditionInput
|
||||
): Promise<void> {
|
||||
const resolvedConditionType = this.resolveConditionType_(data)
|
||||
|
||||
const res = this.atomicPhase_(
|
||||
async (manager) => {
|
||||
const discountConditionRepo: DiscountConditionRepository =
|
||||
manager.getCustomRepository(this.discountConditionRepository_)
|
||||
|
||||
if (!resolvedConditionType) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Missing one of products, collections, tags, types or customer groups in data`
|
||||
)
|
||||
}
|
||||
|
||||
if (data.id) {
|
||||
return await discountConditionRepo.addConditionResources(
|
||||
data.id,
|
||||
resolvedConditionType.resource_ids,
|
||||
resolvedConditionType.type,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
const discount = await this.retrieve(discountId, {
|
||||
relations: ["rule", "rule.conditions"],
|
||||
})
|
||||
|
||||
const created = discountConditionRepo.create({
|
||||
discount_rule_id: discount.rule_id,
|
||||
operator: data.operator,
|
||||
type: resolvedConditionType.type,
|
||||
})
|
||||
|
||||
const discountCondition = await discountConditionRepo.save(created)
|
||||
|
||||
return await discountConditionRepo.addConditionResources(
|
||||
discountCondition.id,
|
||||
resolvedConditionType.resource_ids,
|
||||
resolvedConditionType.type
|
||||
)
|
||||
},
|
||||
async (err: { code: string }) => {
|
||||
if (err.code === PostgresError.DUPLICATE_ERROR) {
|
||||
// A unique key constraint failed meaning the combination of
|
||||
// discount rule id, type, and operator already exists in the db.
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.DUPLICATE_ERROR,
|
||||
`Discount Condition with operator '${data.operator}' and type '${resolvedConditionType?.type}' already exist on a Discount Rule`
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
async validateDiscountForProduct(
|
||||
discountRuleId: string,
|
||||
productId: string | undefined
|
||||
|
||||
@@ -672,7 +672,7 @@ class ProductService extends BaseService {
|
||||
)
|
||||
|
||||
if (!product) {
|
||||
return Promise.resolve()
|
||||
return
|
||||
}
|
||||
|
||||
await productRepo.softRemove(product)
|
||||
@@ -1069,22 +1069,25 @@ class ProductService extends BaseService {
|
||||
|
||||
const productArray = Array.isArray(products) ? products : [products]
|
||||
|
||||
const priceSelectionStrategy =
|
||||
this.priceSelectionStrategy_.withTransaction(manager)
|
||||
const priceSelectionStrategy = this.priceSelectionStrategy_.withTransaction(
|
||||
manager
|
||||
)
|
||||
|
||||
const productsWithPrices = await Promise.all(
|
||||
productArray.map(async (p) => {
|
||||
if (p.variants?.length) {
|
||||
p.variants = await Promise.all(
|
||||
p.variants.map(async (v) => {
|
||||
const prices =
|
||||
await priceSelectionStrategy.calculateVariantPrice(v.id, {
|
||||
const prices = await priceSelectionStrategy.calculateVariantPrice(
|
||||
v.id,
|
||||
{
|
||||
region_id: regionId,
|
||||
currency_code: currencyCode,
|
||||
cart_id: cart_id,
|
||||
customer_id: customer_id,
|
||||
include_discount_prices,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
...v,
|
||||
|
||||
@@ -105,6 +105,7 @@ export class AdminUpsertConditionsReq {
|
||||
}
|
||||
|
||||
export type UpsertDiscountConditionInput = {
|
||||
rule_id?: string
|
||||
id?: string
|
||||
operator?: DiscountConditionOperator
|
||||
products?: string[]
|
||||
|
||||
Reference in New Issue
Block a user