diff --git a/integration-tests/api/__tests__/admin/discount.js b/integration-tests/api/__tests__/admin/discount.js new file mode 100644 index 0000000000..575b1e0a0d --- /dev/null +++ b/integration-tests/api/__tests__/admin/discount.js @@ -0,0 +1,161 @@ +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", () => { + beforeEach(async () => { + const manager = dbConnection.manager; + try { + await adminSeeder(dbConnection); + } 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 discount and updates it", async () => { + const api = useApi(); + + const response = await api + .post( + "/admin/discounts", + { + code: "HELLOWORLD", + rule: { + description: "test", + type: "percentage", + value: 10, + allocation: "total", + }, + usage_limit: 10, + }, + { + headers: { + Authorization: "Bearer test_token", + }, + } + ) + .catch((err) => { + console.log(err); + }); + + expect(response.status).toEqual(200); + expect(response.data.discount).toEqual( + expect.objectContaining({ + code: "HELLOWORLD", + usage_limit: 10, + }) + ); + + const updated = await api + .post( + `/admin/discounts/${response.data.discount.id}`, + { + usage_limit: 20, + }, + { + headers: { + Authorization: "Bearer test_token", + }, + } + ) + .catch((err) => { + console.log(err); + }); + + expect(updated.status).toEqual(200); + expect(updated.data.discount).toEqual( + expect.objectContaining({ + code: "HELLOWORLD", + usage_limit: 20, + }) + ); + }); + }); + + 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); + }); + }); +}); diff --git a/integration-tests/api/helpers/cart-seeder.js b/integration-tests/api/helpers/cart-seeder.js index a29268879e..73f512dbf1 100644 --- a/integration-tests/api/helpers/cart-seeder.js +++ b/integration-tests/api/helpers/cart-seeder.js @@ -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; diff --git a/integration-tests/api/yarn.lock b/integration-tests/api/yarn.lock index cc3dcb2a7b..c8d8bd4e36 100644 --- a/integration-tests/api/yarn.lock +++ b/integration-tests/api/yarn.lock @@ -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: diff --git a/packages/medusa-fulfillment-webshipper/src/services/webshipper-fulfillment.js b/packages/medusa-fulfillment-webshipper/src/services/webshipper-fulfillment.js index 3504b906bc..7348c422c5 100644 --- a/packages/medusa-fulfillment-webshipper/src/services/webshipper-fulfillment.js +++ b/packages/medusa-fulfillment-webshipper/src/services/webshipper-fulfillment.js @@ -244,6 +244,8 @@ class WebshipperFulfillmentService extends FulfillmentService { if (!webshipperOrder) { let invoice + let certificateOfOrigin + if (this.invoiceGenerator_) { const base64Invoice = await this.invoiceGenerator_.createInvoice( fromOrder, @@ -263,6 +265,27 @@ class WebshipperFulfillmentService extends FulfillmentService { .catch((err) => { throw err }) + + if (this.invoiceGenerator_.createCertificateOfOrigin) { + const base64Coo = await this.invoiceGenerator_.createCertificateOfOrigin( + fromOrder, + fulfillmentItems + ) + + certificateOfOrigin = await this.client_.documents + .create({ + type: "documents", + attributes: { + document_size: this.options_.document_size || "A4", + document_format: "PDF", + base64: base64Coo, + document_type: "certificate", + }, + }) + .catch((err) => { + throw err + }) + } } let id = fulfillment.id @@ -335,14 +358,25 @@ class WebshipperFulfillmentService extends FulfillmentService { country_code: methodData.drop_point_country_code.toUpperCase(), } } - if (invoice) { + + if (invoice || certificateOfOrigin) { + const docData = [] + if (invoice) { + docData.push({ + id: invoice.data.id, + type: invoice.data.type, + }) + } + + if (certificateOfOrigin) { + docData.push({ + id: certificateOfOrigin.data.id, + type: certificateOfOrigin.data.type, + }) + } + newOrder.relationships.documents = { - data: [ - { - id: invoice.data.id, - type: invoice.data.type, - }, - ], + data: docData, } } diff --git a/packages/medusa-plugin-contentful/src/services/contentful.js b/packages/medusa-plugin-contentful/src/services/contentful.js index 5fed5759a4..f78d714073 100644 --- a/packages/medusa-plugin-contentful/src/services/contentful.js +++ b/packages/medusa-plugin-contentful/src/services/contentful.js @@ -76,7 +76,7 @@ class ContentfulService extends BaseService { let assets = [] await Promise.all( product.images - .filter((image) => image !== product.thumbnail) + .filter((image) => image.url !== product.thumbnail) .map(async (image, i) => { const asset = await environment.createAsset({ fields: { @@ -89,8 +89,8 @@ class ContentfulService extends BaseService { file: { "en-US": { contentType: "image/xyz", - fileName: image, - upload: image, + fileName: image.url, + upload: image.url, }, }, }, @@ -450,7 +450,7 @@ class ContentfulService extends BaseService { ] // Update came directly from product variant service so only act on a couple - // of fields. When the update comes from the product we want to ensure + // of fields. When the update comes from the product we want to ensure // references are set up correctly so we run through everything. if (variant.fields) { const found = variant.fields.find((f) => updateFields.includes(f)) diff --git a/packages/medusa-plugin-restock-notification/yarn.lock b/packages/medusa-plugin-restock-notification/yarn.lock index ef13831e83..5ca4e22005 100644 --- a/packages/medusa-plugin-restock-notification/yarn.lock +++ b/packages/medusa-plugin-restock-notification/yarn.lock @@ -2090,9 +2090,9 @@ camelcase@^5.0.0, camelcase@^5.3.1: integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== caniuse-lite@^1.0.30001181: - version "1.0.30001207" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001207.tgz#364d47d35a3007e528f69adb6fecb07c2bb2cc50" - integrity sha512-UPQZdmAsyp2qfCTiMU/zqGSWOYaY9F9LL61V8f+8MrubsaDGpaHD9HRV/EWZGULZn0Hxu48SKzI5DgFwTvHuYw== + version "1.0.30001208" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz#a999014a35cebd4f98c405930a057a0d75352eb9" + integrity sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA== capture-exit@^2.0.0: version "2.0.0" @@ -2359,17 +2359,17 @@ copy-descriptor@^0.1.0: integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= core-js-compat@^3.8.1, core-js-compat@^3.9.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.10.0.tgz#3600dc72869673c110215ee7a005a8609dea0fe1" - integrity sha512-9yVewub2MXNYyGvuLnMHcN1k9RkvB7/ofktpeKTIaASyB88YYqGzUnu0ywMMhJrDHOMiTjSHWGzR+i7Wb9Z1kQ== + version "3.10.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.10.1.tgz#62183a3a77ceeffcc420d907a3e6fc67d9b27f1c" + integrity sha512-ZHQTdTPkqvw2CeHiZC970NNJcnwzT6YIueDMASKt+p3WbZsLXOcoD392SkcWhkC0wBBHhlfhqGKKsNCQUozYtg== dependencies: browserslist "^4.16.3" semver "7.0.0" core-js@^3.6.5, core-js@^3.7.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.10.0.tgz#9a020547c8b6879f929306949e31496bbe2ae9b3" - integrity sha512-MQx/7TLgmmDVamSyfE+O+5BHvG1aUGj/gHhLn1wVtm2B5u1eVIPvh7vkfjwWKNCjrTJB8+He99IntSQ1qP+vYQ== + version "3.10.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.10.1.tgz#e683963978b6806dcc6c0a4a8bd4ab0bdaf3f21a" + integrity sha512-pwCxEXnj27XG47mu7SXAwhLP3L5CrlvCB91ANUkIz40P27kUcvNfSdvyZJ9CLHiVoKSp+TTChMQMSKQEH/IQxA== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -2617,9 +2617,9 @@ ee-first@1.1.1: integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.3.649: - version "1.3.709" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.709.tgz#d7be0b5686a2fdfe8bad898faa3a428d04d8f656" - integrity sha512-LolItk2/ikSGQ7SN8UkuKVNMBZp3RG7Itgaxj1npsHRzQobj9JjMneZOZfLhtwlYBe5fCJ75k+cVCiDFUs23oA== + version "1.3.710" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.710.tgz#b33d316e5d6de92b916e766d8a478d19796ffe11" + integrity sha512-b3r0E2o4yc7mNmBeJviejF1rEx49PUBi+2NPa7jHEX3arkAXnVgLhR0YmV8oi6/Qf3HH2a8xzQmCjHNH0IpXWQ== emoji-regex@^7.0.1: version "7.0.3" diff --git a/packages/medusa-plugin-segment/src/subscribers/order.js b/packages/medusa-plugin-segment/src/subscribers/order.js index 12e0ea3bca..786dffd92b 100644 --- a/packages/medusa-plugin-segment/src/subscribers/order.js +++ b/packages/medusa-plugin-segment/src/subscribers/order.js @@ -18,6 +18,17 @@ class OrderSubscriber { this.fulfillmentService_ = fulfillmentService + + // Swaps + // order.swap_received <--- Will be deprecated + // swap.created + // swap.received + // swap.shipment_created + // swap.payment_completed + // swap.payment_captured + // swap.refund_processed + + eventBusService.subscribe( "order.shipment_created", async ({ id, fulfillment_id }) => { diff --git a/packages/medusa-plugin-segment/src/subscribers/swap.js b/packages/medusa-plugin-segment/src/subscribers/swap.js new file mode 100644 index 0000000000..30c83d0b91 --- /dev/null +++ b/packages/medusa-plugin-segment/src/subscribers/swap.js @@ -0,0 +1,149 @@ +import { humanizeAmount } from "medusa-core-utils" + +class OrderSubscriber { + constructor({ + segmentService, + eventBusService, + swapService, + lineItemService, + fulfillmentService, + }) { + this.fulfillmentService_ = fulfillmentService + + this.lineItemService_ = lineItemService + + this.swapService_ = swapService + + this.segmentService_ = segmentService + + eventBusService.subscribe( + "swap.shipment_created", + async ({ id, fulfillment_id }) => { + const [swap, swapReport] = await this.gatherSwapReport(id) + const fulfillment = await this.fulfillmentService_.retrieve( + fulfillment_id + ) + + const currency = swapReport.currency + const total = humanizeAmount(swap.difference_due, currency) + const reporting_total = await this.segmentService_.getReportingValue( + currency, + total + ) + + return await segmentService.track({ + event: "Swap Shipped", + userId: swap.order.customer_id, + timestamp: fulfillment.shipped_at, + properties: { + reporting_total, + total, + ...swapReport, + }, + }) + } + ) + + eventBusService.subscribe("swap.payment_completed", async ({ id }) => { + const [swap, swapReport] = await this.gatherSwapReport(id) + + const currency = swapReport.currency + const total = humanizeAmount(swap.difference_due, currency) + const reporting_total = await this.segmentService_.getReportingValue( + currency, + total + ) + + return await segmentService.track({ + event: "Swap Confirmed", + userId: swap.order.customer_id, + timestamp: swap.confirmed_at, + properties: { + reporting_total, + total, + ...swapReport, + }, + }) + }) + + eventBusService.subscribe("swap.created", async ({ id }) => { + const [swap, swapReport] = await this.gatherSwapReport(id) + + return await segmentService.track({ + event: "Swap Created", + userId: swap.order.customer_id, + timestamp: swap.created_at, + properties: swapReport, + }) + }) + } + + async gatherSwapReport(id) { + const swap = await this.swapService_.retrieve(id, { + relations: [ + "order", + "additional_items", + "additional_items.variant", + "return_order", + "return_order.items", + "return_order.shipping_method", + ], + }) + + const currency = swap.order.currency_code + + const newItems = await Promise.all( + swap.additional_items.map(async (i) => { + const price = humanizeAmount(i.unit_price, currency) + const reporting_price = await this.segmentService_.getReportingValue( + currency, + price + ) + + return { + name: i.title, + product_id: i.variant.product_id, + variant: i.variant.sku, + quantity: i.quantity, + price, + reporting_price, + } + }) + ) + + const returnItems = await Promise.all( + swap.return_order.items.map(async (ri) => { + const i = await this.lineItemService_.retrieve(ri.item_id, { + relations: ["variant"], + }) + const price = humanizeAmount(i.unit_price, currency) + const reporting_price = await this.segmentService_.getReportingValue( + currency, + price + ) + + return { + name: i.title, + product_id: i.variant.product_id, + variant: i.variant.sku, + quantity: ri.quantity, + price, + reporting_price, + } + }) + ) + + return [ + swap, + { + swap_id: swap.id, + order_id: swap.order_id, + new_items: newItems, + return_items: returnItems, + currency, + }, + ] + } +} + +export default OrderSubscriber diff --git a/packages/medusa/src/api/routes/admin/discounts/__tests__/add-region.js b/packages/medusa/src/api/routes/admin/discounts/__tests__/add-region.js index d4120fdf79..654c46ff99 100644 --- a/packages/medusa/src/api/routes/admin/discounts/__tests__/add-region.js +++ b/packages/medusa/src/api/routes/admin/discounts/__tests__/add-region.js @@ -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", diff --git a/packages/medusa/src/api/routes/admin/discounts/__tests__/add-valid-product.js b/packages/medusa/src/api/routes/admin/discounts/__tests__/add-valid-product.js index 02243c1928..05e521083d 100644 --- a/packages/medusa/src/api/routes/admin/discounts/__tests__/add-valid-product.js +++ b/packages/medusa/src/api/routes/admin/discounts/__tests__/add-valid-product.js @@ -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", diff --git a/packages/medusa/src/api/routes/admin/discounts/__tests__/create-discount.js b/packages/medusa/src/api/routes/admin/discounts/__tests__/create-discount.js index cde96a2332..18536d7533 100644 --- a/packages/medusa/src/api/routes/admin/discounts/__tests__/create-discount.js +++ b/packages/medusa/src/api/routes/admin/discounts/__tests__/create-discount.js @@ -11,6 +11,7 @@ describe("POST /admin/discounts", () => { payload: { code: "TEST", rule: { + description: "Test", type: "fixed", value: 10, allocation: "total", @@ -33,6 +34,7 @@ describe("POST /admin/discounts", () => { expect(DiscountServiceMock.create).toHaveBeenCalledWith({ code: "TEST", rule: { + description: "Test", type: "fixed", value: 10, allocation: "total", @@ -51,6 +53,7 @@ describe("POST /admin/discounts", () => { payload: { code: "10%OFF", rule: { + description: "Test", value: 10, allocation: "total", }, diff --git a/packages/medusa/src/api/routes/admin/discounts/__tests__/get-discount.js b/packages/medusa/src/api/routes/admin/discounts/__tests__/get-discount.js index 9f7c71e69a..9260e22679 100644 --- a/packages/medusa/src/api/routes/admin/discounts/__tests__/get-discount.js +++ b/packages/medusa/src/api/routes/admin/discounts/__tests__/get-discount.js @@ -9,6 +9,8 @@ const defaultFields = [ "is_disabled", "rule_id", "parent_discount_id", + "usage_limit", + "usage_count", "starts_at", "ends_at", "created_at", diff --git a/packages/medusa/src/api/routes/admin/discounts/__tests__/remove-region.js b/packages/medusa/src/api/routes/admin/discounts/__tests__/remove-region.js index 438c5b1125..ec33ba1d17 100644 --- a/packages/medusa/src/api/routes/admin/discounts/__tests__/remove-region.js +++ b/packages/medusa/src/api/routes/admin/discounts/__tests__/remove-region.js @@ -9,6 +9,8 @@ const defaultFields = [ "is_disabled", "rule_id", "parent_discount_id", + "usage_limit", + "usage_count", "starts_at", "ends_at", "created_at", diff --git a/packages/medusa/src/api/routes/admin/discounts/__tests__/remove-valid-product.js b/packages/medusa/src/api/routes/admin/discounts/__tests__/remove-valid-product.js index e5fd724a61..7dbc6d2912 100644 --- a/packages/medusa/src/api/routes/admin/discounts/__tests__/remove-valid-product.js +++ b/packages/medusa/src/api/routes/admin/discounts/__tests__/remove-valid-product.js @@ -9,6 +9,8 @@ const defaultFields = [ "is_disabled", "rule_id", "parent_discount_id", + "usage_limit", + "usage_count", "starts_at", "ends_at", "created_at", diff --git a/packages/medusa/src/api/routes/admin/discounts/create-discount.js b/packages/medusa/src/api/routes/admin/discounts/create-discount.js index b053b16728..4e42bdc882 100644 --- a/packages/medusa/src/api/routes/admin/discounts/create-discount.js +++ b/packages/medusa/src/api/routes/admin/discounts/create-discount.js @@ -36,6 +36,9 @@ import { MedusaError, Validator } from "medusa-core-utils" * type: array * items: * type: string + * usage_limit: + * type: number + * description: Maximum times the discount can be used * metadata: * description: An optional set of key-value pairs to hold additional information. * type: object @@ -64,14 +67,14 @@ export default async (req, res) => { .required(), allocation: Validator.string().required(), valid_for: Validator.array().items(Validator.string()), - usage_limit: Validator.number() - .positive() - .optional(), }) .required(), is_disabled: Validator.boolean().default(false), starts_at: Validator.date().optional(), ends_at: Validator.date().optional(), + usage_limit: Validator.number() + .positive() + .optional(), regions: Validator.array() .items(Validator.string()) .optional(), diff --git a/packages/medusa/src/api/routes/admin/discounts/create-dynamic-code.js b/packages/medusa/src/api/routes/admin/discounts/create-dynamic-code.js index b393c19d8a..30e7288f62 100644 --- a/packages/medusa/src/api/routes/admin/discounts/create-dynamic-code.js +++ b/packages/medusa/src/api/routes/admin/discounts/create-dynamic-code.js @@ -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(), }) diff --git a/packages/medusa/src/api/routes/admin/discounts/index.js b/packages/medusa/src/api/routes/admin/discounts/index.js index dbfc487424..6a240a78ed 100644 --- a/packages/medusa/src/api/routes/admin/discounts/index.js +++ b/packages/medusa/src/api/routes/admin/discounts/index.js @@ -62,6 +62,8 @@ export const defaultFields = [ "is_disabled", "rule_id", "parent_discount_id", + "usage_limit", + "usage_count", "starts_at", "ends_at", "created_at", diff --git a/packages/medusa/src/api/routes/admin/discounts/update-discount.js b/packages/medusa/src/api/routes/admin/discounts/update-discount.js index 0679dc7e1b..f3cfecc1d3 100644 --- a/packages/medusa/src/api/routes/admin/discounts/update-discount.js +++ b/packages/medusa/src/api/routes/admin/discounts/update-discount.js @@ -64,14 +64,14 @@ export default async (req, res) => { value: Validator.number().required(), allocation: Validator.string().required(), valid_for: Validator.array().items(Validator.string()), - usage_limit: Validator.number() - .positive() - .optional(), }) .optional(), is_disabled: Validator.boolean().optional(), starts_at: Validator.date().optional(), ends_at: Validator.date().optional(), + usage_limit: Validator.number() + .positive() + .optional(), regions: Validator.array() .items(Validator.string()) .optional(), diff --git a/packages/medusa/src/migrations/1617002207608-discount_usage.ts b/packages/medusa/src/migrations/1617002207608-discount_usage.ts new file mode 100644 index 0000000000..99d3697963 --- /dev/null +++ b/packages/medusa/src/migrations/1617002207608-discount_usage.ts @@ -0,0 +1,41 @@ +import { MigrationInterface, QueryRunner } from "typeorm" + +export class discountUsage1617002207608 implements MigrationInterface { + name = "discountUsage1617002207608" + + public async up(queryRunner: QueryRunner): Promise { + 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'` + ) + await queryRunner.query( + `ALTER TABLE "discount_rule" ALTER COLUMN "description" DROP NOT NULL` + ) + await queryRunner.query( + `COMMENT ON COLUMN "discount_rule"."description" IS NULL` + ) + } + + public async down(queryRunner: QueryRunner): Promise { + 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` + ) + await queryRunner.query( + `COMMENT ON COLUMN "discount_rule"."description" IS NULL` + ) + await queryRunner.query( + `ALTER TABLE "discount_rule" ALTER COLUMN "description" SET NOT NULL` + ) + } +} diff --git a/packages/medusa/src/models/discount-rule.ts b/packages/medusa/src/models/discount-rule.ts index 76ba081ecf..1e86542892 100644 --- a/packages/medusa/src/models/discount-rule.ts +++ b/packages/medusa/src/models/discount-rule.ts @@ -30,7 +30,7 @@ export class DiscountRule { @PrimaryColumn() id: string - @Column() + @Column({ nullable: true }) description: string @Column({ @@ -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 diff --git a/packages/medusa/src/models/discount.ts b/packages/medusa/src/models/discount.ts index 2095808bcd..b76294bece 100644 --- a/packages/medusa/src/models/discount.ts +++ b/packages/medusa/src/models/discount.ts @@ -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 diff --git a/packages/medusa/src/services/__tests__/cart.js b/packages/medusa/src/services/__tests__/cart.js index 6f3a9d8fec..11a9c72a69 100644 --- a/packages/medusa/src/services/__tests__/cart.js +++ b/packages/medusa/src/services/__tests__/cart.js @@ -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, diff --git a/packages/medusa/src/services/cart.js b/packages/medusa/src/services/cart.js index 48a5831180..72995a5931 100644 --- a/packages/medusa/src/services/cart.js +++ b/packages/medusa/src/services/cart.js @@ -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" diff --git a/packages/medusa/src/services/discount.js b/packages/medusa/src/services/discount.js index 760d2ca0dd..6451eb9c72 100644 --- a/packages/medusa/src/services/discount.js +++ b/packages/medusa/src/services/discount.js @@ -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: discount.usage_limit, } const created = await discountRepo.create(toCreate) diff --git a/packages/medusa/src/subscribers/order.js b/packages/medusa/src/subscribers/order.js index c889cd99a9..0a4cea6a46 100644 --- a/packages/medusa/src/subscribers/order.js +++ b/packages/medusa/src/subscribers/order.js @@ -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, }) }) )