feat(medusa): Add endpoints specific to DiscountConditions (#1355)
This commit is contained in:
1 parent
a69b52e031
commit
9ca45ea492
20 files changed
+1403
-129
No files matched your search
@@ -14,3 +14,128 @@ Object {
|
|||||||
"type": "duplicate_error",
|
"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 path = require("path")
|
||||||
const {
|
const {
|
||||||
Region,
|
|
||||||
DiscountRule,
|
DiscountRule,
|
||||||
Discount,
|
Discount,
|
||||||
Customer,
|
Customer,
|
||||||
@@ -12,7 +11,6 @@ const { useApi } = require("../../../helpers/use-api")
|
|||||||
const { initDb, useDb } = require("../../../helpers/use-db")
|
const { initDb, useDb } = require("../../../helpers/use-db")
|
||||||
const adminSeeder = require("../../helpers/admin-seeder")
|
const adminSeeder = require("../../helpers/admin-seeder")
|
||||||
const discountSeeder = require("../../helpers/discount-seeder")
|
const discountSeeder = require("../../helpers/discount-seeder")
|
||||||
const { exportAllDeclaration } = require("@babel/types")
|
|
||||||
const { simpleProductFactory } = require("../../factories")
|
const { simpleProductFactory } = require("../../factories")
|
||||||
const {
|
const {
|
||||||
simpleDiscountFactory,
|
simpleDiscountFactory,
|
||||||
@@ -433,7 +431,6 @@ describe("/admin/discounts", () => {
|
|||||||
await adminSeeder(dbConnection)
|
await adminSeeder(dbConnection)
|
||||||
await discountSeeder(dbConnection)
|
await discountSeeder(dbConnection)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1468,7 +1465,7 @@ describe("/admin/discounts", () => {
|
|||||||
describe("POST /admin/discounts/:discount_id/dynamic-codes", () => {
|
describe("POST /admin/discounts/:discount_id/dynamic-codes", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const manager = dbConnection.manager
|
const manager = dbConnection.manager
|
||||||
try {
|
|
||||||
await adminSeeder(dbConnection)
|
await adminSeeder(dbConnection)
|
||||||
await manager.insert(DiscountRule, {
|
await manager.insert(DiscountRule, {
|
||||||
id: "test-discount-rule",
|
id: "test-discount-rule",
|
||||||
@@ -1499,10 +1496,6 @@ describe("/admin/discounts", () => {
|
|||||||
is_disabled: false,
|
is_disabled: false,
|
||||||
rule_id: "test-discount-rule1",
|
rule_id: "test-discount-rule1",
|
||||||
})
|
})
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@@ -1554,7 +1547,7 @@ describe("/admin/discounts", () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
// console.log(err)
|
console.log(err)
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(response.status).toEqual(200)
|
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"
|
import { Connection } from "typeorm"
|
||||||
|
|
||||||
export type DiscuntConditionFactoryData = {
|
export type DiscuntConditionFactoryData = {
|
||||||
|
id?: string
|
||||||
rule_id: string
|
rule_id: string
|
||||||
type: DiscountConditionType
|
type: DiscountConditionType
|
||||||
operator: DiscountConditionOperator
|
operator: DiscountConditionOperator
|
||||||
@@ -93,11 +94,17 @@ export const simpleDiscountConditionFactory = async (
|
|||||||
resources = data.customer_groups
|
resources = data.customer_groups
|
||||||
}
|
}
|
||||||
|
|
||||||
const condToSave = manager.create(DiscountCondition, {
|
const toCreate = {
|
||||||
type: data.type,
|
type: data.type,
|
||||||
operator: data.operator,
|
operator: data.operator,
|
||||||
discount_rule_id: data.rule_id,
|
discount_rule_id: data.rule_id,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if (data.id) {
|
||||||
|
toCreate["id"] = data.id
|
||||||
|
}
|
||||||
|
|
||||||
|
const condToSave = manager.create(DiscountCondition, toCreate)
|
||||||
|
|
||||||
const { conditionTable, resourceKey } = getJoinTableResourceIdentifiers(
|
const { conditionTable, resourceKey } = getJoinTableResourceIdentifiers(
|
||||||
data.type
|
data.type
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ import {
|
|||||||
AdminDiscountsDeleteRes,
|
AdminDiscountsDeleteRes,
|
||||||
AdminDiscountsListRes,
|
AdminDiscountsListRes,
|
||||||
AdminDiscountsRes,
|
AdminDiscountsRes,
|
||||||
AdminGetDiscountParams,
|
|
||||||
AdminGetDiscountsParams,
|
AdminGetDiscountsParams,
|
||||||
|
AdminPostDiscountsDiscountConditions,
|
||||||
|
AdminPostDiscountsDiscountConditionsCondition,
|
||||||
|
AdminPostDiscountsDiscountConditionsConditionParams,
|
||||||
|
AdminPostDiscountsDiscountConditionsParams,
|
||||||
AdminPostDiscountsDiscountDynamicCodesReq,
|
AdminPostDiscountsDiscountDynamicCodesReq,
|
||||||
AdminPostDiscountsDiscountReq,
|
AdminPostDiscountsDiscountReq,
|
||||||
AdminPostDiscountsReq,
|
AdminPostDiscountsReq,
|
||||||
@@ -133,6 +136,57 @@ class AdminDiscountsResource extends BaseResource {
|
|||||||
const path = `/admin/discounts/${id}/regions/${regionId}`
|
const path = `/admin/discounts/${id}/regions/${regionId}`
|
||||||
return this.client.request("DELETE", path, {}, {}, customHeaders)
|
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
|
export default AdminDiscountsResource
|
||||||
@@ -478,7 +478,14 @@
|
|||||||
"created_at": "2021-03-16T21:24:16.872Z",
|
"created_at": "2021-03-16T21:24:16.872Z",
|
||||||
"updated_at": "2021-03-16T21:24:16.872Z",
|
"updated_at": "2021-03-16T21:24:16.872Z",
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
"metadata": null
|
"metadata": null,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"id": "cnd_01F0YESMVJXKX8XZ6Q9Z6Q6Z6",
|
||||||
|
"type": "products",
|
||||||
|
"operator": "in"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"is_disabled": false,
|
"is_disabled": false,
|
||||||
"parent_discount_id": null,
|
"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) => {
|
rest.get("/admin/draft-orders/", (req, res, ctx) => {
|
||||||
return res(
|
return res(
|
||||||
ctx.status(200),
|
ctx.status(200),
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
AdminDiscountsDeleteRes,
|
AdminDiscountsDeleteRes,
|
||||||
AdminDiscountsRes,
|
AdminDiscountsRes,
|
||||||
|
AdminPostDiscountsDiscountConditions,
|
||||||
|
AdminPostDiscountsDiscountConditionsCondition,
|
||||||
AdminPostDiscountsDiscountDynamicCodesReq,
|
AdminPostDiscountsDiscountDynamicCodesReq,
|
||||||
AdminPostDiscountsDiscountReq,
|
AdminPostDiscountsDiscountReq,
|
||||||
AdminPostDiscountsReq,
|
AdminPostDiscountsReq
|
||||||
} from "@medusajs/medusa"
|
} from "@medusajs/medusa"
|
||||||
import { Response } from "@medusajs/medusa-js"
|
import { Response } from "@medusajs/medusa-js"
|
||||||
import { useMutation, UseMutationOptions, useQueryClient } from "react-query"
|
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 { renderHook } from "@testing-library/react-hooks"
|
||||||
import { fixtures } from "../../../../mocks/data"
|
import { fixtures } from "../../../../mocks/data"
|
||||||
import {
|
import {
|
||||||
@@ -6,7 +7,10 @@ import {
|
|||||||
useAdminDeleteDiscount,
|
useAdminDeleteDiscount,
|
||||||
useAdminDeleteDynamicDiscountCode,
|
useAdminDeleteDynamicDiscountCode,
|
||||||
useAdminDiscountAddRegion,
|
useAdminDiscountAddRegion,
|
||||||
|
useAdminDiscountCreateCondition,
|
||||||
|
useAdminDiscountRemoveCondition,
|
||||||
useAdminDiscountRemoveRegion,
|
useAdminDiscountRemoveRegion,
|
||||||
|
useAdminDiscountUpdateCondition,
|
||||||
useAdminUpdateDiscount,
|
useAdminUpdateDiscount,
|
||||||
} from "../../../../src/"
|
} from "../../../../src/"
|
||||||
import { createWrapper } from "../../../utils"
|
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) => {
|
export default async (req, res) => {
|
||||||
const validated = await validator(AdminPostDiscountsReq, req.body)
|
const validated = await validator(AdminPostDiscountsReq, req.body)
|
||||||
|
|
||||||
console.log(validated.rule.conditions)
|
|
||||||
|
|
||||||
const validatedParams = await validator(
|
const validatedParams = await validator(
|
||||||
AdminPostDiscountsDiscountParams,
|
AdminPostDiscountsDiscountParams,
|
||||||
req.query
|
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)
|
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
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,12 +102,15 @@ export type AdminDiscountsListRes = PaginatedResponse & {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export * from "./add-region"
|
export * from "./add-region"
|
||||||
|
export * from "./create-condition"
|
||||||
export * from "./create-discount"
|
export * from "./create-discount"
|
||||||
export * from "./create-dynamic-code"
|
export * from "./create-dynamic-code"
|
||||||
|
export * from "./delete-condition"
|
||||||
export * from "./delete-discount"
|
export * from "./delete-discount"
|
||||||
export * from "./delete-dynamic-code"
|
export * from "./delete-dynamic-code"
|
||||||
export * from "./get-discount"
|
export * from "./get-discount"
|
||||||
export * from "./get-discount-by-code"
|
export * from "./get-discount-by-code"
|
||||||
export * from "./list-discounts"
|
export * from "./list-discounts"
|
||||||
export * from "./remove-region"
|
export * from "./remove-region"
|
||||||
|
export * from "./update-condition"
|
||||||
export * from "./update-discount"
|
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()
|
@BeforeInsert()
|
||||||
private beforeInsert() {
|
private beforeInsert() {
|
||||||
|
if (this.id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const id = ulid()
|
const id = ulid()
|
||||||
this.id = `discon_${id}`
|
this.id = `discon_${id}`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
Not,
|
Not,
|
||||||
Repository,
|
Repository,
|
||||||
} from "typeorm"
|
} from "typeorm"
|
||||||
|
import { Discount } from "../models"
|
||||||
import {
|
import {
|
||||||
DiscountCondition,
|
DiscountCondition,
|
||||||
DiscountConditionOperator,
|
DiscountConditionOperator,
|
||||||
@@ -35,6 +36,21 @@ type DiscountConditionResourceType = EntityTarget<
|
|||||||
|
|
||||||
@EntityRepository(DiscountCondition)
|
@EntityRepository(DiscountCondition)
|
||||||
export class DiscountConditionRepository extends Repository<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): {
|
getJoinTableResourceIdentifiers(type: string): {
|
||||||
joinTable: string
|
joinTable: string
|
||||||
resourceKey: string
|
resourceKey: string
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { flatten, groupBy, map, merge } from "lodash"
|
import { flatten, groupBy, map, merge } from "lodash"
|
||||||
import { EntityRepository, Repository, FindManyOptions } from "typeorm"
|
import { EntityRepository, FindManyOptions, Repository } from "typeorm"
|
||||||
import { Order } from "../models/order"
|
import { Order } from "../models/order"
|
||||||
|
|
||||||
@EntityRepository(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 "."
|
} from "."
|
||||||
import { Cart } from "../models/cart"
|
import { Cart } from "../models/cart"
|
||||||
import { Discount } from "../models/discount"
|
import { Discount } from "../models/discount"
|
||||||
import { DiscountConditionType } from "../models/discount-condition"
|
|
||||||
import {
|
import {
|
||||||
AllocationType as DiscountAllocation,
|
AllocationType as DiscountAllocation,
|
||||||
DiscountRule,
|
DiscountRule,
|
||||||
@@ -28,10 +27,10 @@ import {
|
|||||||
CreateDynamicDiscountInput,
|
CreateDynamicDiscountInput,
|
||||||
FilterableDiscountProps,
|
FilterableDiscountProps,
|
||||||
UpdateDiscountInput,
|
UpdateDiscountInput,
|
||||||
UpsertDiscountConditionInput,
|
|
||||||
} from "../types/discount"
|
} from "../types/discount"
|
||||||
import { isFuture, isPast } from "../utils/date-helpers"
|
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.
|
* Provides layer to manipulate discounts.
|
||||||
@@ -43,6 +42,7 @@ class DiscountService extends BaseService {
|
|||||||
private discountRuleRepository_: typeof DiscountRuleRepository
|
private discountRuleRepository_: typeof DiscountRuleRepository
|
||||||
private giftCardRepository_: typeof GiftCardRepository
|
private giftCardRepository_: typeof GiftCardRepository
|
||||||
private discountConditionRepository_: typeof DiscountConditionRepository
|
private discountConditionRepository_: typeof DiscountConditionRepository
|
||||||
|
private discountConditionService_: DiscountConditionService
|
||||||
private totalsService_: TotalsService
|
private totalsService_: TotalsService
|
||||||
private productService_: ProductService
|
private productService_: ProductService
|
||||||
private regionService_: RegionService
|
private regionService_: RegionService
|
||||||
@@ -54,6 +54,7 @@ class DiscountService extends BaseService {
|
|||||||
discountRuleRepository,
|
discountRuleRepository,
|
||||||
giftCardRepository,
|
giftCardRepository,
|
||||||
discountConditionRepository,
|
discountConditionRepository,
|
||||||
|
discountConditionService,
|
||||||
totalsService,
|
totalsService,
|
||||||
productService,
|
productService,
|
||||||
regionService,
|
regionService,
|
||||||
@@ -77,6 +78,9 @@ class DiscountService extends BaseService {
|
|||||||
/** @private @const {DiscountConditionRepository} */
|
/** @private @const {DiscountConditionRepository} */
|
||||||
this.discountConditionRepository_ = discountConditionRepository
|
this.discountConditionRepository_ = discountConditionRepository
|
||||||
|
|
||||||
|
/** @private @const {DiscountConditionRepository} */
|
||||||
|
this.discountConditionService_ = discountConditionService
|
||||||
|
|
||||||
/** @private @const {TotalsService} */
|
/** @private @const {TotalsService} */
|
||||||
this.totalsService_ = totalsService
|
this.totalsService_ = totalsService
|
||||||
|
|
||||||
@@ -104,6 +108,7 @@ class DiscountService extends BaseService {
|
|||||||
discountRuleRepository: this.discountRuleRepository_,
|
discountRuleRepository: this.discountRuleRepository_,
|
||||||
giftCardRepository: this.giftCardRepository_,
|
giftCardRepository: this.giftCardRepository_,
|
||||||
discountConditionRepository: this.discountConditionRepository_,
|
discountConditionRepository: this.discountConditionRepository_,
|
||||||
|
discountConditionService: this.discountConditionService_,
|
||||||
totalsService: this.totalsService_,
|
totalsService: this.totalsService_,
|
||||||
productService: this.productService_,
|
productService: this.productService_,
|
||||||
regionService: this.regionService_,
|
regionService: this.regionService_,
|
||||||
@@ -263,9 +268,13 @@ class DiscountService extends BaseService {
|
|||||||
const result = await discountRepo.save(created)
|
const result = await discountRepo.save(created)
|
||||||
|
|
||||||
if (conditions?.length) {
|
if (conditions?.length) {
|
||||||
for (const cond of conditions) {
|
await Promise.all(
|
||||||
await this.upsertDiscountCondition_(result.id, cond)
|
conditions.map(async (cond) => {
|
||||||
}
|
await this.discountConditionService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.upsertCondition({ rule_id: result.rule_id, ...cond })
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -382,9 +391,13 @@ class DiscountService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (conditions?.length) {
|
if (conditions?.length) {
|
||||||
for (const cond of conditions) {
|
await Promise.all(
|
||||||
await this.upsertDiscountCondition_(discount.id, cond)
|
conditions.map(async (cond) => {
|
||||||
}
|
await this.discountConditionService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.upsertCondition({ rule_id: discount.rule_id, ...cond })
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (regions) {
|
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(
|
async validateDiscountForProduct(
|
||||||
discountRuleId: string,
|
discountRuleId: string,
|
||||||
productId: string | undefined
|
productId: string | undefined
|
||||||
|
|||||||
@@ -672,7 +672,7 @@ class ProductService extends BaseService {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (!product) {
|
if (!product) {
|
||||||
return Promise.resolve()
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await productRepo.softRemove(product)
|
await productRepo.softRemove(product)
|
||||||
@@ -1069,22 +1069,25 @@ class ProductService extends BaseService {
|
|||||||
|
|
||||||
const productArray = Array.isArray(products) ? products : [products]
|
const productArray = Array.isArray(products) ? products : [products]
|
||||||
|
|
||||||
const priceSelectionStrategy =
|
const priceSelectionStrategy = this.priceSelectionStrategy_.withTransaction(
|
||||||
this.priceSelectionStrategy_.withTransaction(manager)
|
manager
|
||||||
|
)
|
||||||
|
|
||||||
const productsWithPrices = await Promise.all(
|
const productsWithPrices = await Promise.all(
|
||||||
productArray.map(async (p) => {
|
productArray.map(async (p) => {
|
||||||
if (p.variants?.length) {
|
if (p.variants?.length) {
|
||||||
p.variants = await Promise.all(
|
p.variants = await Promise.all(
|
||||||
p.variants.map(async (v) => {
|
p.variants.map(async (v) => {
|
||||||
const prices =
|
const prices = await priceSelectionStrategy.calculateVariantPrice(
|
||||||
await priceSelectionStrategy.calculateVariantPrice(v.id, {
|
v.id,
|
||||||
|
{
|
||||||
region_id: regionId,
|
region_id: regionId,
|
||||||
currency_code: currencyCode,
|
currency_code: currencyCode,
|
||||||
cart_id: cart_id,
|
cart_id: cart_id,
|
||||||
customer_id: customer_id,
|
customer_id: customer_id,
|
||||||
include_discount_prices,
|
include_discount_prices,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...v,
|
...v,
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ export class AdminUpsertConditionsReq {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type UpsertDiscountConditionInput = {
|
export type UpsertDiscountConditionInput = {
|
||||||
|
rule_id?: string
|
||||||
id?: string
|
id?: string
|
||||||
operator?: DiscountConditionOperator
|
operator?: DiscountConditionOperator
|
||||||
products?: string[]
|
products?: string[]
|
||||||
|
|||||||
Reference in new issue
Block a user