fix(medusa): Move discount usage from rule to discount

This commit is contained in:
olivermrbl
2021-03-29 10:15:41 +02:00
parent 08bb111e29
commit d9cd52a177
17 changed files with 188 additions and 59 deletions

View File

@@ -0,0 +1,83 @@
const { dropDatabase } = require("pg-god");
const path = require("path");
const { Region, DiscountRule, Discount } = require("@medusajs/medusa");
const setupServer = require("../../../helpers/setup-server");
const { useApi } = require("../../../helpers/use-api");
const { initDb } = require("../../../helpers/use-db");
const adminSeeder = require("../../helpers/admin-seeder");
jest.setTimeout(30000);
describe("/admin/discounts", () => {
let medusaProcess;
let dbConnection;
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."));
dbConnection = await initDb({ cwd });
medusaProcess = await setupServer({ cwd });
});
afterAll(async () => {
await dbConnection.close();
await dropDatabase({ databaseName: "medusa-integration" });
medusaProcess.kill();
});
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",
});
} catch (err) {
console.log(err);
throw err;
}
});
afterEach(async () => {
const manager = dbConnection.manager;
await manager.query(`DELETE FROM "discount"`);
await manager.query(`DELETE FROM "discount_rule"`);
await manager.query(`DELETE FROM "user"`);
});
it("creates a dynamic discount", async () => {
const api = useApi();
const response = await api
.post(
"/admin/discounts/test-discount/dynamic-codes",
{
code: "HELLOWORLD",
},
{
headers: {
Authorization: "Bearer test_token",
},
}
)
.catch((err) => {
console.log(err);
});
expect(response.status).toEqual(200);
});
});
});

View File

@@ -16,6 +16,24 @@ module.exports = async (connection, data = {}) => {
tax_rate: 0,
});
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",
rule_id: "test-discount-rule",
is_dynamic: true,
usage_count: 0,
usage_limit: 1,
is_disabled: false,
});
const d = await manager.create(Discount, {
id: "test-discount",
code: "CREATED",
@@ -29,8 +47,6 @@ module.exports = async (connection, data = {}) => {
type: "fixed",
value: 10000,
allocation: "total",
usage_limit: 2,
usage_count: 2,
});
d.rule = dr;

View File

@@ -1174,9 +1174,9 @@
chalk "^4.0.0"
"@medusajs/medusa@1.1.13-dev-1615987548667":
version "1.1.13"
resolved "https://registry.yarnpkg.com/@medusajs/medusa/-/medusa-1.1.13.tgz#1aad18445c062e298bfea2ac362ad2740a53fde6"
integrity sha512-yPQ+uA9qQsJiqME7nR8ggeg/qi2Kypqi6iNsFrdXbA99djjm3Cpkl3kmkTorRvzQ6jbKZ1QMLS+Dx5FwbKuiTw==
version "1.1.16"
resolved "https://registry.yarnpkg.com/@medusajs/medusa/-/medusa-1.1.16.tgz#692578b1eeced9d3603326fda172d43abe5cce95"
integrity sha512-SXK9YEBMxlfP7KgnIYfGTflFhUB2vx6aKI2MiVesCzB5wR2OKkOn6W+oIWeF0/pBttdSitCP4G2iJx2y8dYL5g==
dependencies:
"@babel/plugin-transform-classes" "^7.9.5"
"@hapi/joi" "^16.1.8"
@@ -1198,8 +1198,8 @@
joi "^17.3.0"
joi-objectid "^3.0.1"
jsonwebtoken "^8.5.1"
medusa-core-utils "^1.1.2"
medusa-test-utils "^1.1.5"
medusa-core-utils "^1.1.3"
medusa-test-utils "^1.1.6"
morgan "^1.9.1"
multer "^1.4.2"
passport "^0.4.0"
@@ -4314,28 +4314,28 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
medusa-core-utils@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/medusa-core-utils/-/medusa-core-utils-1.1.2.tgz#3d9ccd37b052bc4701040fbf0618210f075f459a"
integrity sha512-YAGkLkS5DqCSHWlMz2Bfh0nKJQ8n35IfH/39q6J9DXfFUPYU+d5i8lSvIS8PNsXuSs9Es0dzY2ZS/sOIFKWzxw==
medusa-core-utils@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/medusa-core-utils/-/medusa-core-utils-1.1.3.tgz#e740de04a08655b9b037ef135afcc99914498f24"
integrity sha512-Xk7SuHEo4kBgJFHIyd6OkBvK0KO23hF5pPHY2R9Luf26vRvyD3mUZUBIHPnxuT2f576vgJJ7verMN7peizXXkg==
dependencies:
joi "^17.3.0"
joi-objectid "^3.0.1"
medusa-interfaces@1.1.3-dev-1615987548667:
version "1.1.3"
resolved "https://registry.yarnpkg.com/medusa-interfaces/-/medusa-interfaces-1.1.3.tgz#b1fb889c321433d31e6d998dbd7d47eb71a27589"
integrity sha512-6WNzOfcHDM7CaRFOerZBt5eRqje97gMbTcoEiFOxJy8a5B7OIq/wt7UBI1Et3C9lYFKpb5fSUwfDwRWY/yO3UA==
version "1.1.4"
resolved "https://registry.yarnpkg.com/medusa-interfaces/-/medusa-interfaces-1.1.4.tgz#f71e9eb885cd6f51105986d861f83a2b497da240"
integrity sha512-uMjfXbIkJSqkd87/wPGFFQ47ZmNdImW2sGWgrEa8pMGYfE8rGQxgLvGEEnn34ejJHWYGPQRH5Az/J76tY/Zs2g==
dependencies:
medusa-core-utils "^1.1.2"
medusa-core-utils "^1.1.3"
medusa-test-utils@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/medusa-test-utils/-/medusa-test-utils-1.1.5.tgz#5a93c52117c7a8659058512abf939e2aaed619b7"
integrity sha512-oM6lIRdnq6T3VRi702hYOQ9m3T9Zy2IwZAp2nBnJRlXpXbWlNnIS3y0E638m5wsGbFzSKKxrDNdGtEpujxwzyQ==
medusa-test-utils@^1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/medusa-test-utils/-/medusa-test-utils-1.1.6.tgz#c9b2675532338be760a47bd8df7566b162c171d4"
integrity sha512-72k2DMKrxPDgm1JeCCjtNVTl6nFlbdp5HHtE3mWEpl0+88pZvNSp/HL41nENHHZ0YsK2w34FTbm0YMqU4EHg+Q==
dependencies:
"@babel/plugin-transform-classes" "^7.9.5"
medusa-core-utils "^1.1.2"
medusa-core-utils "^1.1.3"
randomatic "^3.1.1"
merge-descriptors@1.0.1:

View File

@@ -38,6 +38,8 @@ describe("POST /admin/discounts/:discount_id/regions/:region_id", () => {
"is_disabled",
"rule_id",
"parent_discount_id",
"usage_limit",
"usage_count",
"starts_at",
"ends_at",
"created_at",

View File

@@ -38,6 +38,8 @@ describe("POST /admin/discounts/:discount_id/variants/:variant_id", () => {
"is_disabled",
"rule_id",
"parent_discount_id",
"usage_limit",
"usage_count",
"starts_at",
"ends_at",
"created_at",

View File

@@ -9,6 +9,8 @@ const defaultFields = [
"is_disabled",
"rule_id",
"parent_discount_id",
"usage_limit",
"usage_count",
"starts_at",
"ends_at",
"created_at",

View File

@@ -9,6 +9,8 @@ const defaultFields = [
"is_disabled",
"rule_id",
"parent_discount_id",
"usage_limit",
"usage_count",
"starts_at",
"ends_at",
"created_at",

View File

@@ -9,6 +9,8 @@ const defaultFields = [
"is_disabled",
"rule_id",
"parent_discount_id",
"usage_limit",
"usage_count",
"starts_at",
"ends_at",
"created_at",

View File

@@ -26,6 +26,7 @@ export default async (req, res) => {
const schema = Validator.object().keys({
code: Validator.string().required(),
usage_limit: Validator.number().default(1),
metadata: Validator.object().optional(),
})

View File

@@ -62,6 +62,8 @@ export const defaultFields = [
"is_disabled",
"rule_id",
"parent_discount_id",
"usage_limit",
"usage_count",
"starts_at",
"ends_at",
"created_at",

View File

@@ -0,0 +1,29 @@
import { MigrationInterface, QueryRunner } from "typeorm"
export class discountUsage1617002207608 implements MigrationInterface {
name = "discountUsage1617002207608"
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "discount_rule" DROP COLUMN "usage_limit"`
)
await queryRunner.query(
`ALTER TABLE "discount_rule" DROP COLUMN "usage_count"`
)
await queryRunner.query(`ALTER TABLE "discount" ADD "usage_limit" integer`)
await queryRunner.query(
`ALTER TABLE "discount" ADD "usage_count" integer NOT NULL DEFAULT '0'`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "discount" DROP COLUMN "usage_count"`)
await queryRunner.query(`ALTER TABLE "discount" DROP COLUMN "usage_limit"`)
await queryRunner.query(
`ALTER TABLE "discount_rule" ADD "usage_count" integer NOT NULL DEFAULT '0'`
)
await queryRunner.query(
`ALTER TABLE "discount_rule" ADD "usage_limit" integer`
)
}
}

View File

@@ -63,12 +63,6 @@ export class DiscountRule {
})
valid_for: Product[]
@Column({ nullable: true })
usage_limit: number
@Column({ default: 0 })
usage_count: number
@CreateDateColumn({ type: "timestamptz" })
created_at: Date
@@ -121,12 +115,6 @@ export class DiscountRule {
* type: array
* items:
* $ref: "#/components/schemas/product"
* usage_limit:
* description: "The maximum number of times that a discount can be used."
* type: integer
* usage_count:
* description: "The number of times a discount has been used."
* type: integer
* created_at:
* description: "The date with timezone at which the resource was created."
* type: string

View File

@@ -68,6 +68,12 @@ export class Discount {
})
regions: Region[]
@Column({ nullable: true })
usage_limit: number
@Column({ default: 0 })
usage_count: number
@CreateDateColumn({ type: "timestamptz" })
created_at: Date
@@ -127,6 +133,12 @@ export class Discount {
* type: array
* items:
* $ref: "#/components/schemas/region"
* usage_limit:
* description: "The maximum number of times that a discount can be used."
* type: integer
* usage_count:
* description: "The number of times a discount has been used."
* type: integer
* created_at:
* description: "The date with timezone at which the resource was created."
* type: string

View File

@@ -1467,10 +1467,9 @@ describe("CartService", () => {
id: IdMap.getId("limit-reached"),
code: "limit-reached",
regions: [{ id: IdMap.getId("good") }],
rule: {
usage_count: 2,
usage_limit: 2,
},
rule: {},
usage_count: 2,
usage_limit: 2,
})
}
if (code === "null-count") {
@@ -1478,10 +1477,9 @@ describe("CartService", () => {
id: IdMap.getId("null-count"),
code: "null-count",
regions: [{ id: IdMap.getId("good") }],
rule: {
usage_count: null,
usage_limit: 2,
},
rule: {},
usage_count: null,
usage_limit: 2,
})
}
if (code === "FREESHIPPING") {
@@ -1630,10 +1628,9 @@ describe("CartService", () => {
id: IdMap.getId("null-count"),
code: "null-count",
regions: [{ id: IdMap.getId("good") }],
rule: {
usage_count: 0,
usage_limit: 2,
},
usage_count: 0,
usage_limit: 2,
rule: {},
},
],
discount_total: 0,

View File

@@ -814,10 +814,10 @@ class CartService extends BaseService {
const rule = discount.rule
// if limit is set and reached, we make an early exit
if (rule?.usage_limit) {
rule.usage_count = rule.usage_count || 0
if (discount.usage_limit) {
discount.usage_count = discount.usage_count || 0
if (rule.usage_limit === rule.usage_count)
if (discount.usage_limit === discount.usage_count)
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Discount has been used maximum allowed times"

View File

@@ -83,13 +83,6 @@ class DiscountService extends BaseService {
.required(),
allocation: Validator.string().required(),
valid_for: Validator.array().optional(),
usage_limit: Validator.number()
.positive()
.allow(null)
.optional(),
usage_count: Validator.number()
.positive()
.optional(),
created_at: Validator.date().optional(),
updated_at: Validator.date()
.allow(null)
@@ -337,6 +330,7 @@ class DiscountService extends BaseService {
is_disabled: false,
code: data.code.toUpperCase(),
parent_discount_id: discount.id,
usage_limit: data.usage_limit,
}
const created = await discountRepo.create(toCreate)

View File

@@ -48,12 +48,9 @@ class OrderSubscriber {
await Promise.all(
order.discounts.map(async d => {
const usageCount = d.rule?.usage_count || 0
const usageCount = d?.usage_count || 0
return this.discountService_.update(d.id, {
rule: {
...d.rule,
usage_count: usageCount + 1,
},
usage_count: usageCount + 1,
})
})
)