From d02905cefaa9802edb8a7974d08e772558e51cda Mon Sep 17 00:00:00 2001 From: "Carlos R. L. Rodrigues" <37986729+carlos-r-l-rodrigues@users.noreply.github.com> Date: Thu, 25 Apr 2024 07:06:23 -0300 Subject: [PATCH] feat(order): bundled actions (#7133) --- .../__tests__/order/draft-order.spec.ts | 6 +- .../modules/__tests__/order/order.spec.ts | 20 +- .../__tests__/create-order.ts | 205 +++++++- .../integration-tests/__tests__/order-edit.ts | 128 ++++- .../__tests__/order-items-shipping.spec.ts | 82 +--- .../src/migrations/Migration20240219102530.ts | 56 ++- packages/order/src/models/index.ts | 1 + .../order/src/models/order-change-action.ts | 6 +- packages/order/src/models/order-change.ts | 2 +- packages/order/src/models/order-item.ts | 15 + .../order/src/models/order-shipping-method.ts | 117 +++++ packages/order/src/models/order-summary.ts | 11 + packages/order/src/models/order.ts | 14 +- packages/order/src/models/shipping-method.ts | 36 -- .../__tests__/util/actions/exchanges.ts | 16 +- .../src/services/order-change-service.ts | 4 + .../src/services/order-module-service.ts | 462 ++++++++++++++++-- packages/order/src/types/utils/index.ts | 4 +- .../order/src/utils/calculate-order-change.ts | 23 +- packages/order/src/utils/transform-order.ts | 23 + packages/types/src/order/mutations.ts | 59 ++- packages/types/src/order/service.ts | 47 +- packages/utils/src/common/trim-zeros.ts | 9 +- 23 files changed, 1114 insertions(+), 232 deletions(-) create mode 100644 packages/order/src/models/order-shipping-method.ts diff --git a/integration-tests/modules/__tests__/order/draft-order.spec.ts b/integration-tests/modules/__tests__/order/draft-order.spec.ts index f708f24901..ed8d4e8104 100644 --- a/integration-tests/modules/__tests__/order/draft-order.spec.ts +++ b/integration-tests/modules/__tests__/order/draft-order.spec.ts @@ -241,9 +241,9 @@ medusaIntegrationTestRunner({ draft_order: expect.objectContaining({ status: "draft", version: 1, - summary: { - total: 8400, - }, + summary: expect.objectContaining({ + // TODO: add summary fields + }), items: [ expect.objectContaining({ title: "Test variant", diff --git a/integration-tests/modules/__tests__/order/order.spec.ts b/integration-tests/modules/__tests__/order/order.spec.ts index 68d7860c00..a2902c717f 100644 --- a/integration-tests/modules/__tests__/order/order.spec.ts +++ b/integration-tests/modules/__tests__/order/order.spec.ts @@ -153,9 +153,9 @@ medusaIntegrationTestRunner({ id: expect.any(String), status: "pending", version: 1, - summary: { - total: 50, - }, + summary: expect.objectContaining({ + // TODO: add all summary fields + }), total: 59.8, subtotal: 50, tax_total: 0.9, @@ -226,7 +226,7 @@ medusaIntegrationTestRunner({ promotion_id: expect.any(String), code: "VIP_25 ETH", raw_amount: { - value: "5.0000000000000000000e-18", + value: "5e-18", precision: 20, }, provider_id: expect.any(String), @@ -237,11 +237,11 @@ medusaIntegrationTestRunner({ subtotal: 5e-18, total: 5e-18, raw_subtotal: { - value: "5.0000000000000000000e-18", + value: "5e-18", precision: 20, }, raw_total: { - value: "5.0000000000000000000e-18", + value: "5e-18", precision: 20, }, }, @@ -289,6 +289,7 @@ medusaIntegrationTestRunner({ metadata: null, created_at: expect.any(String), updated_at: expect.any(String), + deleted_at: null, quantity: 1, fulfilled_quantity: 0, shipped_quantity: 0, @@ -317,7 +318,7 @@ medusaIntegrationTestRunner({ precision: 20, }, raw_discount_total: { - value: "5.0000000000000000000e-18", + value: "5e-18", precision: 20, }, raw_discount_tax_total: { @@ -369,10 +370,9 @@ medusaIntegrationTestRunner({ updated_at: expect.any(String), }, shipping_methods: [ - { + expect.objectContaining({ id: expect.any(String), order_id: expect.any(String), - version: 1, name: "Test shipping method", description: null, raw_amount: { @@ -475,7 +475,7 @@ medusaIntegrationTestRunner({ value: "1", precision: 20, }, - }, + }), ], }) }) diff --git a/packages/order/integration-tests/__tests__/create-order.ts b/packages/order/integration-tests/__tests__/create-order.ts index 3cb6bbd163..9975d120e3 100644 --- a/packages/order/integration-tests/__tests__/create-order.ts +++ b/packages/order/integration-tests/__tests__/create-order.ts @@ -123,7 +123,7 @@ moduleIntegrationTestRunner({ id: expect.stringContaining("order_"), version: 1, summary: expect.objectContaining({ - total: expect.any(Number), + // TODO: add all summary fields }), shipping_address: expect.objectContaining({ id: expect.stringContaining("ordaddr_"), @@ -326,6 +326,209 @@ moduleIntegrationTestRunner({ ) expect(orders4.length).toEqual(0) }) + + it("should create an order, fulfill, ship and return the items", async function () { + const createdOrder = await service.create(input) + + // Fullfilment + await service.registerFulfillment({ + order_id: createdOrder.id, + items: createdOrder.items!.map((item) => { + return { + id: item.id, + quantity: item.quantity, + } + }), + }) + + let getOrder = await service.retrieve(createdOrder.id, { + select: [ + "id", + "version", + "items.id", + "items.quantity", + "items.detail.id", + "items.detail.version", + "items.detail.quantity", + "items.detail.shipped_quantity", + "items.detail.fulfilled_quantity", + ], + relations: ["items", "items.detail"], + }) + + let serializedOrder = JSON.parse(JSON.stringify(getOrder)) + + expect(serializedOrder).toEqual( + expect.objectContaining({ + version: 2, + items: [ + expect.objectContaining({ + quantity: 1, + detail: expect.objectContaining({ + version: 2, + quantity: 1, + fulfilled_quantity: 1, + shipped_quantity: 0, + }), + }), + expect.objectContaining({ + quantity: 2, + detail: expect.objectContaining({ + version: 2, + quantity: 2, + fulfilled_quantity: 2, + shipped_quantity: 0, + }), + }), + expect.objectContaining({ + quantity: 1, + detail: expect.objectContaining({ + version: 2, + quantity: 1, + fulfilled_quantity: 1, + shipped_quantity: 0, + }), + }), + ], + }) + ) + + // Shipment + await service.registerShipment({ + order_id: createdOrder.id, + reference: Modules.FULFILLMENT, + shipping_method: createdOrder.shipping_methods![0].id, + items: createdOrder.items!.map((item) => { + return { + id: item.id, + quantity: item.quantity, + } + }), + }) + + getOrder = await service.retrieve(createdOrder.id, { + select: [ + "id", + "version", + "items.id", + "items.quantity", + "items.detail.id", + "items.detail.version", + "items.detail.quantity", + "items.detail.shipped_quantity", + "items.detail.fulfilled_quantity", + ], + relations: ["items", "items.detail"], + }) + + serializedOrder = JSON.parse(JSON.stringify(getOrder)) + + expect(serializedOrder).toEqual( + expect.objectContaining({ + version: 3, + items: [ + expect.objectContaining({ + quantity: 1, + detail: expect.objectContaining({ + version: 3, + quantity: 1, + fulfilled_quantity: 1, + shipped_quantity: 1, + }), + }), + expect.objectContaining({ + quantity: 2, + detail: expect.objectContaining({ + version: 3, + quantity: 2, + fulfilled_quantity: 2, + shipped_quantity: 2, + }), + }), + expect.objectContaining({ + quantity: 1, + detail: expect.objectContaining({ + version: 3, + quantity: 1, + fulfilled_quantity: 1, + shipped_quantity: 1, + }), + }), + ], + }) + ) + + // Return + await service.createReturn({ + order_id: createdOrder.id, + reference: Modules.FULFILLMENT, + description: "Return all the items", + internal_note: "user wants to return all items", + shipping_method: createdOrder.shipping_methods![0].id, + items: createdOrder.items!.map((item) => { + return { + id: item.id, + quantity: item.quantity, + } + }), + }) + + getOrder = await service.retrieve(createdOrder.id, { + select: [ + "id", + "version", + "items.id", + "items.quantity", + "items.detail.id", + "items.detail.version", + "items.detail.quantity", + "items.detail.shipped_quantity", + "items.detail.fulfilled_quantity", + "items.detail.return_requested_quantity", + ], + relations: ["items", "items.detail"], + }) + + serializedOrder = JSON.parse(JSON.stringify(getOrder)) + + expect(serializedOrder).toEqual( + expect.objectContaining({ + version: 4, + items: [ + expect.objectContaining({ + quantity: 1, + detail: expect.objectContaining({ + version: 4, + quantity: 1, + fulfilled_quantity: 1, + shipped_quantity: 1, + return_requested_quantity: 1, + }), + }), + expect.objectContaining({ + quantity: 2, + detail: expect.objectContaining({ + version: 4, + quantity: 2, + fulfilled_quantity: 2, + shipped_quantity: 2, + return_requested_quantity: 2, + }), + }), + expect.objectContaining({ + quantity: 1, + detail: expect.objectContaining({ + version: 4, + quantity: 1, + fulfilled_quantity: 1, + shipped_quantity: 1, + return_requested_quantity: 1, + }), + }), + ], + }) + ) + }) }) }, }) diff --git a/packages/order/integration-tests/__tests__/order-edit.ts b/packages/order/integration-tests/__tests__/order-edit.ts index aaec411141..5f56f65418 100644 --- a/packages/order/integration-tests/__tests__/order-edit.ts +++ b/packages/order/integration-tests/__tests__/order-edit.ts @@ -1,5 +1,10 @@ import { Modules } from "@medusajs/modules-sdk" -import { CreateOrderDTO, IOrderModuleService } from "@medusajs/types" +import { + CreateOrderChangeActionDTO, + CreateOrderChangeDTO, + CreateOrderDTO, + IOrderModuleService, +} from "@medusajs/types" import { BigNumber } from "@medusajs/utils" import { SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils" import { ChangeActionType } from "../../src/utils" @@ -7,7 +12,7 @@ import { ChangeActionType } from "../../src/utils" jest.setTimeout(100000) moduleIntegrationTestRunner({ - debug: 0, + debug: false, moduleName: Modules.ORDER, testSuite: ({ service }: SuiteOptions) => { describe("Order Module Service - Order Edits", () => { @@ -132,9 +137,10 @@ moduleIntegrationTestRunner({ version: createdOrder.version, internal_note: "adding an item", reference: "order_line_item", - reference_id: createdOrder.items[0].id, + reference_id: createdOrder.items![0].id, amount: - createdOrder.items[0].unit_price * createdOrder.items[0].quantity, + createdOrder.items![0].unit_price * + createdOrder.items![0].quantity, details: { quantity: 1, }, @@ -144,9 +150,10 @@ moduleIntegrationTestRunner({ order_id: createdOrder.id, version: createdOrder.version, reference: "order_line_item", - reference_id: createdOrder.items[1].id, + reference_id: createdOrder.items![1].id, amount: - createdOrder.items[1].unit_price * createdOrder.items[1].quantity, + createdOrder.items![1].unit_price * + createdOrder.items![1].quantity, details: { quantity: 3, }, @@ -158,7 +165,7 @@ moduleIntegrationTestRunner({ reference: "fullfilment", reference_id: "fulfill_123", details: { - reference_id: createdOrder.items[2].id, + reference_id: createdOrder.items![2].id, quantity: 1, }, }, @@ -169,7 +176,7 @@ moduleIntegrationTestRunner({ reference: "fullfilment", reference_id: "shipping_123", details: { - reference_id: createdOrder.items[2].id, + reference_id: createdOrder.items![2].id, quantity: 1, }, }, @@ -181,7 +188,7 @@ moduleIntegrationTestRunner({ reference: "return", reference_id: "return_123", details: { - reference_id: createdOrder.items[2].id, + reference_id: createdOrder.items![2].id, quantity: 1, }, }, @@ -193,11 +200,11 @@ moduleIntegrationTestRunner({ reference: "return", reference_id: "return_123", details: { - reference_id: createdOrder.items[2].id, + reference_id: createdOrder.items![2].id, quantity: 1, }, }, - ]) + ] as CreateOrderChangeActionDTO[]) await service.applyPendingOrderActions(createdOrder.id) @@ -327,7 +334,7 @@ moduleIntegrationTestRunner({ ]) }) - it("should create an order change, add actions to it and confirm the changes.", async function () { + it("should create an order change, add actions to it, confirm the changes, revert all the changes and restore the changes again.", async function () { const createdOrder = await service.create(input) const orderChange = await service.createOrderChange({ @@ -339,10 +346,10 @@ moduleIntegrationTestRunner({ { action: ChangeActionType.ITEM_ADD, reference: "order_line_item", - reference_id: createdOrder.items[0].id, + reference_id: createdOrder.items![0].id, amount: - createdOrder.items[0].unit_price * - createdOrder.items[0].quantity, + createdOrder.items![0].unit_price * + createdOrder.items![0].quantity, details: { quantity: 1, }, @@ -350,10 +357,10 @@ moduleIntegrationTestRunner({ { action: ChangeActionType.ITEM_ADD, reference: "order_line_item", - reference_id: createdOrder.items[1].id, + reference_id: createdOrder.items![1].id, amount: - createdOrder.items[1].unit_price * - createdOrder.items[1].quantity, + createdOrder.items![1].unit_price * + createdOrder.items![1].quantity, details: { quantity: 3, }, @@ -363,7 +370,7 @@ moduleIntegrationTestRunner({ reference: "fullfilment", reference_id: "fulfill_123", details: { - reference_id: createdOrder.items[2].id, + reference_id: createdOrder.items![2].id, quantity: 1, }, }, @@ -372,7 +379,7 @@ moduleIntegrationTestRunner({ reference: "fullfilment", reference_id: "shipping_123", details: { - reference_id: createdOrder.items[2].id, + reference_id: createdOrder.items![2].id, quantity: 1, }, }, @@ -381,7 +388,7 @@ moduleIntegrationTestRunner({ reference: "return", reference_id: "return_123", details: { - reference_id: createdOrder.items[2].id, + reference_id: createdOrder.items![2].id, quantity: 1, }, }, @@ -391,7 +398,7 @@ moduleIntegrationTestRunner({ reference: "return", reference_id: "return_123", details: { - reference_id: createdOrder.items[2].id, + reference_id: createdOrder.items![2].id, quantity: 1, }, }, @@ -427,6 +434,9 @@ moduleIntegrationTestRunner({ }) ) + expect(serializedModifiedOrder.shipping_methods).toHaveLength(1) + expect(serializedModifiedOrder.shipping_methods[0].amount).toEqual(10) + expect(serializedModifiedOrder.items).toEqual([ expect.objectContaining({ quantity: 2, @@ -466,6 +476,72 @@ moduleIntegrationTestRunner({ }), }), ]) + + // Revert Last Changes + await service.revertLastVersion(createdOrder.id) + const revertedOrder = await service.retrieve(createdOrder.id, { + select: [ + "id", + "version", + "items.detail", + "summary", + "shipping_methods", + ], + relations: ["items"], + }) + + const serializedRevertedOrder = JSON.parse( + JSON.stringify(revertedOrder) + ) + expect(serializedRevertedOrder).toEqual( + expect.objectContaining({ + version: 1, + }) + ) + + expect(serializedRevertedOrder.shipping_methods).toHaveLength(1) + expect(serializedRevertedOrder.shipping_methods[0].amount).toEqual(10) + + expect(serializedRevertedOrder.items).toEqual([ + expect.objectContaining({ + quantity: 1, + unit_price: 8, + detail: expect.objectContaining({ + version: 1, + quantity: 1, + }), + }), + expect.objectContaining({ + title: "Item 2", + unit_price: 5, + quantity: 2, + detail: expect.objectContaining({ + version: 1, + quantity: 2, + fulfilled_quantity: 0, + shipped_quantity: 0, + return_requested_quantity: 0, + return_received_quantity: 0, + return_dismissed_quantity: 0, + written_off_quantity: 0, + }), + }), + expect.objectContaining({ + title: "Item 3", + unit_price: 30, + quantity: 1, + detail: expect.objectContaining({ + version: 1, + quantity: 1, + fulfilled_quantity: 0, + shipped_quantity: 0, + return_requested_quantity: 0, + return_received_quantity: 0, + return_dismissed_quantity: 0, + written_off_quantity: 0, + }), + }), + ]) }) it("should create order changes, cancel and reject them.", async function () { @@ -487,16 +563,16 @@ moduleIntegrationTestRunner({ { action: ChangeActionType.ITEM_ADD, reference: "order_line_item", - reference_id: createdOrder.items[0].id, + reference_id: createdOrder.items![0].id, amount: - createdOrder.items[0].unit_price * - createdOrder.items[0].quantity, + createdOrder.items![0].unit_price * + createdOrder.items![0].quantity, details: { quantity: 1, }, }, ], - }) + } as CreateOrderChangeDTO) await service.cancelOrderChange({ id: orderChange.id, diff --git a/packages/order/integration-tests/__tests__/order-items-shipping.spec.ts b/packages/order/integration-tests/__tests__/order-items-shipping.spec.ts index dd66ee13ec..5b4839b79f 100644 --- a/packages/order/integration-tests/__tests__/order-items-shipping.spec.ts +++ b/packages/order/integration-tests/__tests__/order-items-shipping.spec.ts @@ -790,7 +790,7 @@ moduleIntegrationTestRunner({ }, ]) - const methods = await service.createShippingMethods([ + const [eurMethod, usdMethod] = await service.createShippingMethods([ { order_id: eurOrder.id, amount: 100, @@ -811,47 +811,14 @@ moduleIntegrationTestRunner({ eurOrder = orders.find((c) => c.currency_code === "eur")! usdOrder = orders.find((c) => c.currency_code === "usd")! - const eurMethods = methods.filter((m) => m.order_id === eurOrder.id) - const usdMethods = methods.filter((m) => m.order_id === usdOrder.id) - - expect(eurOrder.shipping_methods![0].id).toBe(eurMethods[0].id) - expect(usdOrder.shipping_methods![0].id).toBe(usdMethods[0].id) + expect(eurOrder.shipping_methods![0].id).toBe(eurMethod.id) + expect(usdOrder.shipping_methods![0].id).toBe(usdMethod.id) expect(eurOrder.shipping_methods?.length).toBe(1) expect(usdOrder.shipping_methods?.length).toBe(1) }) }) - describe("deleteShippingMethods", () => { - it("should delete a line item succesfully", async () => { - const [createdOrder] = await service.create([ - { - currency_code: "eur", - }, - ]) - - const [method] = await service.createShippingMethods( - createdOrder.id, - [ - { - amount: 100, - name: "test", - }, - ] - ) - - expect(method.id).not.toBe(null) - - await service.deleteShippingMethods(method.id) - - const order = await service.retrieve(createdOrder.id, { - relations: ["shipping_methods"], - }) - - expect(order.shipping_methods?.length).toBe(0) - }) - }) - describe("setLineItemAdjustments", () => { it("should set line item adjustments for an order", async () => { const [createdOrder] = await service.create([ @@ -1457,6 +1424,7 @@ moduleIntegrationTestRunner({ }) const serialized = JSON.parse(JSON.stringify(order)) + expect(serialized.shipping_methods).toEqual( expect.arrayContaining([ expect.objectContaining({ @@ -1750,39 +1718,43 @@ moduleIntegrationTestRunner({ }, ]) - const orderOneMethods = await service.listShippingMethods( + const orderOneMethods = await service.listOrderShippingMethods( { order_id: orderOne.id }, - { relations: ["adjustments", "order"] } + { relations: ["shipping_method.adjustments"] } ) - const orderTwoMethods = await service.listShippingMethods( + const orderTwoMethods = await service.listOrderShippingMethods( { order_id: orderTwo.id }, - { relations: ["adjustments", "order"] } + { relations: ["shipping_method.adjustments"] } ) expect(orderOneMethods).toEqual( expect.arrayContaining([ expect.objectContaining({ - adjustments: expect.arrayContaining([ - expect.objectContaining({ - shipping_method_id: shippingMethodOne.id, - amount: 100, - code: "FREE", - }), - ]), + shipping_method: expect.objectContaining({ + adjustments: expect.arrayContaining([ + expect.objectContaining({ + shipping_method_id: shippingMethodOne.id, + amount: 100, + code: "FREE", + }), + ]), + }), }), ]) ) expect(orderTwoMethods).toEqual( expect.arrayContaining([ expect.objectContaining({ - adjustments: expect.arrayContaining([ - expect.objectContaining({ - shipping_method_id: shippingMethodTwo.id, - amount: 150, - code: "CODE-2", - }), - ]), + shipping_method: expect.objectContaining({ + adjustments: expect.arrayContaining([ + expect.objectContaining({ + shipping_method_id: shippingMethodTwo.id, + amount: 150, + code: "CODE-2", + }), + ]), + }), }), ]) ) @@ -1828,7 +1800,7 @@ moduleIntegrationTestRunner({ }) describe("deleteShippingMethodAdjustments", () => { - it("should delete a shipping method succesfully", async () => { + it("should delete a shipping method adjustment succesfully", async () => { const [createdOrder] = await service.create([ { currency_code: "eur", diff --git a/packages/order/src/migrations/Migration20240219102530.ts b/packages/order/src/migrations/Migration20240219102530.ts index 66bdabe072..50b4440b44 100644 --- a/packages/order/src/migrations/Migration20240219102530.ts +++ b/packages/order/src/migrations/Migration20240219102530.ts @@ -158,13 +158,15 @@ export class Migration20240219102530 extends Migration { "totals" JSONB NULL, "created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(), "updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(), + "deleted_at" timestamptz NULL, CONSTRAINT "order_summary_pkey" PRIMARY KEY ("id") ); CREATE INDEX IF NOT EXISTS "IDX_order_summary_order_id_version" ON "order_summary" ( order_id, version - ); + ) + WHERE deleted_at IS NOT NULL; CREATE TABLE IF NOT EXISTS "order_change" ( "id" TEXT NOT NULL, @@ -181,7 +183,7 @@ export class Migration20240219102530 extends Migration { ) ) NOT NULL DEFAULT 'pending', "internal_note" text NULL, - "created_by" text NOT NULL, + "created_by" text NULL, "requested_by" text NULL, "requested_at" timestamptz NULL, "confirmed_by" text NULL, @@ -261,22 +263,53 @@ export class Migration20240219102530 extends Migration { "metadata" JSONB NULL, "created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(), "updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(), + "deleted_at" timestamptz NULL, CONSTRAINT "order_item_pkey" PRIMARY KEY ("id") ); CREATE INDEX IF NOT EXISTS "IDX_order_item_order_id" ON "order_item" ( order_id - ); + ) + WHERE deleted_at IS NOT NULL; CREATE INDEX IF NOT EXISTS "IDX_order_item_order_id_version" ON "order_item" ( order_id, version - ); + ) + WHERE deleted_at IS NOT NULL; CREATE INDEX IF NOT EXISTS "IDX_order_item_item_id" ON "order_item" ( item_id + ) + WHERE deleted_at IS NOT NULL; + + CREATE TABLE IF NOT EXISTS "order_shipping" ( + "id" TEXT NOT NULL, + "order_id" TEXT NOT NULL, + "version" INTEGER NOT NULL, + "shipping_method_id" TEXT NOT NULL, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(), + "updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(), + "deleted_at" timestamptz NULL, + CONSTRAINT "order_shipping_pkey" PRIMARY KEY ("id") ); + CREATE INDEX IF NOT EXISTS "IDX_order_shipping_order_id" ON "order_shipping" ( + order_id + ) + WHERE deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_shipping_order_id_version" ON "order_shipping" ( + order_id, + version + ) + WHERE deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_shipping_item_id" ON "order_shipping" ( + shipping_method_id + ) + WHERE deleted_at IS NOT NULL; + CREATE TABLE IF NOT EXISTS "order_line_item" ( "id" TEXT NOT NULL, "totals_id" TEXT NULL, @@ -350,8 +383,6 @@ export class Migration20240219102530 extends Migration { CREATE TABLE IF NOT EXISTS "order_shipping_method" ( "id" TEXT NOT NULL, - "order_id" TEXT NOT NULL, - "version" INTEGER NOT NULL DEFAULT 1, "name" TEXT NOT NULL, "description" JSONB NULL, "amount" NUMERIC NOT NULL, @@ -365,15 +396,6 @@ export class Migration20240219102530 extends Migration { CONSTRAINT "order_shipping_method_pkey" PRIMARY KEY ("id") ); - CREATE INDEX IF NOT EXISTS "IDX_order_shipping_method_order_id" ON "order_shipping_method" ( - order_id - ); - - CREATE INDEX IF NOT EXISTS "IDX_order_shipping_method_order_id_version" ON "order_shipping_method" ( - order_id, - version - ); - CREATE INDEX IF NOT EXISTS "IDX_order_shipping_method_shipping_option_id" ON "order_shipping_method" ( shipping_option_id ); @@ -484,8 +506,8 @@ export class Migration20240219102530 extends Migration { UPDATE CASCADE ON DELETE CASCADE; - ALTER TABLE if exists "order_shipping_method" - ADD CONSTRAINT "order_shipping_method_order_id_foreign" FOREIGN KEY ("order_id") REFERENCES "order" ("id") ON + ALTER TABLE if exists "order_shipping" + ADD CONSTRAINT "order_shipping_order_id_foreign" FOREIGN KEY ("order_id") REFERENCES "order" ("id") ON UPDATE CASCADE ON DELETE CASCADE; diff --git a/packages/order/src/models/index.ts b/packages/order/src/models/index.ts index 56db40a996..72fb64f390 100644 --- a/packages/order/src/models/index.ts +++ b/packages/order/src/models/index.ts @@ -6,6 +6,7 @@ export { default as Order } from "./order" export { default as OrderChange } from "./order-change" export { default as OrderChangeAction } from "./order-change-action" export { default as OrderItem } from "./order-item" +export { default as OrderShippingMethod } from "./order-shipping-method" export { default as OrderSummary } from "./order-summary" export { default as ShippingMethod } from "./shipping-method" export { default as ShippingMethodAdjustment } from "./shipping-method-adjustment" diff --git a/packages/order/src/models/order-change-action.ts b/packages/order/src/models/order-change-action.ts index 8f550e48a4..64b4584b55 100644 --- a/packages/order/src/models/order-change-action.ts +++ b/packages/order/src/models/order-change-action.ts @@ -136,14 +136,16 @@ export default class OrderChangeAction { @BeforeCreate() onCreate() { this.id = generateEntityId(this.id, "ordchact") - this.order_id ??= this.order?.id ?? null + this.order_id ??= this.order?.id ?? this.order_change?.order_id ?? null this.order_change_id ??= this.order_change?.id ?? null + this.version ??= this.order_change?.version ?? null } @OnInit() onInit() { this.id = generateEntityId(this.id, "ordchact") - this.order_id ??= this.order?.id ?? null + this.order_id ??= this.order?.id ?? this.order_change?.order_id ?? null this.order_change_id ??= this.order_change?.id ?? null + this.version ??= this.order_change?.version ?? null } } diff --git a/packages/order/src/models/order-change.ts b/packages/order/src/models/order-change.ts index 03a35e1591..0aa67ca1be 100644 --- a/packages/order/src/models/order-change.ts +++ b/packages/order/src/models/order-change.ts @@ -82,7 +82,7 @@ export default class OrderChange { @Property({ columnType: "text", nullable: true }) internal_note: string | null = null - @Property({ columnType: "text" }) + @Property({ columnType: "text", nullable: true }) created_by: string // customer, user, third party, etc. @Property({ columnType: "text", nullable: true }) diff --git a/packages/order/src/models/order-item.ts b/packages/order/src/models/order-item.ts index 89db537424..edb7eefcd2 100644 --- a/packages/order/src/models/order-item.ts +++ b/packages/order/src/models/order-item.ts @@ -22,16 +22,25 @@ type OptionalLineItemProps = DAL.EntityDateColumns const OrderIdIndex = createPsqlIndexStatementHelper({ tableName: "order_item", columns: ["order_id"], + where: "deleted_at IS NOT NULL", }) const OrderVersionIndex = createPsqlIndexStatementHelper({ tableName: "order_item", columns: ["version"], + where: "deleted_at IS NOT NULL", }) const ItemIdIndex = createPsqlIndexStatementHelper({ tableName: "order_item", columns: ["item_id"], + where: "deleted_at IS NOT NULL", +}) + +const DeletedAtIndex = createPsqlIndexStatementHelper({ + tableName: "order", + columns: "deleted_at", + where: "deleted_at IS NOT NULL", }) @Entity({ tableName: "order_item" }) @@ -133,11 +142,16 @@ export default class OrderItem { }) updated_at: Date + @Property({ columnType: "timestamptz", nullable: true }) + @DeletedAtIndex.MikroORMIndex() + deleted_at: Date | null = null + @BeforeCreate() onCreate() { this.id = generateEntityId(this.id, "orditem") this.order_id ??= this.order?.id this.item_id ??= this.item?.id + this.version ??= this.order?.version } @OnInit() @@ -145,5 +159,6 @@ export default class OrderItem { this.id = generateEntityId(this.id, "orditem") this.order_id ??= this.order?.id this.item_id ??= this.item?.id + this.version ??= this.order?.version } } diff --git a/packages/order/src/models/order-shipping-method.ts b/packages/order/src/models/order-shipping-method.ts new file mode 100644 index 0000000000..95a0c6c8cd --- /dev/null +++ b/packages/order/src/models/order-shipping-method.ts @@ -0,0 +1,117 @@ +import { DAL } from "@medusajs/types" +import { + createPsqlIndexStatementHelper, + generateEntityId, +} from "@medusajs/utils" +import { + BeforeCreate, + Entity, + ManyToOne, + OnInit, + OptionalProps, + PrimaryKey, + Property, +} from "@mikro-orm/core" +import Order from "./order" +import ShippingMethod from "./shipping-method" + +type OptionalShippingMethodProps = DAL.EntityDateColumns + +const OrderIdIndex = createPsqlIndexStatementHelper({ + tableName: "order_shipping", + columns: ["order_id"], + where: "deleted_at IS NOT NULL", +}) + +const OrderVersionIndex = createPsqlIndexStatementHelper({ + tableName: "order_shipping", + columns: ["version"], + where: "deleted_at IS NOT NULL", +}) + +const ItemIdIndex = createPsqlIndexStatementHelper({ + tableName: "order_shipping", + columns: ["shipping_method_id"], + where: "deleted_at IS NOT NULL", +}) + +const DeletedAtIndex = createPsqlIndexStatementHelper({ + tableName: "order", + columns: "deleted_at", + where: "deleted_at IS NOT NULL", +}) + +@Entity({ tableName: "order_shipping" }) +export default class OrderShippingMethod { + [OptionalProps]?: OptionalShippingMethodProps + + @PrimaryKey({ columnType: "text" }) + id: string + + @ManyToOne({ + entity: () => Order, + mapToPk: true, + fieldName: "order_id", + columnType: "text", + }) + @OrderIdIndex.MikroORMIndex() + order_id: string + + @Property({ columnType: "integer" }) + @OrderVersionIndex.MikroORMIndex() + version: number + + @ManyToOne(() => Order, { + persist: false, + }) + order: Order + + @ManyToOne({ + entity: () => ShippingMethod, + fieldName: "shipping_method_id", + mapToPk: true, + columnType: "text", + }) + @ItemIdIndex.MikroORMIndex() + shipping_method_id: string + + @ManyToOne(() => ShippingMethod, { + persist: false, + }) + shipping_method: ShippingMethod + + @Property({ + onCreate: () => new Date(), + columnType: "timestamptz", + defaultRaw: "now()", + }) + created_at: Date + + @Property({ + onCreate: () => new Date(), + onUpdate: () => new Date(), + columnType: "timestamptz", + defaultRaw: "now()", + }) + updated_at: Date + + @Property({ columnType: "timestamptz", nullable: true }) + @DeletedAtIndex.MikroORMIndex() + deleted_at: Date | null = null + + @BeforeCreate() + onCreate() { + this.id = generateEntityId(this.id, "ordspmv") + this.order_id ??= this.order?.id + this.shipping_method_id ??= this.shipping_method?.id + this.version ??= this.order?.version + } + + @OnInit() + onInit() { + this.id = generateEntityId(this.id, "ordspmv") + this.order_id ??= this.order?.id + this.shipping_method_id ??= this.shipping_method?.id + this.version ??= this.order?.version + } +} diff --git a/packages/order/src/models/order-summary.ts b/packages/order/src/models/order-summary.ts index 2d83ef49b7..6fb87fab26 100644 --- a/packages/order/src/models/order-summary.ts +++ b/packages/order/src/models/order-summary.ts @@ -41,6 +41,13 @@ type OrderSummaryTotals = { const OrderIdVersionIndex = createPsqlIndexStatementHelper({ tableName: "order_summary", columns: ["order_id", "version"], + where: "deleted_at IS NOT NULL", +}) + +const DeletedAtIndex = createPsqlIndexStatementHelper({ + tableName: "order", + columns: "deleted_at", + where: "deleted_at IS NOT NULL", }) @Entity({ tableName: "order_summary" }) @@ -87,6 +94,10 @@ export default class OrderSummary { }) updated_at: Date + @Property({ columnType: "timestamptz", nullable: true }) + @DeletedAtIndex.MikroORMIndex() + deleted_at: Date | null = null + @BeforeCreate() onCreate() { this.id = generateEntityId(this.id, "ordsum") diff --git a/packages/order/src/models/order.ts b/packages/order/src/models/order.ts index 8f61856643..a357889909 100644 --- a/packages/order/src/models/order.ts +++ b/packages/order/src/models/order.ts @@ -19,8 +19,8 @@ import { } from "@mikro-orm/core" import Address from "./address" import OrderItem from "./order-item" +import OrderShippingMethod from "./order-shipping-method" import OrderSummary from "./order-summary" -import ShippingMethod from "./shipping-method" import Transaction from "./transaction" type OptionalOrderProps = @@ -166,10 +166,14 @@ export default class Order { }) items = new Collection(this) - @OneToMany(() => ShippingMethod, (shippingMethod) => shippingMethod.order, { - cascade: [Cascade.PERSIST], - }) - shipping_methods = new Collection(this) + @OneToMany( + () => OrderShippingMethod, + (shippingMethod) => shippingMethod.order, + { + cascade: [Cascade.PERSIST], + } + ) + shipping_methods = new Collection(this) @OneToMany(() => Transaction, (transaction) => transaction.order, { cascade: [Cascade.PERSIST], diff --git a/packages/order/src/models/shipping-method.ts b/packages/order/src/models/shipping-method.ts index cadc462644..5a17a1f736 100644 --- a/packages/order/src/models/shipping-method.ts +++ b/packages/order/src/models/shipping-method.ts @@ -10,13 +10,11 @@ import { Cascade, Collection, Entity, - ManyToOne, OneToMany, OnInit, PrimaryKey, Property, } from "@mikro-orm/core" -import Order from "./order" import ShippingMethodAdjustment from "./shipping-method-adjustment" import ShippingMethodTaxLine from "./shipping-method-tax-line" @@ -25,43 +23,11 @@ const ShippingOptionIdIndex = createPsqlIndexStatementHelper({ columns: "shipping_option_id", }) -const OrderIdIndex = createPsqlIndexStatementHelper({ - tableName: "order_shipping_method", - columns: "order_id", -}) - -const OrderVersionIndex = createPsqlIndexStatementHelper({ - tableName: "order_shipping_method", - columns: ["order_id", "version"], -}) - @Entity({ tableName: "order_shipping_method" }) -@OrderVersionIndex.MikroORMIndex() export default class ShippingMethod { @PrimaryKey({ columnType: "text" }) id: string - @ManyToOne({ - entity: () => Order, - columnType: "text", - fieldName: "order_id", - mapToPk: true, - onDelete: "cascade", - }) - @OrderIdIndex.MikroORMIndex() - order_id: string - - @ManyToOne(() => Order, { - persist: false, - }) - order: Order - - @Property({ - columnType: "integer", - defaultRaw: "1", - }) - version: number = 1 - @Property({ columnType: "text" }) name: string @@ -126,11 +92,9 @@ export default class ShippingMethod { @BeforeCreate() onCreate() { this.id = generateEntityId(this.id, "ordsm") - this.order_id ??= this.order?.id } @OnInit() onInit() { this.id = generateEntityId(this.id, "ordsm") - this.order_id ??= this.order?.id } } diff --git a/packages/order/src/services/__tests__/util/actions/exchanges.ts b/packages/order/src/services/__tests__/util/actions/exchanges.ts index 1c8063e600..1a463f2bb4 100644 --- a/packages/order/src/services/__tests__/util/actions/exchanges.ts +++ b/packages/order/src/services/__tests__/util/actions/exchanges.ts @@ -56,9 +56,7 @@ describe("Order Exchange - Actions", function () { price: 0, }, ], - summary: { - total: 270, - }, + total: 270, } it("should perform an item exchage", function () { @@ -98,14 +96,14 @@ describe("Order Exchange - Actions", function () { const sumToJSON = JSON.parse(JSON.stringify(changes.summary)) expect(sumToJSON).toEqual({ - transactionTotal: "0", + transactionTotal: 0, originalOrderTotal: 270, - currentOrderTotal: "312.5", - temporaryDifference: "62.5", + currentOrderTotal: 312.5, + temporaryDifference: 62.5, futureDifference: 0, - futureTemporaryDifference: "0", - pendingDifference: "312.5", - differenceSum: "42.5", + futureTemporaryDifference: 0, + pendingDifference: 312.5, + differenceSum: 42.5, }) const toJson = JSON.parse(JSON.stringify(changes.order.items)) diff --git a/packages/order/src/services/order-change-service.ts b/packages/order/src/services/order-change-service.ts index 9b3d6a3eeb..40d34737ba 100644 --- a/packages/order/src/services/order-change-service.ts +++ b/packages/order/src/services/order-change-service.ts @@ -74,6 +74,10 @@ export default class OrderChangeService< } } + if (!orderChange) { + return [] + } + const relations = deduplicate([...(config.relations ?? []), "actions"]) config.relations = relations diff --git a/packages/order/src/services/order-module-service.ts b/packages/order/src/services/order-module-service.ts index 3f12ebe398..090419d977 100644 --- a/packages/order/src/services/order-module-service.ts +++ b/packages/order/src/services/order-module-service.ts @@ -1,16 +1,21 @@ import { Context, + CreateOrderChangeActionDTO, DAL, FindConfig, InternalModuleDeclaration, IOrderModuleService, ModuleJoinerConfig, ModulesSdkTypes, + OrderDTO, OrderTypes, UpdateOrderItemWithSelectorDTO, } from "@medusajs/types" import { + createRawPropertiesFromBigNumber, + decorateCartTotals, deduplicate, + getShippingMethodsTotals, InjectManager, InjectTransactionManager, isObject, @@ -30,6 +35,7 @@ import { OrderChange, OrderChangeAction, OrderItem, + OrderShippingMethod, OrderSummary, ShippingMethod, ShippingMethodAdjustment, @@ -48,7 +54,7 @@ import { UpdateOrderShippingMethodTaxLineDTO, } from "@types" import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" -import { calculateOrderChange } from "../utils" +import { calculateOrderChange, ChangeActionType } from "../utils" import { formatOrder } from "../utils/transform-order" import OrderChangeService from "./order-change-service" import OrderService from "./order-service" @@ -68,6 +74,7 @@ type InjectedDependencies = { orderChangeActionService: ModulesSdkTypes.InternalModuleService orderItemService: ModulesSdkTypes.InternalModuleService orderSummaryService: ModulesSdkTypes.InternalModuleService + orderShippingMethodService: ModulesSdkTypes.InternalModuleService } const generateMethodForModels = [ @@ -83,6 +90,7 @@ const generateMethodForModels = [ OrderChangeAction, OrderItem, OrderSummary, + OrderShippingMethod, ] export default class OrderModuleService< @@ -98,7 +106,8 @@ export default class OrderModuleService< TOrderChange extends OrderChange = OrderChange, TOrderChangeAction extends OrderChangeAction = OrderChangeAction, TOrderItem extends OrderItem = OrderItem, - TOrderSummary extends OrderSummary = OrderSummary + TOrderSummary extends OrderSummary = OrderSummary, + TOrderShippingMethod extends OrderShippingMethod = OrderShippingMethod > extends ModulesSdkUtils.abstractModuleServiceFactory< InjectedDependencies, @@ -118,6 +127,7 @@ export default class OrderModuleService< OrderChangeAction: { dto: OrderTypes.OrderChangeActionDTO } OrderItem: { dto: OrderTypes.OrderItemDTO } OrderSummary: { dto: OrderTypes.OrderSummaryDTO } + OrderShippingMethod: { dto: OrderShippingMethod } } >(Order, generateMethodForModels, entityNameToLinkableKeysMap) implements IOrderModuleService @@ -136,6 +146,7 @@ export default class OrderModuleService< protected orderChangeActionService_: ModulesSdkTypes.InternalModuleService protected orderItemService_: ModulesSdkTypes.InternalModuleService protected orderSummaryService_: ModulesSdkTypes.InternalModuleService + protected orderShippingMethodService_: ModulesSdkTypes.InternalModuleService constructor( { @@ -153,6 +164,7 @@ export default class OrderModuleService< orderChangeActionService, orderItemService, orderSummaryService, + orderShippingMethodService, }: InjectedDependencies, protected readonly moduleDeclaration: InternalModuleDeclaration ) { @@ -173,6 +185,7 @@ export default class OrderModuleService< this.orderChangeActionService_ = orderChangeActionService this.orderItemService_ = orderItemService this.orderSummaryService_ = orderSummaryService + this.orderShippingMethodService_ = orderShippingMethodService } __joinerConfig(): ModuleJoinerConfig { @@ -304,20 +317,6 @@ export default class OrderModuleService< ): Promise { const input = Array.isArray(data) ? data : [data] - // TODO: calculate order total - for (const inp of input) { - const ordTotals = inp as any - ordTotals.summary = { - totals: { - total: - inp.items?.reduce((acc, item) => { - const it = item as any - return acc + it.unit_price * it.quantity - }, 0) ?? 0, - }, - } - } - const orders = await this.create_(input, sharedContext) const result = await this.list( @@ -354,8 +353,33 @@ export default class OrderModuleService< const lineItemsToCreate: CreateOrderLineItemDTO[] = [] const createdOrders: Order[] = [] - for (const { items, ...order } of data) { - const created = await this.orderService_.create(order, sharedContext) + for (const { items, shipping_methods, ...order } of data) { + const ord = order as any + + const shippingMethods = shipping_methods?.map((sm: any) => { + return { + shipping_method: { ...sm }, + } + }) + + ord.shipping_methods = shippingMethods + + const orderWithTotals = decorateCartTotals({ + ...ord, + items, + }) as any + const calculated = calculateOrderChange({ + order: orderWithTotals, + actions: [], + transactions: order.transactions, + }) + createRawPropertiesFromBigNumber(calculated) + + ord.summary = { + totals: calculated.summary, + } + + const created = await this.orderService_.create(ord, sharedContext) createdOrders.push(created) @@ -484,7 +508,26 @@ export default class OrderModuleService< const data = Array.isArray(orderIdOrData) ? orderIdOrData : [orderIdOrData] - items = await this.createLineItemsBulk_(data, sharedContext) + + const allOrderIds = data.map((dt) => dt.order_id) + const order = await this.list( + { id: allOrderIds }, + { select: ["id", "version"] }, + sharedContext + ) + const mapOrderVersion = order.reduce((acc, curr) => { + acc[curr.id] = curr.version + return acc + }, {}) + + const lineItems = data.map((dt) => { + return { + ...dt, + version: mapOrderVersion[dt.order_id], + } + }) + + items = await this.createLineItemsBulk_(lineItems, sharedContext) } return await this.baseRepository_.serialize( @@ -867,8 +910,27 @@ export default class OrderModuleService< const data = Array.isArray(orderIdOrData) ? orderIdOrData : [orderIdOrData] + + const allOrderIds = data.map((dt) => dt.order_id) + const order = await this.list( + { id: allOrderIds }, + { select: ["id", "version"] }, + sharedContext + ) + const mapOrderVersion = order.reduce((acc, curr) => { + acc[curr.id] = curr.version + return acc + }, {}) + + const orderShippingMethodData = data.map((dt) => { + return { + shipping_method: dt, + order_id: dt.order_id, + version: mapOrderVersion[dt.order_id], + } + }) methods = await this.createShippingMethodsBulk_( - data as OrderTypes.CreateOrderShippingMethodDTO[], + orderShippingMethodData as any, sharedContext ) } @@ -886,13 +948,13 @@ export default class OrderModuleService< ): Promise { const order = await this.retrieve( orderId, - { select: ["id"] }, + { select: ["id", "version"] }, sharedContext ) const methods = data.map((method) => { return { - ...method, + shipping_method: method, order_id: order.id, version: method.version ?? order.version ?? 1, } @@ -903,13 +965,19 @@ export default class OrderModuleService< @InjectTransactionManager("baseRepository_") protected async createShippingMethodsBulk_( - data: OrderTypes.CreateOrderShippingMethodDTO[], + data: { + shipping_method: OrderTypes.CreateOrderShippingMethodDTO + order_id: string + version: number + }[], @MedusaContext() sharedContext: Context = {} ): Promise { - return await this.shippingMethodService_.create( + const sm = await this.orderShippingMethodService_.create( data as unknown as CreateOrderShippingMethodDTO[], sharedContext ) + + return sm.map((s) => s.shipping_method) } async createLineItemAdjustments( @@ -1494,12 +1562,12 @@ export default class OrderModuleService< } async confirmOrderChange( - orderId: string, + orderChangeId: string, sharedContext?: Context ): Promise async confirmOrderChange( - orderId: string[], + orderChangeId: string[], sharedContext?: Context ): Promise @@ -1561,12 +1629,12 @@ export default class OrderModuleService< } async declineOrderChange( - orderId: string, + orderChangeId: string, sharedContext?: Context ): Promise async declineOrderChange( - orderId: string[], + orderChangeId: string[], sharedContext?: Context ): Promise @@ -1656,6 +1724,120 @@ export default class OrderModuleService< await this.applyOrderChanges_(changes, sharedContext) } + @InjectManager("baseRepository_") + async revertLastVersion(orderId: string, sharedContext?: Context) { + const order = await super.retrieve( + orderId, + { + select: ["id", "version"], + }, + sharedContext + ) + + if (order.version < 2) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Order with id ${orderId} has no previous versions` + ) + } + + return await this.revertLastChange_(order, sharedContext) + } + + @InjectTransactionManager("baseRepository_") + protected async revertLastChange_( + order: OrderDTO, + sharedContext?: Context + ): Promise { + const currentVersion = order.version + + // Order Changes + const orderChanges = await this.orderChangeService_.list( + { + order_id: order.id, + version: currentVersion, + }, + { select: ["id", "version"] }, + sharedContext + ) + const orderChangesIds = orderChanges.map((change) => change.id) + + await this.orderChangeService_.softDelete(orderChangesIds, sharedContext) + + // Order Changes Actions + const orderChangesActions = await this.orderChangeActionService_.list( + { + order_id: order.id, + version: currentVersion, + }, + { select: ["id", "version"] }, + sharedContext + ) + const orderChangeActionsIds = orderChangesActions.map((action) => action.id) + + await this.orderChangeActionService_.softDelete( + orderChangeActionsIds, + sharedContext + ) + + // Order Summary + const orderSummary = await this.orderSummaryService_.list( + { + order_id: order.id, + version: currentVersion, + }, + { select: ["id", "version"] }, + sharedContext + ) + const orderSummaryIds = orderSummary.map((summary) => summary.id) + + await this.orderSummaryService_.softDelete(orderSummaryIds, sharedContext) + + // Order Items + const orderItems = await this.orderItemService_.list( + { + order_id: order.id, + version: currentVersion, + }, + { select: ["id", "version"] }, + sharedContext + ) + const orderItemIds = orderItems.map((summary) => summary.id) + + await this.orderItemService_.softDelete(orderItemIds, sharedContext) + + // Shipping Methods + const orderShippingMethods = await this.orderShippingMethodService_.list( + { + order_id: order.id, + version: currentVersion, + }, + { select: ["id", "version"] }, + sharedContext + ) + const orderShippingMethodIds = orderShippingMethods.map( + (summary) => summary.id + ) + + await this.orderShippingMethodService_.softDelete( + orderShippingMethodIds, + sharedContext + ) + + // Order + await this.orderService_.update( + { + selector: { + id: order.id, + }, + data: { + version: order.version - 1, + }, + }, + sharedContext + ) + } + private async getAndValidateOrderChange_( orderChangeIds: string[], includeActions: boolean, @@ -1812,14 +1994,22 @@ export default class OrderModuleService< const orders = await this.list( { id: deduplicate(ordersIds) }, { - select: ["id", "version", "items.detail", "transactions", "summary"], + select: [ + "id", + "version", + "items.detail", + "transactions", + "summary", + "total", + ], relations: ["transactions", "items", "items.detail"], }, sharedContext ) const itemsToUpsert: OrderItem[] = [] - const summariesToUpdate: any[] = [] + const shippingMethodsToInsert: OrderShippingMethod[] = [] + const summariesToUpsert: any[] = [] const orderToUpdate: any[] = [] for (const order of orders) { @@ -1829,11 +2019,14 @@ export default class OrderModuleService< transactions: order.transactions, }) + createRawPropertiesFromBigNumber(calculated) + const version = actionsMap[order.id][0].version! for (const item of calculated.order.items) { + const orderItem = item.detail as any itemsToUpsert.push({ - id: item.detail.id, + id: orderItem.version === version ? orderItem.id : undefined, item_id: item.id, order_id: order.id, version, @@ -1848,7 +2041,24 @@ export default class OrderModuleService< } as any) } + const orderSummary = order.summary as any + summariesToUpsert.push({ + id: orderSummary.version === version ? orderSummary.id : undefined, + order_id: order.id, + version, + totals: calculated.summary, + }) + if (version > order.version) { + for (const shippingMethod of order.shipping_methods ?? []) { + const sm = { + ...(shippingMethod as any).detail, + version, + } + delete sm.id + shippingMethodsToInsert.push(sm) + } + orderToUpdate.push({ selector: { id: order.id, @@ -1858,16 +2068,6 @@ export default class OrderModuleService< }, }) } - - summariesToUpdate.push({ - selector: { - order_id: order.id, - }, - data: { - version, - totals: calculated.summary, - }, - }) } await promiseAll([ @@ -1880,9 +2080,185 @@ export default class OrderModuleService< itemsToUpsert.length ? this.orderItemService_.upsert(itemsToUpsert, sharedContext) : null, - summariesToUpdate.length - ? this.orderSummaryService_.update(summariesToUpdate, sharedContext) + summariesToUpsert.length + ? this.orderSummaryService_.upsert(summariesToUpsert, sharedContext) + : null, + shippingMethodsToInsert.length + ? this.orderShippingMethodService_.create( + shippingMethodsToInsert, + sharedContext + ) : null, ]) } + + @InjectTransactionManager("baseRepository_") + async registerFulfillment( + data: OrderTypes.RegisterOrderFulfillmentDTO, + sharedContext?: Context + ): Promise { + const items = data.items.map((item) => { + return { + action: ChangeActionType.FULFILL_ITEM, + internal_note: item.internal_note, + reference: data.reference, + reference_id: data.reference_id, + details: { + reference_id: item.id, + quantity: item.quantity, + metadata: item.metadata, + }, + } + }) + + const change = await this.createOrderChange_( + { + order_id: data.order_id, + description: data.description, + internal_note: data.internal_note, + created_by: data.created_by, + metadata: data.metadata, + actions: items, + }, + sharedContext + ) + + await this.confirmOrderChange(change[0].id, sharedContext) + } + + @InjectTransactionManager("baseRepository_") + async registerShipment( + data: OrderTypes.RegisterOrderShipmentDTO, + sharedContext?: Context + ): Promise { + let shippingMethodId + + if (!isString(data.shipping_method)) { + const methods = await this.createShippingMethods( + data.order_id, + data.shipping_method as any, + sharedContext + ) + shippingMethodId = methods[0].id + } else { + shippingMethodId = data.shipping_method + } + + const method = await this.shippingMethodService_.retrieve( + shippingMethodId, + { + relations: ["tax_lines", "adjustments"], + }, + sharedContext + ) + + const calculatedAmount = getShippingMethodsTotals([method as any], {})[ + method.id + ] + + const actions: CreateOrderChangeActionDTO[] = data.items.map((item) => { + return { + action: ChangeActionType.SHIP_ITEM, + internal_note: item.internal_note, + reference: data.reference, + reference_id: shippingMethodId, + details: { + reference_id: item.id, + quantity: item.quantity, + metadata: item.metadata, + }, + } + }) + + if (shippingMethodId) { + actions.push({ + action: ChangeActionType.SHIPPING_ADD, + reference: data.reference, + reference_id: shippingMethodId, + amount: calculatedAmount.total, + }) + } + + const change = await this.createOrderChange_( + { + order_id: data.order_id, + description: data.description, + internal_note: data.internal_note, + created_by: data.created_by, + metadata: data.metadata, + actions, + }, + sharedContext + ) + + await this.confirmOrderChange(change[0].id, sharedContext) + } + + @InjectTransactionManager("baseRepository_") + async createReturn( + data: OrderTypes.CreateOrderReturnDTO, + sharedContext?: Context + ): Promise { + let shippingMethodId + + if (!isString(data.shipping_method)) { + const methods = await this.createShippingMethods( + data.order_id, + data.shipping_method as any, + sharedContext + ) + shippingMethodId = methods[0].id + } else { + shippingMethodId = data.shipping_method + } + + const method = await this.shippingMethodService_.retrieve( + shippingMethodId, + { + relations: ["tax_lines", "adjustments"], + }, + sharedContext + ) + + const calculatedAmount = getShippingMethodsTotals([method as any], {})[ + method.id + ] + + const actions: CreateOrderChangeActionDTO[] = data.items.map((item) => { + return { + action: ChangeActionType.RETURN_ITEM, + internal_note: item.internal_note, + reference: data.reference, + reference_id: shippingMethodId, + details: { + reference_id: item.id, + quantity: item.quantity, + metadata: item.metadata, + }, + } + }) + + if (shippingMethodId) { + actions.push({ + action: ChangeActionType.SHIPPING_ADD, + reference: data.reference, + reference_id: shippingMethodId, + amount: calculatedAmount.total, + }) + } + + const change = await this.createOrderChange_( + { + order_id: data.order_id, + description: data.description, + internal_note: data.internal_note, + created_by: data.created_by, + metadata: data.metadata, + actions, + }, + sharedContext + ) + + await this.confirmOrderChange(change[0].id, sharedContext) + } } diff --git a/packages/order/src/types/utils/index.ts b/packages/order/src/types/utils/index.ts index 481b39846c..1182d98f90 100644 --- a/packages/order/src/types/utils/index.ts +++ b/packages/order/src/types/utils/index.ts @@ -24,9 +24,7 @@ export type VirtualOrder = { price: BigNumberInput }[] - summary: { - total: BigNumberInput - } + total: BigNumberInput metadata?: Record } diff --git a/packages/order/src/utils/calculate-order-change.ts b/packages/order/src/utils/calculate-order-change.ts index 6e739b7bc4..21a503c6ea 100644 --- a/packages/order/src/utils/calculate-order-change.ts +++ b/packages/order/src/utils/calculate-order-change.ts @@ -1,5 +1,6 @@ import { BigNumberInput, OrderSummaryDTO } from "@medusajs/types" import { + BigNumber, MathBN, isDefined, transformPropertiesToBigNumber, @@ -61,8 +62,8 @@ export class OrderChangeProcessing { pendingDifference: 0, futureTemporarySum: 0, differenceSum: 0, - currentOrderTotal: this.order.summary?.total ?? 0, - originalOrderTotal: this.order.summary?.total ?? 0, + currentOrderTotal: this.order.total ?? 0, + originalOrderTotal: this.order.total ?? 0, transactionTotal, } } @@ -344,14 +345,16 @@ export class OrderChangeProcessing { public getSummary(): OrderSummaryDTO { const summary = this.summary const orderSummary = { - transactionTotal: summary.transactionTotal, - originalOrderTotal: summary.originalOrderTotal, - currentOrderTotal: summary.currentOrderTotal, - temporaryDifference: summary.temporaryDifference, - futureDifference: summary.futureDifference, - futureTemporaryDifference: summary.futureTemporaryDifference, - pendingDifference: summary.pendingDifference, - differenceSum: summary.differenceSum, + transactionTotal: new BigNumber(summary.transactionTotal), + originalOrderTotal: new BigNumber(summary.originalOrderTotal), + currentOrderTotal: new BigNumber(summary.currentOrderTotal), + temporaryDifference: new BigNumber(summary.temporaryDifference), + futureDifference: new BigNumber(summary.futureDifference), + futureTemporaryDifference: new BigNumber( + summary.futureTemporaryDifference + ), + pendingDifference: new BigNumber(summary.pendingDifference), + differenceSum: new BigNumber(summary.differenceSum), } as unknown as OrderSummaryDTO /* diff --git a/packages/order/src/utils/transform-order.ts b/packages/order/src/utils/transform-order.ts index c7cd8893de..ea1851b5b1 100644 --- a/packages/order/src/utils/transform-order.ts +++ b/packages/order/src/utils/transform-order.ts @@ -29,6 +29,19 @@ export function formatOrder( } }) + order.shipping_methods = order.shipping_methods?.map((shippingMethod) => { + const sm = { ...shippingMethod.shipping_method } + + delete shippingMethod.shipping_method + return { + ...sm, + order_id: shippingMethod.order_id, + detail: { + ...shippingMethod, + }, + } + }) + order.summary = order.summary?.[0]?.totals return options?.includeTotals @@ -58,6 +71,16 @@ export function mapRepositoryToOrderModel(config) { if (rel == "summary" && type === "fields") { obj.populate.push("summary") return "summary.totals" + } else if ( + rel.includes("shipping_methods") && + !rel.includes("shipping_methods.shipping_method") + ) { + obj.populate.push("shipping_methods.shipping_method") + + return rel.replace( + "shipping_methods", + "shipping_methods.shipping_method" + ) } else if (rel.includes("items.detail")) { return rel.replace("items.detail", "items") } else if (rel == "items") { diff --git a/packages/types/src/order/mutations.ts b/packages/types/src/order/mutations.ts index 054956b66b..7240d667a9 100644 --- a/packages/types/src/order/mutations.ts +++ b/packages/types/src/order/mutations.ts @@ -52,7 +52,9 @@ export interface UpdateOrderDTO { region_id?: string customer_id?: string sales_channel_id?: string - status?: string + items?: CreateOrderLineItemDTO[] + shipping_address?: CreateOrderAddressDTO | UpdateOrderAddressDTO + billing_address?: CreateOrderAddressDTO | UpdateOrderAddressDTO email?: string no_notification?: boolean metadata?: Record @@ -199,6 +201,7 @@ export interface UpdateOrderLineItemDTO export interface CreateOrderShippingMethodDTO { name: string order_id: string + version?: number amount: BigNumberInput shipping_option_id?: string data?: Record @@ -289,10 +292,12 @@ export interface ConfirmOrderChangeDTO { export interface CreateOrderChangeActionDTO { order_change_id?: string + version?: number reference?: string reference_id?: string action: string internal_note?: string + amount?: BigNumberInput details?: Record } @@ -344,3 +349,55 @@ export interface UpdateOrderItemWithSelectorDTO { } /** ORDER DETAIL END */ + +/** ORDER bundled action flows */ + +export interface RegisterOrderFulfillmentDTO { + order_id: string + description?: string + internal_note?: string + reference?: string + reference_id?: string + created_by?: string + items: { + id: string + quantity: BigNumberInput + internal_note?: string + metadata?: Record + }[] + metadata?: Record +} + +export interface RegisterOrderShipmentDTO { + order_id: string + description?: string + internal_note?: string + reference?: string + created_by?: string + shipping_method: Omit | string + items: { + id: string + quantity: BigNumberInput + internal_note?: string + metadata?: Record + }[] + metadata?: Record +} + +export interface CreateOrderReturnDTO { + order_id: string + description?: string + reference?: string + internal_note?: string + created_by?: string + shipping_method: Omit | string + items: { + id: string + quantity: BigNumberInput + internal_note?: string + metadata?: Record + }[] + metadata?: Record +} + +/** ORDER bundled action flows */ diff --git a/packages/types/src/order/service.ts b/packages/types/src/order/service.ts index b3a8ba1e7c..c4200e308e 100644 --- a/packages/types/src/order/service.ts +++ b/packages/types/src/order/service.ts @@ -34,10 +34,13 @@ import { CreateOrderLineItemDTO, CreateOrderLineItemForOrderDTO, CreateOrderLineItemTaxLineDTO, + CreateOrderReturnDTO, CreateOrderShippingMethodAdjustmentDTO, CreateOrderShippingMethodDTO, CreateOrderShippingMethodTaxLineDTO, DeclineOrderChangeDTO, + RegisterOrderFulfillmentDTO, + RegisterOrderShipmentDTO, UpdateOrderAddressDTO, UpdateOrderDTO, UpdateOrderItemDTO, @@ -1130,12 +1133,15 @@ export interface IOrderModuleService extends IModuleService { * ``` * */ - confirmOrderChange(orderId: string, sharedContext?: Context): Promise + confirmOrderChange( + orderChangeId: string, + sharedContext?: Context + ): Promise /** * This method Represents the completion of an asynchronous operation * - * @param {string[]} orderId - The order's ID. + * @param {string[]} orderChangeId - The order change's ID. * @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module. * @returns {Promise} Resolves when {summary} * @@ -1145,7 +1151,10 @@ export interface IOrderModuleService extends IModuleService { * ``` * */ - confirmOrderChange(orderId: string[], sharedContext?: Context): Promise + confirmOrderChange( + orderChangeId: string[], + sharedContext?: Context + ): Promise /** * This method Represents the completion of an asynchronous operation @@ -1192,7 +1201,7 @@ export interface IOrderModuleService extends IModuleService { /** * This method Represents the completion of an asynchronous operation * - * @param {string} orderId - The order's ID. + * @param {string} orderChangeId - The order change's ID. * @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module. * @returns {Promise} Resolves when {summary} * @@ -1202,12 +1211,15 @@ export interface IOrderModuleService extends IModuleService { * ``` * */ - declineOrderChange(orderId: string, sharedContext?: Context): Promise + declineOrderChange( + orderChangeId: string, + sharedContext?: Context + ): Promise /** * This method Represents the completion of an asynchronous operation * - * @param {string[]} orderId - The order's ID. + * @param {string[]} orderChangeId - The order change's ID. * @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module. * @returns {Promise} Resolves when {summary} * @@ -1217,7 +1229,10 @@ export interface IOrderModuleService extends IModuleService { * ``` * */ - declineOrderChange(orderId: string[], sharedContext?: Context): Promise + declineOrderChange( + orderChangeId: string[], + sharedContext?: Context + ): Promise /** * This method Represents the completion of an asynchronous operation @@ -1371,4 +1386,22 @@ export interface IOrderModuleService extends IModuleService { config?: RestoreReturn, sharedContext?: Context ): Promise | void> + + revertLastVersion(orderId: string, sharedContext?: Context): Promise + + // Bundled flows + registerFulfillment( + data: RegisterOrderFulfillmentDTO, + sharedContext?: Context + ): Promise + + registerShipment( + data: RegisterOrderShipmentDTO, + sharedContext?: Context + ): Promise + + createReturn( + returnData: CreateOrderReturnDTO, + sharedContext?: Context + ): Promise } diff --git a/packages/utils/src/common/trim-zeros.ts b/packages/utils/src/common/trim-zeros.ts index 5dcab7d3c4..60cfef1903 100644 --- a/packages/utils/src/common/trim-zeros.ts +++ b/packages/utils/src/common/trim-zeros.ts @@ -2,13 +2,16 @@ export function trimZeros(value: string) { const [whole, fraction] = value.split(".") if (fraction) { - const decimal = fraction.replace(/0+$/, "") + const exp = fraction.split("e") + + const decimal = exp[0].replace(/0+$/, "") + const expStr = exp.length > 1 ? `e${exp[1]}` : "" if (!decimal) { - return whole + return whole + expStr } - return `${whole}.${decimal}` + return `${whole}.${decimal}` + expStr } return whole