feat(order): order change actions engine (#6467)
This commit is contained in:
1 parent
cdb01e073b
commit
03ca5c814e
70 files changed
+5727
-992
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -145,6 +145,7 @@ export class Migration20240219102530 extends Migration {
|
||||
CREATE TABLE IF NOT EXISTS "order_change" (
|
||||
"id" TEXT NOT NULL,
|
||||
"order_id" TEXT NOT NULL,
|
||||
"version" INTEGER NOT NULL,
|
||||
"description" TEXT NULL,
|
||||
"status" text check (
|
||||
"status" IN (
|
||||
@@ -176,6 +177,11 @@ export class Migration20240219102530 extends Migration {
|
||||
order_id
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_order_change_order_id_version" ON "order_change" (
|
||||
order_id,
|
||||
version
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_order_change_status" ON "order_change" (status);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "order_change_action" (
|
||||
@@ -195,11 +201,12 @@ export class Migration20240219102530 extends Migration {
|
||||
order_change_id
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_order_change_action_reference_id" ON "order_change_action" (
|
||||
CREATE INDEX IF NOT EXISTS "IDX_order_change_action_reference_reference_id" ON "order_change_action" (
|
||||
reference,
|
||||
reference_id
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "order_detail" (
|
||||
CREATE TABLE IF NOT EXISTS "order_item" (
|
||||
"id" TEXT NOT NULL,
|
||||
"order_id" TEXT NOT NULL,
|
||||
"version" INTEGER NOT NULL,
|
||||
@@ -218,18 +225,25 @@ export class Migration20240219102530 extends Migration {
|
||||
"raw_return_dismissed_quantity" JSONB NOT NULL,
|
||||
"written_off_quantity" NUMERIC NOT NULL,
|
||||
"raw_written_off_quantity" JSONB NOT NULL,
|
||||
"summary" JSONB NOT NULL,
|
||||
"metadata" JSONB NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
|
||||
CONSTRAINT "order_detail_pkey" PRIMARY KEY ("id")
|
||||
CONSTRAINT "order_item_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "IDX_order_detail_order_id_item_id_version" ON "order_detail" (
|
||||
CREATE INDEX IF NOT EXISTS "IDX_order_item_order_id" ON "order_item" (
|
||||
order_id
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_order_item_order_id_version" ON "order_item" (
|
||||
order_id,
|
||||
item_id,
|
||||
version
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_order_item_item_id" ON "order_item" (
|
||||
item_id
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "order_line_item" (
|
||||
"id" TEXT NOT NULL,
|
||||
"totals_id" TEXT NULL,
|
||||
@@ -278,7 +292,7 @@ export class Migration20240219102530 extends Migration {
|
||||
"provider_id" TEXT NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
|
||||
"item_id" TEXT NULL,
|
||||
"item_id" TEXT NOT NULL,
|
||||
CONSTRAINT "order_line_item_tax_line_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
@@ -294,9 +308,8 @@ export class Migration20240219102530 extends Migration {
|
||||
"provider_id" TEXT NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
|
||||
"item_id" TEXT NULL,
|
||||
CONSTRAINT "order_line_item_adjustment_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT order_line_item_adjustment_check CHECK (amount >= 0)
|
||||
"item_id" TEXT NOT NULL,
|
||||
CONSTRAINT "order_line_item_adjustment_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_order_line_item_adjustment_item_id" ON "order_line_item_adjustment" (item_id);
|
||||
@@ -304,6 +317,7 @@ 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,
|
||||
@@ -314,14 +328,18 @@ export class Migration20240219102530 extends Migration {
|
||||
"metadata" JSONB NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
|
||||
CONSTRAINT "order_shipping_method_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT order_shipping_method_check CHECK (amount >= 0)
|
||||
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
|
||||
);
|
||||
@@ -336,7 +354,7 @@ export class Migration20240219102530 extends Migration {
|
||||
"provider_id" TEXT NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
|
||||
"shipping_method_id" TEXT NULL,
|
||||
"shipping_method_id" TEXT NOT NULL,
|
||||
CONSTRAINT "order_shipping_method_adjustment_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
@@ -354,7 +372,7 @@ export class Migration20240219102530 extends Migration {
|
||||
"provider_id" TEXT NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
|
||||
"shipping_method_id" TEXT NULL,
|
||||
"shipping_method_id" TEXT NOT NULL,
|
||||
CONSTRAINT "order_shipping_method_tax_line_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
@@ -409,18 +427,18 @@ export class Migration20240219102530 extends Migration {
|
||||
UPDATE CASCADE ON
|
||||
DELETE CASCADE;
|
||||
|
||||
ALTER TABLE if exists "order_detail"
|
||||
ADD CONSTRAINT "order_detail_order_id_foreign" FOREIGN KEY ("order_id") REFERENCES "order" ("id") ON
|
||||
ALTER TABLE if exists "order_item"
|
||||
ADD CONSTRAINT "order_item_order_id_foreign" FOREIGN KEY ("order_id") REFERENCES "order" ("id") ON
|
||||
UPDATE CASCADE ON
|
||||
DELETE CASCADE;
|
||||
|
||||
ALTER TABLE if exists "order_detail"
|
||||
ADD CONSTRAINT "order_detail_item_id_foreign" FOREIGN KEY ("item_id") REFERENCES "order_line_item" ("id") ON
|
||||
ALTER TABLE if exists "order_item"
|
||||
ADD CONSTRAINT "order_item_item_id_foreign" FOREIGN KEY ("item_id") REFERENCES "order_line_item" ("id") ON
|
||||
UPDATE CASCADE ON
|
||||
DELETE CASCADE;
|
||||
|
||||
ALTER TABLE if exists "order_line_item"
|
||||
ADD CONSTRAINT "order_line_item_totals_id_foreign" FOREIGN KEY ("totals_id") REFERENCES "order_detail" ("id") ON
|
||||
ADD CONSTRAINT "order_line_item_totals_id_foreign" FOREIGN KEY ("totals_id") REFERENCES "order_item" ("id") ON
|
||||
UPDATE CASCADE ON
|
||||
DELETE CASCADE;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ export { default as LineItemTaxLine } from "./line-item-tax-line"
|
||||
export { default as Order } from "./order"
|
||||
export { default as OrderChange } from "./order-change"
|
||||
export { default as OrderChangeAction } from "./order-change-action"
|
||||
export { default as OrderDetail } from "./order-detail"
|
||||
export { default as OrderItem } from "./order-item"
|
||||
export { default as ShippingMethod } from "./shipping-method"
|
||||
export { default as ShippingMethodAdjustment } from "./shipping-method-adjustment"
|
||||
export { default as ShippingMethodTaxLine } from "./shipping-method-tax-line"
|
||||
|
||||
@@ -5,11 +5,9 @@ import {
|
||||
import {
|
||||
BeforeCreate,
|
||||
Cascade,
|
||||
Check,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import AdjustmentLine from "./adjustment-line"
|
||||
import LineItem from "./line-item"
|
||||
@@ -20,27 +18,31 @@ const ItemIdIndex = createPsqlIndexStatementHelper({
|
||||
})
|
||||
|
||||
@Entity({ tableName: "order_line_item_adjustment" })
|
||||
@Check<LineItemAdjustment>({
|
||||
expression: (columns) => `${columns.amount} >= 0`,
|
||||
})
|
||||
export default class LineItemAdjustment extends AdjustmentLine {
|
||||
@ManyToOne({
|
||||
entity: () => LineItem,
|
||||
cascade: [Cascade.REMOVE, Cascade.PERSIST],
|
||||
@ManyToOne(() => LineItem, {
|
||||
persist: false,
|
||||
})
|
||||
item: LineItem
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
@ManyToOne({
|
||||
entity: () => LineItem,
|
||||
columnType: "text",
|
||||
fieldName: "item_id",
|
||||
cascade: [Cascade.REMOVE],
|
||||
mapToPk: true,
|
||||
})
|
||||
@ItemIdIndex.MikroORMIndex()
|
||||
item_id: string
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "ordliadj")
|
||||
this.item_id ??= this.item?.id
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "ordliadj")
|
||||
this.item_id ??= this.item?.id
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
Entity,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import LineItem from "./line-item"
|
||||
import TaxLine from "./tax-line"
|
||||
@@ -20,23 +19,31 @@ const ItemIdIndex = createPsqlIndexStatementHelper({
|
||||
|
||||
@Entity({ tableName: "order_line_item_tax_line" })
|
||||
export default class LineItemTaxLine extends TaxLine {
|
||||
@ManyToOne({
|
||||
entity: () => LineItem,
|
||||
cascade: [Cascade.REMOVE, Cascade.PERSIST],
|
||||
@ManyToOne(() => LineItem, {
|
||||
fieldName: "item_id",
|
||||
persist: false,
|
||||
})
|
||||
item: LineItem
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
@ManyToOne({
|
||||
entity: () => LineItem,
|
||||
columnType: "text",
|
||||
fieldName: "item_id",
|
||||
cascade: [Cascade.PERSIST, Cascade.REMOVE],
|
||||
mapToPk: true,
|
||||
})
|
||||
@ItemIdIndex.MikroORMIndex()
|
||||
item_id: string
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "ordlitxl")
|
||||
this.item_id ??= this.item?.id
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "ordlitxl")
|
||||
this.item_id ??= this.item?.id
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,23 @@
|
||||
import { BigNumberRawValue, DAL } from "@medusajs/types"
|
||||
import {
|
||||
BigNumber,
|
||||
MikroOrmBigNumberProperty,
|
||||
createPsqlIndexStatementHelper,
|
||||
generateEntityId,
|
||||
MikroOrmBigNumberProperty,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Cascade,
|
||||
Collection,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
OnInit,
|
||||
OneToMany,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import LineItemAdjustment from "./line-item-adjustment"
|
||||
import LineItemTaxLine from "./line-item-tax-line"
|
||||
import OrderDetail from "./order-detail"
|
||||
|
||||
type OptionalLineItemProps =
|
||||
| "is_discoutable"
|
||||
@@ -45,13 +43,6 @@ export default class LineItem {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@ManyToOne({
|
||||
entity: () => OrderDetail,
|
||||
onDelete: "cascade",
|
||||
cascade: [Cascade.REMOVE, Cascade.PERSIST],
|
||||
})
|
||||
totals: OrderDetail
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
title: string
|
||||
|
||||
@@ -131,12 +122,12 @@ export default class LineItem {
|
||||
raw_unit_price: BigNumberRawValue
|
||||
|
||||
@OneToMany(() => LineItemTaxLine, (taxLine) => taxLine.item, {
|
||||
cascade: [Cascade.REMOVE],
|
||||
cascade: [Cascade.PERSIST],
|
||||
})
|
||||
tax_lines = new Collection<LineItemTaxLine>(this)
|
||||
|
||||
@OneToMany(() => LineItemAdjustment, (adjustment) => adjustment.item, {
|
||||
cascade: [Cascade.REMOVE],
|
||||
cascade: [Cascade.PERSIST],
|
||||
})
|
||||
adjustments = new Collection<LineItemAdjustment>(this)
|
||||
|
||||
|
||||
@@ -22,26 +22,31 @@ const OrderChangeIdIndex = createPsqlIndexStatementHelper({
|
||||
columns: "order_change_id",
|
||||
})
|
||||
|
||||
const ReferenceIdIndex = createPsqlIndexStatementHelper({
|
||||
const ReferenceIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "order_change_action",
|
||||
columns: "reference_id",
|
||||
columns: ["reference", "reference_id"],
|
||||
})
|
||||
|
||||
@Entity({ tableName: "order_change_action" })
|
||||
@ReferenceIndex.MikroORMIndex()
|
||||
export default class OrderChangeAction {
|
||||
[OptionalProps]?: OptionalLineItemProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
@ManyToOne({
|
||||
entity: () => OrderChange,
|
||||
columnType: "text",
|
||||
fieldName: "order_change_id",
|
||||
cascade: [Cascade.REMOVE],
|
||||
mapToPk: true,
|
||||
})
|
||||
@OrderChangeIdIndex.MikroORMIndex()
|
||||
order_change_id: string
|
||||
|
||||
@ManyToOne({
|
||||
entity: () => OrderChange,
|
||||
fieldName: "order_change_id",
|
||||
cascade: [Cascade.REMOVE, Cascade.PERSIST],
|
||||
@ManyToOne(() => OrderChange, {
|
||||
persist: false,
|
||||
})
|
||||
order_change: OrderChange
|
||||
|
||||
@@ -49,7 +54,6 @@ export default class OrderChangeAction {
|
||||
reference: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
@ReferenceIdIndex.MikroORMIndex()
|
||||
reference_id: string
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
@@ -82,10 +86,12 @@ export default class OrderChangeAction {
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "ordchact")
|
||||
this.order_change_id ??= this.order_change?.id
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "ordchact")
|
||||
this.order_change_id ??= this.order_change?.id
|
||||
}
|
||||
}
|
||||
@@ -32,24 +32,38 @@ const OrderChangeStatusIndex = createPsqlIndexStatementHelper({
|
||||
columns: "status",
|
||||
})
|
||||
|
||||
const VersionIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "order_change",
|
||||
columns: ["order_id", "version"],
|
||||
})
|
||||
|
||||
@Entity({ tableName: "order_change" })
|
||||
@VersionIndex.MikroORMIndex()
|
||||
export default class OrderChange {
|
||||
[OptionalProps]?: OptionalLineItemProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
@ManyToOne({
|
||||
entity: () => Order,
|
||||
columnType: "text",
|
||||
fieldName: "order_id",
|
||||
cascade: [Cascade.REMOVE],
|
||||
mapToPk: true,
|
||||
})
|
||||
@OrderIdIndex.MikroORMIndex()
|
||||
order_id: string
|
||||
|
||||
@ManyToOne({
|
||||
entity: () => Order,
|
||||
fieldName: "order_id",
|
||||
cascade: [Cascade.REMOVE, Cascade.PERSIST],
|
||||
@ManyToOne(() => Order, {
|
||||
persist: false,
|
||||
})
|
||||
order: Order
|
||||
|
||||
@Property({ columnType: "integer" })
|
||||
@VersionIndex.MikroORMIndex()
|
||||
version: number
|
||||
|
||||
@OneToMany(() => OrderChangeAction, (action) => action.order_change_id, {
|
||||
cascade: [Cascade.REMOVE],
|
||||
})
|
||||
@@ -131,10 +145,12 @@ export default class OrderChange {
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "ordch")
|
||||
this.order_id ??= this.order?.id
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "ordch")
|
||||
this.order_id ??= this.order?.id
|
||||
}
|
||||
}
|
||||
+50
-30
@@ -1,13 +1,12 @@
|
||||
import { BigNumberRawValue, DAL } from "@medusajs/types"
|
||||
import {
|
||||
BigNumber,
|
||||
MikroOrmBigNumberProperty,
|
||||
createPsqlIndexStatementHelper,
|
||||
generateEntityId,
|
||||
MikroOrmBigNumberProperty,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Cascade,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
@@ -15,45 +14,62 @@ import {
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import { ItemSummary } from "../types/common"
|
||||
import LineItem from "./line-item"
|
||||
import Order from "./order"
|
||||
|
||||
type OptionalLineItemProps = DAL.EntityDateColumns
|
||||
|
||||
const OrderItemVersionIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "order_detail",
|
||||
columns: ["order_id", "item_id", "version"],
|
||||
unique: true,
|
||||
const OrderIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "order_item",
|
||||
columns: ["order_id"],
|
||||
})
|
||||
|
||||
@Entity({ tableName: "order_detail" })
|
||||
@OrderItemVersionIndex.MikroORMIndex()
|
||||
export default class OrderDetail {
|
||||
const OrderVersionIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "order_item",
|
||||
columns: ["version"],
|
||||
})
|
||||
|
||||
const ItemIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "order_item",
|
||||
columns: ["item_id"],
|
||||
})
|
||||
|
||||
@Entity({ tableName: "order_item" })
|
||||
export default class OrderItem {
|
||||
[OptionalProps]?: OptionalLineItemProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
@ManyToOne({
|
||||
entity: () => Order,
|
||||
mapToPk: true,
|
||||
fieldName: "order_id",
|
||||
columnType: "text",
|
||||
})
|
||||
@OrderIdIndex.MikroORMIndex()
|
||||
order_id: string
|
||||
|
||||
@Property({ columnType: "integer" })
|
||||
@OrderVersionIndex.MikroORMIndex()
|
||||
version: number
|
||||
@ManyToOne({
|
||||
entity: () => Order,
|
||||
onDelete: "cascade",
|
||||
cascade: [Cascade.REMOVE, Cascade.PERSIST],
|
||||
|
||||
@ManyToOne(() => Order, {
|
||||
persist: false,
|
||||
})
|
||||
order: Order
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
item_id: string
|
||||
|
||||
@ManyToOne({
|
||||
entity: () => LineItem,
|
||||
onDelete: "cascade",
|
||||
cascade: [Cascade.REMOVE, Cascade.PERSIST],
|
||||
fieldName: "item_id",
|
||||
mapToPk: true,
|
||||
columnType: "text",
|
||||
})
|
||||
@ItemIdIndex.MikroORMIndex()
|
||||
item_id: string
|
||||
|
||||
@ManyToOne(() => LineItem, {
|
||||
persist: false,
|
||||
})
|
||||
item: LineItem
|
||||
|
||||
@@ -64,43 +80,43 @@ export default class OrderDetail {
|
||||
raw_quantity: BigNumberRawValue
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
fulfilled_quantity: BigNumber | number
|
||||
fulfilled_quantity: BigNumber | number = 0
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_fulfilled_quantity: BigNumberRawValue
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
shipped_quantity: BigNumber | number
|
||||
shipped_quantity: BigNumber | number = 0
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_shipped_quantity: BigNumberRawValue
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
return_requested_quantity: BigNumber | number
|
||||
return_requested_quantity: BigNumber | number = 0
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_return_requested_quantity: BigNumberRawValue
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
return_received_quantity: BigNumber | number
|
||||
return_received_quantity: BigNumber | number = 0
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_return_received_quantity: BigNumberRawValue
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
return_dismissed_quantity: BigNumber | number
|
||||
return_dismissed_quantity: BigNumber | number = 0
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_return_dismissed_quantity: BigNumberRawValue
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
written_off_quantity: BigNumber | number
|
||||
written_off_quantity: BigNumber | number = 0
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_written_off_quantity: BigNumberRawValue
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
summary: ItemSummary | null = {} as ItemSummary
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
@@ -119,11 +135,15 @@ export default class OrderDetail {
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "ordlisum")
|
||||
this.id = generateEntityId(this.id, "orditem")
|
||||
this.order_id ??= this.order?.id
|
||||
this.item_id ??= this.item?.id
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "ordlisum")
|
||||
this.id = generateEntityId(this.id, "orditem")
|
||||
this.order_id ??= this.order?.id
|
||||
this.item_id ??= this.item?.id
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from "@mikro-orm/core"
|
||||
import { OrderSummary } from "../types/common"
|
||||
import Address from "./address"
|
||||
import OrderDetail from "./order-detail"
|
||||
import OrderItem from "./order-item"
|
||||
import ShippingMethod from "./shipping-method"
|
||||
import Transaction from "./transaction"
|
||||
|
||||
@@ -160,18 +160,18 @@ export default class Order {
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@OneToMany(() => OrderDetail, (itemDetail) => itemDetail.order, {
|
||||
cascade: [Cascade.REMOVE],
|
||||
@OneToMany(() => OrderItem, (itemDetail) => itemDetail.order, {
|
||||
cascade: [Cascade.PERSIST],
|
||||
})
|
||||
items = new Collection<OrderDetail>(this)
|
||||
items = new Collection<OrderItem>(this)
|
||||
|
||||
@OneToMany(() => ShippingMethod, (shippingMethod) => shippingMethod.order, {
|
||||
cascade: [Cascade.REMOVE],
|
||||
cascade: [Cascade.PERSIST],
|
||||
})
|
||||
shipping_methods = new Collection<ShippingMethod>(this)
|
||||
|
||||
@OneToMany(() => Transaction, (transaction) => transaction.order, {
|
||||
cascade: [Cascade.REMOVE],
|
||||
cascade: [Cascade.PERSIST],
|
||||
})
|
||||
transactions = new Collection<Transaction>(this)
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
Entity,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import AdjustmentLine from "./adjustment-line"
|
||||
import ShippingMethod from "./shipping-method"
|
||||
@@ -20,24 +19,30 @@ const ShippingMethodIdIdIndex = createPsqlIndexStatementHelper({
|
||||
|
||||
@Entity({ tableName: "order_shipping_method_adjustment" })
|
||||
export default class ShippingMethodAdjustment extends AdjustmentLine {
|
||||
@ManyToOne({
|
||||
entity: () => ShippingMethod,
|
||||
fieldName: "shipping_method_id",
|
||||
cascade: [Cascade.REMOVE, Cascade.PERSIST],
|
||||
@ManyToOne(() => ShippingMethod, {
|
||||
persist: false,
|
||||
})
|
||||
shipping_method: ShippingMethod
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
@ManyToOne({
|
||||
entity: () => ShippingMethod,
|
||||
columnType: "text",
|
||||
fieldName: "shipping_method_id",
|
||||
mapToPk: true,
|
||||
cascade: [Cascade.REMOVE],
|
||||
})
|
||||
@ShippingMethodIdIdIndex.MikroORMIndex()
|
||||
shipping_method_id: string
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "ordsmadj")
|
||||
this.shipping_method_id ??= this.shipping_method?.id
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "ordsmadj")
|
||||
this.shipping_method_id ??= this.shipping_method?.id
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
Entity,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import ShippingMethod from "./shipping-method"
|
||||
import TaxLine from "./tax-line"
|
||||
@@ -20,24 +19,30 @@ const ShippingMethodIdIdIndex = createPsqlIndexStatementHelper({
|
||||
|
||||
@Entity({ tableName: "order_shipping_method_tax_line" })
|
||||
export default class ShippingMethodTaxLine extends TaxLine {
|
||||
@ManyToOne({
|
||||
entity: () => ShippingMethod,
|
||||
fieldName: "shipping_method_id",
|
||||
cascade: [Cascade.REMOVE, Cascade.PERSIST],
|
||||
@ManyToOne(() => ShippingMethod, {
|
||||
persist: false,
|
||||
})
|
||||
shipping_method: ShippingMethod
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
@ManyToOne({
|
||||
entity: () => ShippingMethod,
|
||||
fieldName: "shipping_method_id",
|
||||
columnType: "text",
|
||||
mapToPk: true,
|
||||
cascade: [Cascade.REMOVE],
|
||||
})
|
||||
@ShippingMethodIdIdIndex.MikroORMIndex()
|
||||
shipping_method_id: string
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "ordsmtxl")
|
||||
this.shipping_method_id ??= this.shipping_method?.id
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "ordsmtxl")
|
||||
this.shipping_method_id ??= this.shipping_method?.id
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
import {
|
||||
BeforeCreate,
|
||||
Cascade,
|
||||
Check,
|
||||
Collection,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
@@ -31,23 +30,41 @@ const OrderIdIndex = createPsqlIndexStatementHelper({
|
||||
columns: "order_id",
|
||||
})
|
||||
|
||||
const OrderVersionIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "order_shipping_method",
|
||||
columns: ["order_id", "version"],
|
||||
})
|
||||
|
||||
@Entity({ tableName: "order_shipping_method" })
|
||||
@Check<ShippingMethod>({ expression: (columns) => `${columns.amount} >= 0` })
|
||||
@OrderVersionIndex.MikroORMIndex()
|
||||
export default class ShippingMethod {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
@ManyToOne({
|
||||
entity: () => Order,
|
||||
columnType: "text",
|
||||
fieldName: "order_id",
|
||||
mapToPk: true,
|
||||
cascade: [Cascade.REMOVE],
|
||||
})
|
||||
@OrderIdIndex.MikroORMIndex()
|
||||
order_id: string
|
||||
|
||||
@ManyToOne({
|
||||
entity: () => Order,
|
||||
fieldName: "order_id",
|
||||
cascade: [Cascade.REMOVE, Cascade.PERSIST],
|
||||
cascade: [Cascade.REMOVE],
|
||||
persist: false,
|
||||
})
|
||||
order: Order
|
||||
|
||||
@Property({
|
||||
columnType: "integer",
|
||||
defaultRaw: "1",
|
||||
})
|
||||
version: number = 1
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
name: string
|
||||
|
||||
@@ -80,7 +97,7 @@ export default class ShippingMethod {
|
||||
() => ShippingMethodTaxLine,
|
||||
(taxLine) => taxLine.shipping_method,
|
||||
{
|
||||
cascade: [Cascade.REMOVE],
|
||||
cascade: [Cascade.PERSIST],
|
||||
}
|
||||
)
|
||||
tax_lines = new Collection<ShippingMethodTaxLine>(this)
|
||||
@@ -89,7 +106,7 @@ export default class ShippingMethod {
|
||||
() => ShippingMethodAdjustment,
|
||||
(adjustment) => adjustment.shipping_method,
|
||||
{
|
||||
cascade: [Cascade.REMOVE],
|
||||
cascade: [Cascade.PERSIST],
|
||||
}
|
||||
)
|
||||
adjustments = new Collection<ShippingMethodAdjustment>(this)
|
||||
@@ -112,9 +129,11 @@ 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
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { BigNumberRawValue, DAL } from "@medusajs/types"
|
||||
import {
|
||||
BigNumber,
|
||||
MikroOrmBigNumberProperty,
|
||||
createPsqlIndexStatementHelper,
|
||||
generateEntityId,
|
||||
MikroOrmBigNumberProperty,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
@@ -41,14 +41,18 @@ export default class Transaction {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
@ManyToOne({
|
||||
entity: () => Order,
|
||||
columnType: "text",
|
||||
fieldName: "order_id",
|
||||
cascade: [Cascade.REMOVE],
|
||||
mapToPk: true,
|
||||
})
|
||||
@OrderIdIndex.MikroORMIndex()
|
||||
order_id: string
|
||||
|
||||
@ManyToOne({
|
||||
entity: () => Order,
|
||||
fieldName: "order_id",
|
||||
cascade: [Cascade.REMOVE, Cascade.PERSIST],
|
||||
@ManyToOne(() => Order, {
|
||||
persist: false,
|
||||
})
|
||||
order: Order
|
||||
|
||||
@@ -93,10 +97,12 @@ export default class Transaction {
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "ordtrx")
|
||||
this.order_id ??= this.order?.id
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "ordtrx")
|
||||
this.order_id ??= this.order?.id
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
|
||||
export * from "./order"
|
||||
@@ -0,0 +1,114 @@
|
||||
import { Context, DAL } from "@medusajs/types"
|
||||
import { DALUtils } from "@medusajs/utils"
|
||||
import { LoadStrategy } from "@mikro-orm/core"
|
||||
import { EntityManager } from "@mikro-orm/postgresql"
|
||||
import { Order } from "@models"
|
||||
import { mapRepositoryToOrderModel } from "../utils/transform-order"
|
||||
|
||||
export class OrderRepository extends DALUtils.mikroOrmBaseRepositoryFactory<Order>(
|
||||
Order
|
||||
) {
|
||||
async find(
|
||||
options?: DAL.FindOptions<Order>,
|
||||
context?: Context
|
||||
): Promise<Order[]> {
|
||||
const manager = this.getActiveManager<EntityManager>(context)
|
||||
const knex = manager.getKnex()
|
||||
|
||||
const findOptions_ = { ...options } as any
|
||||
findOptions_.options ??= {}
|
||||
findOptions_.where ??= {}
|
||||
|
||||
if (!("strategy" in findOptions_.options)) {
|
||||
if (findOptions_.options.limit != null || findOptions_.options.offset) {
|
||||
Object.assign(findOptions_.options, {
|
||||
strategy: LoadStrategy.SELECT_IN,
|
||||
})
|
||||
} else {
|
||||
Object.assign(findOptions_.options, {
|
||||
strategy: LoadStrategy.JOINED,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const config = mapRepositoryToOrderModel(findOptions_)
|
||||
|
||||
const expandDetails = config.options.populate?.filter((p) =>
|
||||
p.includes("items")
|
||||
)?.length
|
||||
|
||||
// If no version is specified, we default to the latest version
|
||||
if (expandDetails) {
|
||||
let defaultVersion = knex.raw(`"o0"."version"`)
|
||||
const strategy = config.options.strategy ?? LoadStrategy.JOINED
|
||||
if (strategy === LoadStrategy.SELECT_IN) {
|
||||
const sql = manager
|
||||
.qb(Order, "_sub0")
|
||||
.select("version")
|
||||
.where({ id: knex.raw(`"o0"."order_id"`) })
|
||||
.getKnexQuery()
|
||||
.toString()
|
||||
|
||||
defaultVersion = knex.raw(`(${sql})`)
|
||||
}
|
||||
|
||||
const version = config.where.version ?? defaultVersion
|
||||
delete config.where?.version
|
||||
|
||||
config.options.populateWhere ??= {}
|
||||
config.options.populateWhere.items ??= {}
|
||||
config.options.populateWhere.items.version = version
|
||||
}
|
||||
|
||||
return await manager.find(Order, config.where, config.options)
|
||||
}
|
||||
|
||||
async findAndCount(
|
||||
findOptions: DAL.FindOptions<Order> = { where: {} },
|
||||
context: Context = {}
|
||||
): Promise<[Order[], number]> {
|
||||
const manager = this.getActiveManager<EntityManager>(context)
|
||||
const knex = manager.getKnex()
|
||||
|
||||
const findOptions_ = { ...findOptions } as any
|
||||
findOptions_.options ??= {}
|
||||
findOptions_.where ??= {}
|
||||
|
||||
if (!("strategy" in findOptions_.options)) {
|
||||
Object.assign(findOptions_.options, {
|
||||
strategy: LoadStrategy.SELECT_IN,
|
||||
})
|
||||
}
|
||||
|
||||
const config = mapRepositoryToOrderModel(findOptions_)
|
||||
|
||||
const expandDetails = config.options.populate?.filter((p) =>
|
||||
p.includes("items")
|
||||
)?.length
|
||||
|
||||
// If no version is specified, we default to the latest version
|
||||
if (expandDetails) {
|
||||
let defaultVersion = knex.raw(`"o0"."version"`)
|
||||
const strategy = config.options.strategy ?? LoadStrategy.JOINED
|
||||
if (strategy === LoadStrategy.SELECT_IN) {
|
||||
const sql = manager
|
||||
.qb(Order, "_sub0")
|
||||
.select("version")
|
||||
.where({ id: knex.raw(`"o0"."order_id"`) })
|
||||
.getKnexQuery()
|
||||
.toString()
|
||||
|
||||
defaultVersion = knex.raw(`(${sql})`)
|
||||
}
|
||||
|
||||
const version = config.where.version ?? defaultVersion
|
||||
delete config.where.version
|
||||
|
||||
config.options.populateWhere ??= {}
|
||||
config.options.populateWhere.items ??= {}
|
||||
config.options.populateWhere.items.version = version
|
||||
}
|
||||
|
||||
return await manager.findAndCount(Order, config.where, config.options)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
describe("noop", function () {
|
||||
it("should run", function () {
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,135 @@
|
||||
import { OrderChangeEvent } from "../../../../types"
|
||||
import { ChangeActionType, calculateOrderChange } from "../../../../utils"
|
||||
|
||||
describe("Order Exchange - Actions", function () {
|
||||
const originalOrder = {
|
||||
items: [
|
||||
{
|
||||
id: "1",
|
||||
quantity: 1,
|
||||
unit_price: 10,
|
||||
|
||||
fulfilled_quantity: 1,
|
||||
return_requested_quantity: 0,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 0,
|
||||
written_off_quantity: 0,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
quantity: 2,
|
||||
unit_price: 100,
|
||||
|
||||
fulfilled_quantity: 1,
|
||||
return_requested_quantity: 0,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 0,
|
||||
written_off_quantity: 0,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
quantity: 3,
|
||||
unit_price: 20,
|
||||
|
||||
fulfilled_quantity: 3,
|
||||
return_requested_quantity: 0,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 0,
|
||||
written_off_quantity: 0,
|
||||
},
|
||||
],
|
||||
shipping_methods: [
|
||||
{
|
||||
id: "ship_123",
|
||||
price: 0,
|
||||
},
|
||||
],
|
||||
total: 270,
|
||||
shipping_total: 0,
|
||||
}
|
||||
|
||||
it("should perform an item exchage", function () {
|
||||
const actions = [
|
||||
{
|
||||
action: ChangeActionType.RETURN_ITEM,
|
||||
reference_id: "return_123",
|
||||
details: {
|
||||
reference_id: "3",
|
||||
quantity: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
action: ChangeActionType.ITEM_ADD,
|
||||
reference_id: "item_555",
|
||||
details: {
|
||||
unit_price: 50,
|
||||
quantity: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
action: ChangeActionType.SHIPPING_ADD,
|
||||
reference_id: "shipping_345",
|
||||
amount: 5,
|
||||
},
|
||||
{
|
||||
action: ChangeActionType.SHIPPING_ADD,
|
||||
reference_id: "return_shipping_345",
|
||||
amount: 7.5,
|
||||
},
|
||||
] as OrderChangeEvent[]
|
||||
|
||||
const changes = calculateOrderChange({
|
||||
order: originalOrder,
|
||||
actions: actions,
|
||||
})
|
||||
|
||||
expect(changes.summary).toEqual({
|
||||
transactionTotal: 0,
|
||||
originalOrderTotal: 270,
|
||||
currentOrderTotal: 312.5,
|
||||
temporaryDifference: 62.5,
|
||||
futureDifference: 0,
|
||||
futureTemporaryDifference: 0,
|
||||
pendingDifference: 312.5,
|
||||
differenceSum: 42.5,
|
||||
})
|
||||
|
||||
expect(changes.order.items).toEqual([
|
||||
{
|
||||
id: "1",
|
||||
quantity: 1,
|
||||
unit_price: 10,
|
||||
fulfilled_quantity: 1,
|
||||
return_requested_quantity: 0,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 0,
|
||||
written_off_quantity: 0,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
quantity: 2,
|
||||
unit_price: 100,
|
||||
fulfilled_quantity: 1,
|
||||
return_requested_quantity: 0,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 0,
|
||||
written_off_quantity: 0,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
quantity: 3,
|
||||
unit_price: 20,
|
||||
fulfilled_quantity: 3,
|
||||
return_requested_quantity: 1,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 0,
|
||||
written_off_quantity: 0,
|
||||
},
|
||||
{
|
||||
id: "item_555",
|
||||
unit_price: 50,
|
||||
quantity: 1,
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,261 @@
|
||||
import { OrderChangeEvent } from "../../../../types"
|
||||
import { ChangeActionType, calculateOrderChange } from "../../../../utils"
|
||||
|
||||
describe("Order Return - Actions", function () {
|
||||
const originalOrder = {
|
||||
items: [
|
||||
{
|
||||
id: "1",
|
||||
quantity: 1,
|
||||
unit_price: 10,
|
||||
|
||||
fulfilled_quantity: 1,
|
||||
return_requested_quantity: 0,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 0,
|
||||
written_off_quantity: 0,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
quantity: 2,
|
||||
unit_price: 100,
|
||||
|
||||
fulfilled_quantity: 1,
|
||||
return_requested_quantity: 0,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 0,
|
||||
written_off_quantity: 0,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
quantity: 3,
|
||||
unit_price: 20,
|
||||
|
||||
fulfilled_quantity: 3,
|
||||
return_requested_quantity: 0,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 0,
|
||||
written_off_quantity: 0,
|
||||
},
|
||||
],
|
||||
shipping_methods: [
|
||||
{
|
||||
id: "ship_123",
|
||||
price: 0,
|
||||
},
|
||||
],
|
||||
total: 270,
|
||||
shipping_total: 0,
|
||||
}
|
||||
|
||||
it("should validate return requests", function () {
|
||||
const actions = [
|
||||
{
|
||||
action: ChangeActionType.RETURN_ITEM,
|
||||
reference_id: "return_123",
|
||||
details: {
|
||||
reference_id: "1",
|
||||
quantity: 1,
|
||||
},
|
||||
},
|
||||
] as OrderChangeEvent[]
|
||||
|
||||
expect(() => {
|
||||
actions[0].details!.quantity = 2
|
||||
calculateOrderChange({
|
||||
order: originalOrder,
|
||||
actions,
|
||||
})
|
||||
}).toThrow("Cannot request to return more items than what was fulfilled.")
|
||||
|
||||
expect(() => {
|
||||
actions[0].details!.reference_id = undefined
|
||||
calculateOrderChange({
|
||||
order: originalOrder,
|
||||
actions,
|
||||
})
|
||||
}).toThrow("Details reference ID is required.")
|
||||
|
||||
expect(() => {
|
||||
actions[0].details!.reference_id = "333"
|
||||
calculateOrderChange({
|
||||
order: originalOrder,
|
||||
actions,
|
||||
})
|
||||
}).toThrow(`Reference ID "333" not found.`)
|
||||
})
|
||||
|
||||
it("should validate return received", function () {
|
||||
const [] = []
|
||||
const actions = [
|
||||
{
|
||||
action: ChangeActionType.RETURN_ITEM,
|
||||
reference_id: "return_123",
|
||||
details: {
|
||||
reference_id: "2",
|
||||
quantity: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
action: ChangeActionType.RETURN_ITEM,
|
||||
reference_id: "return_123",
|
||||
details: {
|
||||
reference_id: "3",
|
||||
quantity: 2,
|
||||
},
|
||||
},
|
||||
] as OrderChangeEvent[]
|
||||
|
||||
const changes = calculateOrderChange({
|
||||
order: originalOrder,
|
||||
actions,
|
||||
})
|
||||
|
||||
expect(changes.order.items).toEqual([
|
||||
{
|
||||
id: "1",
|
||||
quantity: 1,
|
||||
unit_price: 10,
|
||||
fulfilled_quantity: 1,
|
||||
return_requested_quantity: 0,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 0,
|
||||
written_off_quantity: 0,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
quantity: 2,
|
||||
unit_price: 100,
|
||||
fulfilled_quantity: 1,
|
||||
return_requested_quantity: 1,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 0,
|
||||
written_off_quantity: 0,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
quantity: 3,
|
||||
unit_price: 20,
|
||||
fulfilled_quantity: 3,
|
||||
return_requested_quantity: 2,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 0,
|
||||
written_off_quantity: 0,
|
||||
},
|
||||
])
|
||||
|
||||
const modifiedOrder = {
|
||||
...originalOrder,
|
||||
items: [...changes.order.items],
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
calculateOrderChange({
|
||||
order: modifiedOrder,
|
||||
actions: [
|
||||
{
|
||||
action: ChangeActionType.RETURN_ITEM,
|
||||
details: {
|
||||
reference_id: "3",
|
||||
quantity: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}).toThrow("Cannot request to return more items than what was fulfilled.")
|
||||
|
||||
expect(() => {
|
||||
calculateOrderChange({
|
||||
order: modifiedOrder,
|
||||
actions: [
|
||||
{
|
||||
action: ChangeActionType.RECEIVE_RETURN_ITEM,
|
||||
details: {
|
||||
reference_id: "3",
|
||||
quantity: 3,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}).toThrow(
|
||||
"Cannot receive more items than what was requested to be returned."
|
||||
)
|
||||
|
||||
expect(() => {
|
||||
calculateOrderChange({
|
||||
order: modifiedOrder,
|
||||
actions: [
|
||||
{
|
||||
action: ChangeActionType.RECEIVE_RETURN_ITEM,
|
||||
details: {
|
||||
reference_id: "3",
|
||||
quantity: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
action: ChangeActionType.RECEIVE_DAMAGED_RETURN_ITEM,
|
||||
details: {
|
||||
reference_id: "3",
|
||||
quantity: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}).toThrow(
|
||||
"Cannot receive more items than what was requested to be returned."
|
||||
)
|
||||
|
||||
const receivedChanges = calculateOrderChange({
|
||||
order: modifiedOrder,
|
||||
actions: [
|
||||
{
|
||||
action: ChangeActionType.RECEIVE_RETURN_ITEM,
|
||||
details: {
|
||||
reference_id: "3",
|
||||
quantity: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
action: ChangeActionType.RECEIVE_DAMAGED_RETURN_ITEM,
|
||||
details: {
|
||||
reference_id: "3",
|
||||
quantity: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
expect(receivedChanges.order.items).toEqual([
|
||||
{
|
||||
id: "1",
|
||||
quantity: 1,
|
||||
unit_price: 10,
|
||||
fulfilled_quantity: 1,
|
||||
return_requested_quantity: 0,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 0,
|
||||
written_off_quantity: 0,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
quantity: 2,
|
||||
unit_price: 100,
|
||||
fulfilled_quantity: 1,
|
||||
return_requested_quantity: 1,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 0,
|
||||
written_off_quantity: 0,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
quantity: 3,
|
||||
unit_price: 20,
|
||||
fulfilled_quantity: 3,
|
||||
return_requested_quantity: 0,
|
||||
return_received_quantity: 1,
|
||||
return_dismissed_quantity: 1,
|
||||
written_off_quantity: 0,
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -2,20 +2,22 @@ import {
|
||||
Context,
|
||||
DAL,
|
||||
FilterableLineItemTaxLineProps,
|
||||
IOrderModuleService,
|
||||
FindConfig,
|
||||
InternalModuleDeclaration,
|
||||
IOrderModuleService,
|
||||
ModuleJoinerConfig,
|
||||
ModulesSdkTypes,
|
||||
OrderTypes,
|
||||
UpdateOrderItemWithSelectorDTO,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
isObject,
|
||||
isString,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
ModulesSdkUtils,
|
||||
isObject,
|
||||
isString,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
Address,
|
||||
@@ -25,22 +27,25 @@ import {
|
||||
Order,
|
||||
OrderChange,
|
||||
OrderChangeAction,
|
||||
OrderDetail,
|
||||
OrderItem,
|
||||
ShippingMethod,
|
||||
ShippingMethodAdjustment,
|
||||
ShippingMethodTaxLine,
|
||||
Transaction,
|
||||
} from "@models"
|
||||
import {
|
||||
CreateOrderItemDTO,
|
||||
CreateOrderLineItemDTO,
|
||||
CreateOrderLineItemTaxLineDTO,
|
||||
CreateOrderShippingMethodDTO,
|
||||
CreateOrderShippingMethodTaxLineDTO,
|
||||
UpdateOrderItemDTO,
|
||||
UpdateOrderLineItemDTO,
|
||||
UpdateOrderLineItemTaxLineDTO,
|
||||
UpdateOrderShippingMethodTaxLineDTO,
|
||||
} from "@types"
|
||||
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
||||
import { formatOrder } from "../utils/transform-order"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
@@ -55,7 +60,7 @@ type InjectedDependencies = {
|
||||
transactionService: ModulesSdkTypes.InternalModuleService<any>
|
||||
orderChangeService: ModulesSdkTypes.InternalModuleService<any>
|
||||
orderChangeActionService: ModulesSdkTypes.InternalModuleService<any>
|
||||
orderDetailService: ModulesSdkTypes.InternalModuleService<any>
|
||||
orderItemService: ModulesSdkTypes.InternalModuleService<any>
|
||||
}
|
||||
|
||||
const generateMethodForModels = [
|
||||
@@ -69,7 +74,7 @@ const generateMethodForModels = [
|
||||
Transaction,
|
||||
OrderChange,
|
||||
OrderChangeAction,
|
||||
OrderDetail,
|
||||
OrderItem,
|
||||
]
|
||||
|
||||
export default class OrderModuleService<
|
||||
@@ -84,7 +89,7 @@ export default class OrderModuleService<
|
||||
TTransaction extends Transaction = Transaction,
|
||||
TOrderChange extends OrderChange = OrderChange,
|
||||
TOrderChangeAction extends OrderChangeAction = OrderChangeAction,
|
||||
TOrderDetail extends OrderDetail = OrderDetail
|
||||
TOrderItem extends OrderItem = OrderItem
|
||||
>
|
||||
extends ModulesSdkUtils.abstractModuleServiceFactory<
|
||||
InjectedDependencies,
|
||||
@@ -102,6 +107,7 @@ export default class OrderModuleService<
|
||||
Transaction: { dto: OrderTypes.OrderTransactionDTO }
|
||||
Change: { dto: OrderTypes.OrderChangeDTO }
|
||||
ChangeAction: { dto: OrderTypes.OrderChangeActionDTO }
|
||||
OrderItem: { dto: OrderTypes.OrderItemDTO }
|
||||
}
|
||||
>(Order, generateMethodForModels, entityNameToLinkableKeysMap)
|
||||
implements IOrderModuleService
|
||||
@@ -118,7 +124,7 @@ export default class OrderModuleService<
|
||||
protected transactionService_: ModulesSdkTypes.InternalModuleService<TTransaction>
|
||||
protected orderChangeService_: ModulesSdkTypes.InternalModuleService<TOrderChange>
|
||||
protected orderChangeActionService_: ModulesSdkTypes.InternalModuleService<TOrderChangeAction>
|
||||
protected orderDetailService_: ModulesSdkTypes.InternalModuleService<TOrderDetail>
|
||||
protected orderItemService_: ModulesSdkTypes.InternalModuleService<TOrderItem>
|
||||
|
||||
constructor(
|
||||
{
|
||||
@@ -134,7 +140,7 @@ export default class OrderModuleService<
|
||||
transactionService,
|
||||
orderChangeService,
|
||||
orderChangeActionService,
|
||||
orderDetailService,
|
||||
orderItemService,
|
||||
}: InjectedDependencies,
|
||||
protected readonly moduleDeclaration: InternalModuleDeclaration
|
||||
) {
|
||||
@@ -153,13 +159,47 @@ export default class OrderModuleService<
|
||||
this.transactionService_ = transactionService
|
||||
this.orderChangeService_ = orderChangeService
|
||||
this.orderChangeActionService_ = orderChangeActionService
|
||||
this.orderDetailService_ = orderDetailService
|
||||
this.orderItemService_ = orderItemService
|
||||
}
|
||||
|
||||
__joinerConfig(): ModuleJoinerConfig {
|
||||
return joinerConfig
|
||||
}
|
||||
|
||||
async retrieve(
|
||||
id: string,
|
||||
config?: FindConfig<any> | undefined,
|
||||
sharedContext?: Context | undefined
|
||||
): Promise<OrderTypes.OrderDTO> {
|
||||
const order = await super.retrieve(id, config, sharedContext)
|
||||
|
||||
return formatOrder(order) as OrderTypes.OrderDTO
|
||||
}
|
||||
|
||||
async list(
|
||||
filters?: any,
|
||||
config?: FindConfig<any> | undefined,
|
||||
sharedContext?: Context | undefined
|
||||
): Promise<OrderTypes.OrderDTO[]> {
|
||||
const orders = await super.list(filters, config, sharedContext)
|
||||
|
||||
return formatOrder(orders) as OrderTypes.OrderDTO[]
|
||||
}
|
||||
|
||||
async listAndCount(
|
||||
filters?: any,
|
||||
config?: FindConfig<any> | undefined,
|
||||
sharedContext?: Context | undefined
|
||||
): Promise<[OrderTypes.OrderDTO[], number]> {
|
||||
const [orders, count] = await super.listAndCount(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return [formatOrder(orders) as OrderTypes.OrderDTO[], count]
|
||||
}
|
||||
|
||||
async create(
|
||||
data: OrderTypes.CreateOrderDTO[],
|
||||
sharedContext?: Context
|
||||
@@ -180,9 +220,20 @@ export default class OrderModuleService<
|
||||
const orders = await this.create_(input, sharedContext)
|
||||
|
||||
const result = await this.list(
|
||||
{ id: orders.map((p) => p!.id) },
|
||||
{
|
||||
relations: ["shipping_address", "billing_address"],
|
||||
id: orders.map((p) => p!.id),
|
||||
},
|
||||
{
|
||||
relations: [
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
"items",
|
||||
"items.tax_lines",
|
||||
"items.adjustments",
|
||||
"shipping_methods",
|
||||
"shipping_methods.tax_lines",
|
||||
"shipping_methods.adjustments",
|
||||
],
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
@@ -198,9 +249,10 @@ export default class OrderModuleService<
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
const lineItemsToCreate: CreateOrderLineItemDTO[] = []
|
||||
|
||||
const createdOrders: Order[] = []
|
||||
for (const { items, ...order } of data) {
|
||||
const [created] = await this.orderService_.create([order], sharedContext)
|
||||
const created = await this.orderService_.create(order, sharedContext)
|
||||
|
||||
createdOrders.push(created)
|
||||
|
||||
@@ -224,42 +276,75 @@ export default class OrderModuleService<
|
||||
}
|
||||
|
||||
async update(
|
||||
data: OrderTypes.UpdateOrderDTO[],
|
||||
sharedContext?: Context
|
||||
data: OrderTypes.UpdateOrderDTO[]
|
||||
): Promise<OrderTypes.OrderDTO[]>
|
||||
|
||||
async update(
|
||||
orderId: string,
|
||||
data: OrderTypes.UpdateOrderDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<OrderTypes.OrderDTO>
|
||||
async update(
|
||||
selector: Partial<OrderTypes.OrderDTO>,
|
||||
data: OrderTypes.UpdateOrderDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<OrderTypes.OrderDTO[]>
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async update(
|
||||
data: OrderTypes.UpdateOrderDTO[] | OrderTypes.UpdateOrderDTO,
|
||||
dataOrIdOrSelector:
|
||||
| OrderTypes.UpdateOrderDTO[]
|
||||
| string
|
||||
| Partial<OrderTypes.OrderDTO>,
|
||||
data?: OrderTypes.UpdateOrderDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<OrderTypes.OrderDTO[] | OrderTypes.OrderDTO> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
const orders = await this.update_(input, sharedContext)
|
||||
const result = await this.update_(dataOrIdOrSelector, data, sharedContext)
|
||||
|
||||
const result = await this.list(
|
||||
{ id: orders.map((p) => p!.id) },
|
||||
{
|
||||
relations: ["shipping_address", "billing_address"],
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
const serializedResult = await this.baseRepository_.serialize<
|
||||
OrderTypes.OrderDTO[]
|
||||
>(result, {
|
||||
populate: true,
|
||||
})
|
||||
|
||||
return (Array.isArray(data) ? result : result[0]) as
|
||||
| OrderTypes.OrderDTO
|
||||
| OrderTypes.OrderDTO[]
|
||||
return isString(dataOrIdOrSelector) ? serializedResult[0] : serializedResult
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
protected async update_(
|
||||
data: OrderTypes.UpdateOrderDTO[],
|
||||
dataOrIdOrSelector:
|
||||
| OrderTypes.UpdateOrderDTO[]
|
||||
| string
|
||||
| Partial<OrderTypes.OrderDTO>,
|
||||
data?: OrderTypes.UpdateOrderDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
return await this.orderService_.update(data, sharedContext)
|
||||
let toUpdate: OrderTypes.UpdateOrderDTO[] = []
|
||||
if (isString(dataOrIdOrSelector)) {
|
||||
toUpdate = [
|
||||
{
|
||||
id: dataOrIdOrSelector,
|
||||
...data,
|
||||
},
|
||||
]
|
||||
} else if (Array.isArray(dataOrIdOrSelector)) {
|
||||
toUpdate = dataOrIdOrSelector
|
||||
} else {
|
||||
const orders = await this.orderService_.list(
|
||||
{ ...dataOrIdOrSelector },
|
||||
{ select: ["id"] },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
toUpdate = orders.map((order) => {
|
||||
return {
|
||||
...data,
|
||||
id: order.id,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const result = await this.orderService_.update(toUpdate, sharedContext)
|
||||
return result
|
||||
}
|
||||
|
||||
addLineItems(
|
||||
@@ -315,7 +400,7 @@ export default class OrderModuleService<
|
||||
): Promise<LineItem[]> {
|
||||
const order = await this.retrieve(
|
||||
orderId,
|
||||
{ select: ["id"] },
|
||||
{ select: ["id", "version"] },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
@@ -323,6 +408,7 @@ export default class OrderModuleService<
|
||||
return {
|
||||
...item,
|
||||
order_id: order.id,
|
||||
version: order.version,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -334,7 +420,25 @@ export default class OrderModuleService<
|
||||
data: CreateOrderLineItemDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<LineItem[]> {
|
||||
return await this.lineItemService_.create(data, sharedContext)
|
||||
const orderItemToCreate: CreateOrderItemDTO[] = []
|
||||
|
||||
const lineItems = await this.lineItemService_.create(data, sharedContext)
|
||||
|
||||
for (let i = 0; i < lineItems.length; i++) {
|
||||
const item = lineItems[i]
|
||||
const toCreate = data[i]
|
||||
|
||||
orderItemToCreate.push({
|
||||
order_id: toCreate.order_id,
|
||||
version: toCreate.version ?? 1,
|
||||
item_id: item.id,
|
||||
quantity: toCreate.quantity,
|
||||
})
|
||||
}
|
||||
|
||||
await this.orderItemService_.create(orderItemToCreate, sharedContext)
|
||||
|
||||
return lineItems
|
||||
}
|
||||
|
||||
updateLineItems(
|
||||
@@ -408,6 +512,18 @@ export default class OrderModuleService<
|
||||
sharedContext
|
||||
)
|
||||
|
||||
if ("quantity" in data) {
|
||||
await this.updateOrderItemWithSelector_(
|
||||
[
|
||||
{
|
||||
selector: { item_id: item.id },
|
||||
data,
|
||||
},
|
||||
],
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
@@ -417,6 +533,7 @@ export default class OrderModuleService<
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<LineItem[]> {
|
||||
let toUpdate: UpdateOrderLineItemDTO[] = []
|
||||
const detailsToUpdate: UpdateOrderItemWithSelectorDTO[] = []
|
||||
for (const { selector, data } of updates) {
|
||||
const items = await this.listLineItems({ ...selector }, {}, sharedContext)
|
||||
|
||||
@@ -425,12 +542,118 @@ export default class OrderModuleService<
|
||||
...data,
|
||||
id: item.id,
|
||||
})
|
||||
|
||||
if ("quantity" in data) {
|
||||
detailsToUpdate.push({
|
||||
selector: { item_id: item.id },
|
||||
data,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (detailsToUpdate.length) {
|
||||
await this.updateOrderItemWithSelector_(detailsToUpdate, sharedContext)
|
||||
}
|
||||
|
||||
return await this.lineItemService_.update(toUpdate, sharedContext)
|
||||
}
|
||||
|
||||
updateOrderItem(
|
||||
selector: Partial<OrderTypes.OrderItemDTO>,
|
||||
data: OrderTypes.UpdateOrderItemDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<OrderTypes.OrderItemDTO[]>
|
||||
updateOrderItem(
|
||||
orderItemId: string,
|
||||
data: Partial<OrderTypes.UpdateOrderItemDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<OrderTypes.OrderItemDTO>
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async updateOrderItem(
|
||||
orderItemIdOrDataOrSelector:
|
||||
| string
|
||||
| OrderTypes.UpdateOrderItemWithSelectorDTO[]
|
||||
| Partial<OrderTypes.OrderItemDTO>,
|
||||
data?:
|
||||
| OrderTypes.UpdateOrderItemDTO
|
||||
| Partial<OrderTypes.UpdateOrderItemDTO>,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<OrderTypes.OrderItemDTO[] | OrderTypes.OrderItemDTO> {
|
||||
let items: OrderItem[] = []
|
||||
if (isString(orderItemIdOrDataOrSelector)) {
|
||||
const item = await this.updateOrderItem_(
|
||||
orderItemIdOrDataOrSelector,
|
||||
data as Partial<OrderTypes.UpdateOrderItemDTO>,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.baseRepository_.serialize<OrderTypes.OrderItemDTO>(
|
||||
item,
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const toUpdate = Array.isArray(orderItemIdOrDataOrSelector)
|
||||
? orderItemIdOrDataOrSelector
|
||||
: [
|
||||
{
|
||||
selector: orderItemIdOrDataOrSelector,
|
||||
data: data,
|
||||
} as OrderTypes.UpdateOrderItemWithSelectorDTO,
|
||||
]
|
||||
|
||||
items = await this.updateOrderItemWithSelector_(toUpdate, sharedContext)
|
||||
|
||||
return await this.baseRepository_.serialize<OrderTypes.OrderItemDTO[]>(
|
||||
items,
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
protected async updateOrderItem_(
|
||||
orderItemId: string,
|
||||
data: Partial<OrderTypes.UpdateOrderItemDTO>,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<OrderItem> {
|
||||
const [detail] = await this.orderItemService_.update(
|
||||
[{ id: orderItemId, ...data }],
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return detail
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
protected async updateOrderItemWithSelector_(
|
||||
updates: OrderTypes.UpdateOrderItemWithSelectorDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<OrderItem[]> {
|
||||
let toUpdate: UpdateOrderItemDTO[] = []
|
||||
for (const { selector, data } of updates) {
|
||||
const details = await this.listOrderItems(
|
||||
{ ...selector },
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
details.forEach((detail) => {
|
||||
toUpdate.push({
|
||||
...data,
|
||||
id: detail.id,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return await this.orderItemService_.update(toUpdate, sharedContext)
|
||||
}
|
||||
|
||||
async removeLineItems(
|
||||
itemIds: string[],
|
||||
sharedContext?: Context
|
||||
@@ -545,7 +768,7 @@ export default class OrderModuleService<
|
||||
): Promise<OrderTypes.OrderShippingMethodDTO[]>
|
||||
async addShippingMethods(
|
||||
orderId: string,
|
||||
methods: OrderTypes.CreateOrderShippingMethodForSingleOrderDTO[],
|
||||
methods: OrderTypes.CreateOrderShippingMethodDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<OrderTypes.OrderShippingMethodDTO[]>
|
||||
|
||||
@@ -555,9 +778,7 @@ export default class OrderModuleService<
|
||||
| string
|
||||
| OrderTypes.CreateOrderShippingMethodDTO[]
|
||||
| OrderTypes.CreateOrderShippingMethodDTO,
|
||||
data?:
|
||||
| OrderTypes.CreateOrderShippingMethodDTO[]
|
||||
| OrderTypes.CreateOrderShippingMethodForSingleOrderDTO[],
|
||||
data?: OrderTypes.CreateOrderShippingMethodDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<
|
||||
OrderTypes.OrderShippingMethodDTO[] | OrderTypes.OrderShippingMethodDTO
|
||||
@@ -566,7 +787,7 @@ export default class OrderModuleService<
|
||||
if (isString(orderIdOrData)) {
|
||||
methods = await this.addShippingMethods_(
|
||||
orderIdOrData,
|
||||
data as OrderTypes.CreateOrderShippingMethodForSingleOrderDTO[],
|
||||
data!,
|
||||
sharedContext
|
||||
)
|
||||
} else {
|
||||
@@ -587,7 +808,7 @@ export default class OrderModuleService<
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
protected async addShippingMethods_(
|
||||
orderId: string,
|
||||
data: OrderTypes.CreateOrderShippingMethodForSingleOrderDTO[],
|
||||
data: OrderTypes.CreateOrderShippingMethodDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ShippingMethod[]> {
|
||||
const order = await this.retrieve(
|
||||
@@ -600,6 +821,7 @@ export default class OrderModuleService<
|
||||
return {
|
||||
...method,
|
||||
order_id: order.id,
|
||||
version: order.version ?? 1,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -682,7 +904,7 @@ export default class OrderModuleService<
|
||||
if (isString(orderIdOrData)) {
|
||||
const order = await this.retrieve(
|
||||
orderIdOrData,
|
||||
{ select: ["id"], relations: ["items"] },
|
||||
{ select: ["id"], relations: ["items.item"] },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
@@ -730,15 +952,14 @@ export default class OrderModuleService<
|
||||
): Promise<OrderTypes.OrderLineItemAdjustmentDTO[]> {
|
||||
const order = await this.retrieve(
|
||||
orderId,
|
||||
{ select: ["id"], relations: ["items.adjustments"] },
|
||||
{ select: ["id"], relations: ["items.item.adjustments"] },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const existingAdjustments = await this.listLineItemAdjustments(
|
||||
{ item: { order_id: order.id } },
|
||||
{ select: ["id"] },
|
||||
sharedContext
|
||||
)
|
||||
const existingAdjustments = (order.items ?? [])
|
||||
.map((item) => item.adjustments ?? [])
|
||||
.flat()
|
||||
.map((adjustment) => adjustment.id)
|
||||
|
||||
const adjustmentsSet = new Set(
|
||||
adjustments
|
||||
@@ -746,22 +967,17 @@ export default class OrderModuleService<
|
||||
.filter(Boolean)
|
||||
)
|
||||
|
||||
const toDelete: OrderTypes.OrderLineItemAdjustmentDTO[] = []
|
||||
const toDelete: string[] = []
|
||||
|
||||
// From the existing adjustments, find the ones that are not passed in adjustments
|
||||
existingAdjustments.forEach(
|
||||
(adj: OrderTypes.OrderLineItemAdjustmentDTO) => {
|
||||
if (!adjustmentsSet.has(adj.id)) {
|
||||
toDelete.push(adj)
|
||||
}
|
||||
existingAdjustments.forEach((adj) => {
|
||||
if (!adjustmentsSet.has(adj)) {
|
||||
toDelete.push(adj)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
if (toDelete.length) {
|
||||
await this.lineItemAdjustmentService_.delete(
|
||||
toDelete.map((adj) => adj!.id),
|
||||
sharedContext
|
||||
)
|
||||
await this.lineItemAdjustmentService_.delete(toDelete, sharedContext)
|
||||
}
|
||||
|
||||
let result = await this.lineItemAdjustmentService_.upsert(
|
||||
@@ -831,11 +1047,10 @@ export default class OrderModuleService<
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const existingAdjustments = await this.listShippingMethodAdjustments(
|
||||
{ shipping_method: { order_id: order.id } },
|
||||
{ select: ["id"] },
|
||||
sharedContext
|
||||
)
|
||||
const existingAdjustments = (order.shipping_methods ?? [])
|
||||
.map((shippingMethod) => shippingMethod.adjustments ?? [])
|
||||
.flat()
|
||||
.map((adjustment) => adjustment.id)
|
||||
|
||||
const adjustmentsSet = new Set(
|
||||
adjustments
|
||||
@@ -845,20 +1060,18 @@ export default class OrderModuleService<
|
||||
.filter(Boolean)
|
||||
)
|
||||
|
||||
const toDelete: OrderTypes.OrderShippingMethodAdjustmentDTO[] = []
|
||||
const toDelete: string[] = []
|
||||
|
||||
// From the existing adjustments, find the ones that are not passed in adjustments
|
||||
existingAdjustments.forEach(
|
||||
(adj: OrderTypes.OrderShippingMethodAdjustmentDTO) => {
|
||||
if (!adjustmentsSet.has(adj.id)) {
|
||||
toDelete.push(adj)
|
||||
}
|
||||
existingAdjustments.forEach((adj) => {
|
||||
if (!adjustmentsSet.has(adj)) {
|
||||
toDelete.push(adj)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
if (toDelete.length) {
|
||||
await this.shippingMethodAdjustmentService_.delete(
|
||||
toDelete.map((adj) => adj!.id),
|
||||
toDelete,
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
@@ -1018,9 +1231,6 @@ export default class OrderModuleService<
|
||||
> {
|
||||
let addedTaxLines: LineItemTaxLine[]
|
||||
if (isString(orderIdOrData)) {
|
||||
// existence check
|
||||
await this.retrieve(orderIdOrData, { select: ["id"] }, sharedContext)
|
||||
|
||||
const lines = Array.isArray(taxLines) ? taxLines : [taxLines]
|
||||
|
||||
addedTaxLines = await this.lineItemTaxLineService_.create(
|
||||
@@ -1062,15 +1272,14 @@ export default class OrderModuleService<
|
||||
): Promise<OrderTypes.OrderLineItemTaxLineDTO[]> {
|
||||
const order = await this.retrieve(
|
||||
orderId,
|
||||
{ select: ["id"], relations: ["items.tax_lines"] },
|
||||
{ select: ["id"], relations: ["items.item.tax_lines"] },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const existingTaxLines = await this.listLineItemTaxLines(
|
||||
{ item: { order_id: order.id } },
|
||||
{ select: ["id"] },
|
||||
sharedContext
|
||||
)
|
||||
const existingTaxLines = (order.items ?? [])
|
||||
.map((item) => item.tax_lines ?? [])
|
||||
.flat()
|
||||
.map((taxLine) => taxLine.id)
|
||||
|
||||
const taxLinesSet = new Set(
|
||||
taxLines
|
||||
@@ -1080,20 +1289,15 @@ export default class OrderModuleService<
|
||||
.filter(Boolean)
|
||||
)
|
||||
|
||||
const toDelete: OrderTypes.OrderLineItemTaxLineDTO[] = []
|
||||
|
||||
// From the existing tax lines, find the ones that are not passed in taxLines
|
||||
existingTaxLines.forEach((taxLine: OrderTypes.OrderLineItemTaxLineDTO) => {
|
||||
if (!taxLinesSet.has(taxLine.id)) {
|
||||
const toDelete: string[] = []
|
||||
existingTaxLines.forEach((taxLine: string) => {
|
||||
if (!taxLinesSet.has(taxLine)) {
|
||||
toDelete.push(taxLine)
|
||||
}
|
||||
})
|
||||
|
||||
if (toDelete.length) {
|
||||
await this.lineItemTaxLineService_.delete(
|
||||
toDelete.map((taxLine) => taxLine!.id),
|
||||
sharedContext
|
||||
)
|
||||
await this.lineItemTaxLineService_.delete(toDelete, sharedContext)
|
||||
}
|
||||
|
||||
const result = await this.lineItemTaxLineService_.upsert(
|
||||
@@ -1178,9 +1382,6 @@ export default class OrderModuleService<
|
||||
> {
|
||||
let addedTaxLines: ShippingMethodTaxLine[]
|
||||
if (isString(orderIdOrData)) {
|
||||
// existence check
|
||||
await this.retrieve(orderIdOrData, { select: ["id"] }, sharedContext)
|
||||
|
||||
const lines = Array.isArray(taxLines) ? taxLines : [taxLines]
|
||||
|
||||
addedTaxLines = await this.shippingMethodTaxLineService_.create(
|
||||
@@ -1224,11 +1425,10 @@ export default class OrderModuleService<
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const existingTaxLines = await this.listShippingMethodTaxLines(
|
||||
{ shipping_method: { order_id: order.id } },
|
||||
{ select: ["id"] },
|
||||
sharedContext
|
||||
)
|
||||
const existingTaxLines = (order.shipping_methods ?? [])
|
||||
.map((shippingMethod) => shippingMethod.tax_lines ?? [])
|
||||
.flat()
|
||||
.map((taxLine) => taxLine.id)
|
||||
|
||||
const taxLinesSet = new Set(
|
||||
taxLines
|
||||
@@ -1239,22 +1439,15 @@ export default class OrderModuleService<
|
||||
.filter(Boolean)
|
||||
)
|
||||
|
||||
const toDelete: OrderTypes.OrderShippingMethodTaxLineDTO[] = []
|
||||
|
||||
// From the existing tax lines, find the ones that are not passed in taxLines
|
||||
existingTaxLines.forEach(
|
||||
(taxLine: OrderTypes.OrderShippingMethodTaxLineDTO) => {
|
||||
if (!taxLinesSet.has(taxLine.id)) {
|
||||
toDelete.push(taxLine)
|
||||
}
|
||||
const toDelete: string[] = []
|
||||
existingTaxLines.forEach((taxLine: string) => {
|
||||
if (!taxLinesSet.has(taxLine)) {
|
||||
toDelete.push(taxLine)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
if (toDelete.length) {
|
||||
await this.shippingMethodTaxLineService_.delete(
|
||||
toDelete.map((taxLine) => taxLine!.id),
|
||||
sharedContext
|
||||
)
|
||||
await this.shippingMethodTaxLineService_.delete(toDelete, sharedContext)
|
||||
}
|
||||
|
||||
const result = await this.shippingMethodTaxLineService_.upsert(
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
Context,
|
||||
DAL,
|
||||
FindConfig,
|
||||
OrderTypes,
|
||||
RepositoryService,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
InjectManager,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
ModulesSdkUtils,
|
||||
} from "@medusajs/utils"
|
||||
import { Order } from "@models"
|
||||
|
||||
type InjectedDependencies = {
|
||||
orderRepository: DAL.RepositoryService
|
||||
}
|
||||
|
||||
export default class OrderService<
|
||||
TEntity extends Order = Order
|
||||
> extends ModulesSdkUtils.internalModuleServiceFactory<InjectedDependencies>(
|
||||
Order
|
||||
)<TEntity> {
|
||||
protected readonly orderRepository_: RepositoryService<TEntity>
|
||||
|
||||
constructor(container: InjectedDependencies) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
this.orderRepository_ = container.orderRepository
|
||||
}
|
||||
|
||||
@InjectManager("orderRepository_")
|
||||
async retrieveOrderVersion<TEntityMethod = OrderTypes.OrderDTO>(
|
||||
id: string,
|
||||
version: number,
|
||||
config: FindConfig<TEntityMethod> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity> {
|
||||
const queryConfig = ModulesSdkUtils.buildQuery<TEntity>(
|
||||
{ id, items: { version } },
|
||||
{ ...config, take: 1 }
|
||||
)
|
||||
const [result] = await this.orderRepository_.find(
|
||||
queryConfig,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
if (!result) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Order with id: "${id}" and version: "${version}" not found`
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,12 @@ export * from "./line-item"
|
||||
export * from "./line-item-adjustment"
|
||||
export * from "./line-item-tax-line"
|
||||
export * from "./order"
|
||||
export * from "./order-detail"
|
||||
export * from "./shipping-method"
|
||||
export * from "./shipping-method-adjustment"
|
||||
export * from "./shipping-method-tax-line"
|
||||
export * from "./transaction"
|
||||
export * from "./utils"
|
||||
|
||||
export type InitializeModuleInjectableDependencies = {
|
||||
logger?: Logger
|
||||
|
||||
@@ -26,6 +26,7 @@ interface PartialUpsertOrderLineItemDTO {
|
||||
}
|
||||
|
||||
export interface CreateOrderLineItemDTO extends PartialUpsertOrderLineItemDTO {
|
||||
version?: number
|
||||
title: string
|
||||
quantity: BigNumberInput
|
||||
unit_price: BigNumberInput
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { BigNumberInput } from "@medusajs/types"
|
||||
|
||||
export interface PartialUpsertOrderItemDTO {
|
||||
order_id?: string
|
||||
version?: number
|
||||
item_id?: string
|
||||
|
||||
quantity?: BigNumberInput
|
||||
fulfilled_quantity?: BigNumberInput
|
||||
return_requested_quantity?: BigNumberInput
|
||||
return_received_quantity?: BigNumberInput
|
||||
return_dismissed_quantity?: BigNumberInput
|
||||
written_off_quantity?: BigNumberInput
|
||||
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface CreateOrderItemDTO extends PartialUpsertOrderItemDTO {
|
||||
order_id: string
|
||||
version: number
|
||||
item_id: string
|
||||
quantity: BigNumberInput
|
||||
}
|
||||
|
||||
export interface UpdateOrderItemDTO
|
||||
extends PartialUpsertOrderItemDTO,
|
||||
Partial<CreateOrderItemDTO> {
|
||||
id: string
|
||||
}
|
||||
@@ -1,8 +1,4 @@
|
||||
import { OrderStatus } from "@medusajs/utils"
|
||||
import {
|
||||
CreateOrderLineItemAdjustmentDTO,
|
||||
UpdateOrderLineItemAdjustmentDTO,
|
||||
} from "./line-item-adjustment"
|
||||
|
||||
export interface CreateOrderDTO {
|
||||
region_id?: string
|
||||
@@ -26,9 +22,4 @@ export interface UpdateOrderDTO {
|
||||
status?: OrderStatus
|
||||
no_notification?: boolean
|
||||
metadata?: Record<string, unknown>
|
||||
|
||||
adjustments?: (
|
||||
| CreateOrderLineItemAdjustmentDTO
|
||||
| UpdateOrderLineItemAdjustmentDTO
|
||||
)[]
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
import { BigNumberInput } from "@medusajs/types"
|
||||
|
||||
export interface CreateOrderShippingMethodDTO {
|
||||
version?: number
|
||||
name: string
|
||||
shipping_method_id: string
|
||||
order_id: string
|
||||
amount: BigNumberInput
|
||||
data?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface UpdateOrderShippingMethodDTO {
|
||||
id: string
|
||||
shipping_method_id: string
|
||||
name?: string
|
||||
amount?: BigNumberInput
|
||||
data?: Record<string, unknown>
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
export type VirtualOrder = {
|
||||
items: {
|
||||
id: string
|
||||
unit_price: number
|
||||
quantity: number
|
||||
|
||||
fulfilled_quantity: number
|
||||
return_requested_quantity: number
|
||||
return_received_quantity: number
|
||||
return_dismissed_quantity: number
|
||||
written_off_quantity: number
|
||||
}[]
|
||||
|
||||
shipping_methods: {
|
||||
id: string
|
||||
price: number
|
||||
}[]
|
||||
|
||||
total: number
|
||||
shipping_total: number
|
||||
}
|
||||
|
||||
export enum EVENT_STATUS {
|
||||
PENDING = "pending",
|
||||
VOIDED = "voided",
|
||||
DONE = "done",
|
||||
}
|
||||
|
||||
export interface OrderSummary {
|
||||
currentOrderTotal: number
|
||||
originalOrderTotal: number
|
||||
transactionTotal: number
|
||||
futureDifference: number
|
||||
pendingDifference: number
|
||||
futureTemporaryDifference: number
|
||||
temporaryDifference: number
|
||||
differenceSum: number
|
||||
}
|
||||
|
||||
export interface OrderTransaction {
|
||||
amount: number
|
||||
}
|
||||
|
||||
export interface OrderChangeEvent {
|
||||
action: string
|
||||
amount?: number
|
||||
|
||||
reference?: string
|
||||
reference_id?: string
|
||||
|
||||
group_id?: string
|
||||
|
||||
evaluationOnly?: boolean
|
||||
|
||||
details?: any
|
||||
|
||||
resolve?: {
|
||||
group_id?: string
|
||||
reference_id?: string
|
||||
amount?: number
|
||||
}
|
||||
}
|
||||
|
||||
export type InternalOrderChangeEvent = OrderChangeEvent & {
|
||||
status?: EVENT_STATUS
|
||||
original_?: InternalOrderChangeEvent
|
||||
}
|
||||
|
||||
export type OrderReferences = {
|
||||
action: InternalOrderChangeEvent
|
||||
previousEvents?: InternalOrderChangeEvent[]
|
||||
currentOrder: VirtualOrder
|
||||
summary: OrderSummary
|
||||
transactions: OrderTransaction[]
|
||||
type: ActionTypeDefinition
|
||||
actions: InternalOrderChangeEvent[]
|
||||
}
|
||||
|
||||
export interface ActionTypeDefinition {
|
||||
isDeduction?: boolean
|
||||
awaitRequired?: boolean
|
||||
versioning?: boolean
|
||||
void?: boolean
|
||||
commitsAction?: string
|
||||
operation?: (obj: OrderReferences) => number | void
|
||||
revert?: (obj: OrderReferences) => number | void
|
||||
validate?: (obj: OrderReferences) => void
|
||||
[key: string]: unknown
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export enum ChangeActionType {
|
||||
CANCEL = "CANCEL",
|
||||
CANCEL_RETURN = "CANCEL_RETURN",
|
||||
FULFILL_ITEM = "FULFILL_ITEM",
|
||||
ITEM_ADD = "ITEM_ADD",
|
||||
ITEM_REMOVE = "ITEM_REMOVE",
|
||||
RECEIVE_DAMAGED_RETURN_ITEM = "RECEIVE_DAMAGED_RETURN_ITEM",
|
||||
RECEIVE_RETURN_ITEM = "RECEIVE_RETURN_ITEM",
|
||||
RETURN_ITEM = "RETURN_ITEM",
|
||||
SHIPPING_ADD = "SHIPPING_ADD",
|
||||
WRITE_OFF_ITEM = "WRITE_OFF_ITEM",
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { MedusaError, isDefined } from "@medusajs/utils"
|
||||
import { ChangeActionType } from "../action-key"
|
||||
import { OrderChangeProcessing } from "../calculate-order-change"
|
||||
|
||||
OrderChangeProcessing.registerActionType(ChangeActionType.CANCEL_RETURN, {
|
||||
operation({ action, currentOrder }) {
|
||||
const existing = currentOrder.items.find(
|
||||
(item) => item.id === action.details.reference_id
|
||||
)!
|
||||
|
||||
existing.return_requested_quantity -= action.details.quantity
|
||||
|
||||
return action.details.unit_price * action.details.quantity
|
||||
},
|
||||
revert({ action, currentOrder }) {
|
||||
const existing = currentOrder.items.find(
|
||||
(item) => item.id === action.details.reference_id
|
||||
)!
|
||||
|
||||
existing.return_requested_quantity += action.details.quantity
|
||||
},
|
||||
validate({ action, currentOrder }) {
|
||||
const refId = action.details?.reference_id
|
||||
if (!isDefined(refId)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Details reference ID is required."
|
||||
)
|
||||
}
|
||||
|
||||
const existing = currentOrder.items.find((item) => item.id === refId)
|
||||
|
||||
if (!existing) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Reference ID "${refId}" not found.`
|
||||
)
|
||||
}
|
||||
|
||||
const notFulfilled =
|
||||
(existing.quantity as number) - (existing.fulfilled_quantity as number)
|
||||
|
||||
if (action.details.quantity > notFulfilled) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Cannot fulfill more items than what was ordered."
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ChangeActionType } from "../action-key"
|
||||
import { OrderChangeProcessing } from "../calculate-order-change"
|
||||
|
||||
OrderChangeProcessing.registerActionType(ChangeActionType.CANCEL, {
|
||||
void: true,
|
||||
})
|
||||
@@ -0,0 +1,44 @@
|
||||
import { MedusaError, isDefined } from "@medusajs/utils"
|
||||
import { ChangeActionType } from "../action-key"
|
||||
import { OrderChangeProcessing } from "../calculate-order-change"
|
||||
|
||||
OrderChangeProcessing.registerActionType(ChangeActionType.FULFILL_ITEM, {
|
||||
operation({ action, currentOrder }) {
|
||||
const existing = currentOrder.items.find(
|
||||
(item) => item.id === action.details.reference_id
|
||||
)!
|
||||
|
||||
existing.fulfilled_quantity += action.details.quantity
|
||||
},
|
||||
revert({ action, currentOrder }) {
|
||||
const existing = currentOrder.items.find(
|
||||
(item) => item.id === action.reference_id
|
||||
)!
|
||||
|
||||
existing.fulfilled_quantity -= action.details.quantity
|
||||
},
|
||||
validate({ action, currentOrder }) {
|
||||
const refId = action.details.reference_id
|
||||
if (!isDefined(refId)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Reference ID is required."
|
||||
)
|
||||
}
|
||||
|
||||
const existing = currentOrder.items.find((item) => item.id === refId)
|
||||
if (!existing) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Reference ID "${refId}" not found.`
|
||||
)
|
||||
}
|
||||
|
||||
if (action.details.quantity < 1) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Quantity must be greater than 0."
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
export * from "./cancel"
|
||||
export * from "./cancel-return"
|
||||
export * from "./fulfill-item"
|
||||
export * from "./item-add"
|
||||
export * from "./item-remove"
|
||||
export * from "./receive-damaged-return-item"
|
||||
export * from "./receive-return-item"
|
||||
export * from "./return-item"
|
||||
export * from "./shipping-add"
|
||||
@@ -0,0 +1,60 @@
|
||||
import { MedusaError, isDefined } from "@medusajs/utils"
|
||||
import { VirtualOrder } from "@types"
|
||||
import { ChangeActionType } from "../action-key"
|
||||
import { OrderChangeProcessing } from "../calculate-order-change"
|
||||
|
||||
OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_ADD, {
|
||||
operation({ action, currentOrder }) {
|
||||
const existing = currentOrder.items.find(
|
||||
(item) => item.id === action.reference_id
|
||||
)
|
||||
|
||||
if (existing) {
|
||||
existing.quantity += action.details.quantity
|
||||
} else {
|
||||
currentOrder.items.push({
|
||||
id: action.reference_id!,
|
||||
unit_price: action.details.unit_price,
|
||||
quantity: action.details.quantity,
|
||||
} as VirtualOrder["items"][0])
|
||||
}
|
||||
|
||||
return action.details.unit_price * action.details.quantity
|
||||
},
|
||||
revert({ action, currentOrder }) {
|
||||
const existingIndex = currentOrder.items.findIndex(
|
||||
(item) => item.id === action.reference_id
|
||||
)
|
||||
|
||||
if (existingIndex > -1) {
|
||||
const existing = currentOrder.items[existingIndex]
|
||||
existing.quantity -= action.details.quantity
|
||||
|
||||
if (existing.quantity <= 0) {
|
||||
currentOrder.items.splice(existingIndex, 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
validate({ action }) {
|
||||
if (!isDefined(action.reference_id)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Reference ID is required."
|
||||
)
|
||||
}
|
||||
|
||||
if (!isDefined(action.details.unit_price)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Unit price is required."
|
||||
)
|
||||
}
|
||||
|
||||
if (action.details.quantity < 1) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Quantity must be greater than 0."
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,78 @@
|
||||
import { MedusaError, isDefined } from "@medusajs/utils"
|
||||
import { VirtualOrder } from "@types"
|
||||
import { ChangeActionType } from "../action-key"
|
||||
import { OrderChangeProcessing } from "../calculate-order-change"
|
||||
|
||||
OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_REMOVE, {
|
||||
isDeduction: true,
|
||||
operation({ action, currentOrder }) {
|
||||
const existingIndex = currentOrder.items.findIndex(
|
||||
(item) => item.id === action.reference_id
|
||||
)
|
||||
|
||||
const existing = currentOrder.items[existingIndex]
|
||||
existing.quantity -= action.details.quantity
|
||||
|
||||
if (existing.quantity <= 0) {
|
||||
currentOrder.items.splice(existingIndex, 1)
|
||||
}
|
||||
|
||||
return existing.unit_price * action.details.quantity
|
||||
},
|
||||
revert({ action, currentOrder }) {
|
||||
const existing = currentOrder.items.find(
|
||||
(item) => item.id === action.reference_id
|
||||
)
|
||||
|
||||
if (existing) {
|
||||
existing.quantity += action.details.quantity
|
||||
} else {
|
||||
currentOrder.items.push({
|
||||
id: action.reference_id!,
|
||||
unit_price: action.details.unit_price,
|
||||
quantity: action.details.quantity,
|
||||
} as VirtualOrder["items"][0])
|
||||
}
|
||||
},
|
||||
validate({ action, currentOrder }) {
|
||||
const refId = action.reference_id
|
||||
if (!isDefined(refId)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Reference ID is required."
|
||||
)
|
||||
}
|
||||
|
||||
const existing = currentOrder.items.find((item) => item.id === refId)
|
||||
if (!existing) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Reference ID "${refId}" not found.`
|
||||
)
|
||||
}
|
||||
|
||||
if (!isDefined(action.details.unit_price)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Unit price is required."
|
||||
)
|
||||
}
|
||||
|
||||
if (action.details.quantity < 1) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Quantity must be greater than 0."
|
||||
)
|
||||
}
|
||||
|
||||
const notFulfilled =
|
||||
(existing.quantity as number) - (existing.fulfilled_quantity as number)
|
||||
|
||||
if (action.details.quantity > notFulfilled) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Cannot remove fulfilled items."
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,88 @@
|
||||
import { MedusaError, isDefined } from "@medusajs/utils"
|
||||
import { EVENT_STATUS } from "@types"
|
||||
import { ChangeActionType } from "../action-key"
|
||||
import { OrderChangeProcessing } from "../calculate-order-change"
|
||||
|
||||
OrderChangeProcessing.registerActionType(
|
||||
ChangeActionType.RECEIVE_DAMAGED_RETURN_ITEM,
|
||||
{
|
||||
isDeduction: true,
|
||||
commitsAction: "return_item",
|
||||
operation({ action, currentOrder, previousEvents }) {
|
||||
const existing = currentOrder.items.find(
|
||||
(item) => item.id === action.details.reference_id
|
||||
)!
|
||||
|
||||
let toReturn = action.details.quantity
|
||||
|
||||
existing.return_dismissed_quantity ??= 0
|
||||
existing.return_dismissed_quantity += toReturn
|
||||
existing.return_requested_quantity -= toReturn
|
||||
|
||||
if (previousEvents) {
|
||||
for (const previousEvent of previousEvents) {
|
||||
previousEvent.original_ = JSON.parse(JSON.stringify(previousEvent))
|
||||
|
||||
let ret = Math.min(toReturn, previousEvent.details.quantity)
|
||||
toReturn -= ret
|
||||
|
||||
previousEvent.details.quantity -= ret
|
||||
if (previousEvent.details.quantity <= 0) {
|
||||
previousEvent.status = EVENT_STATUS.DONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return existing.unit_price * action.details.quantity
|
||||
},
|
||||
revert({ action, currentOrder, previousEvents }) {
|
||||
const existing = currentOrder.items.find(
|
||||
(item) => item.id === action.details.reference_id
|
||||
)!
|
||||
|
||||
existing.return_dismissed_quantity -= action.details.quantity
|
||||
existing.return_requested_quantity += action.details.quantity
|
||||
|
||||
if (previousEvents) {
|
||||
for (const previousEvent of previousEvents) {
|
||||
if (!previousEvent.original_) {
|
||||
continue
|
||||
}
|
||||
|
||||
previousEvent.details = JSON.parse(
|
||||
JSON.stringify(previousEvent.original_.details)
|
||||
)
|
||||
delete previousEvent.original_
|
||||
|
||||
previousEvent.status = EVENT_STATUS.PENDING
|
||||
}
|
||||
}
|
||||
},
|
||||
validate({ action, currentOrder }) {
|
||||
const refId = action.details?.reference_id
|
||||
if (!isDefined(refId)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Details reference ID is required."
|
||||
)
|
||||
}
|
||||
|
||||
const existing = currentOrder.items.find((item) => item.id === refId)
|
||||
|
||||
if (!existing) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Reference ID "${refId}" not found.`
|
||||
)
|
||||
}
|
||||
|
||||
const quantityRequested = existing?.return_requested_quantity || 0
|
||||
if (action.details.quantity > quantityRequested) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Cannot receive more items than what was requested to be returned."
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,85 @@
|
||||
import { MedusaError, isDefined } from "@medusajs/utils"
|
||||
import { EVENT_STATUS } from "@types"
|
||||
import { ChangeActionType } from "../action-key"
|
||||
import { OrderChangeProcessing } from "../calculate-order-change"
|
||||
|
||||
OrderChangeProcessing.registerActionType(ChangeActionType.RECEIVE_RETURN_ITEM, {
|
||||
isDeduction: true,
|
||||
commitsAction: "return_item",
|
||||
operation({ action, currentOrder, previousEvents }) {
|
||||
const existing = currentOrder.items.find(
|
||||
(item) => item.id === action.details.reference_id
|
||||
)!
|
||||
|
||||
let toReturn = action.details.quantity
|
||||
|
||||
existing.return_received_quantity ??= 0
|
||||
existing.return_received_quantity += toReturn
|
||||
existing.return_requested_quantity -= toReturn
|
||||
|
||||
if (previousEvents) {
|
||||
for (const previousEvent of previousEvents) {
|
||||
previousEvent.original_ = JSON.parse(JSON.stringify(previousEvent))
|
||||
|
||||
let ret = Math.min(toReturn, previousEvent.details.quantity)
|
||||
toReturn -= ret
|
||||
|
||||
previousEvent.details.quantity -= ret
|
||||
if (previousEvent.details.quantity <= 0) {
|
||||
previousEvent.status = EVENT_STATUS.DONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return existing.unit_price * action.details.quantity
|
||||
},
|
||||
revert({ action, currentOrder, previousEvents }) {
|
||||
const existing = currentOrder.items.find(
|
||||
(item) => item.id === action.details.reference_id
|
||||
)!
|
||||
|
||||
existing.return_received_quantity -= action.details.quantity
|
||||
existing.return_requested_quantity += action.details.quantity
|
||||
|
||||
if (previousEvents) {
|
||||
for (const previousEvent of previousEvents) {
|
||||
if (!previousEvent.original_) {
|
||||
continue
|
||||
}
|
||||
|
||||
previousEvent.details = JSON.parse(
|
||||
JSON.stringify(previousEvent.original_.details)
|
||||
)
|
||||
delete previousEvent.original_
|
||||
|
||||
previousEvent.status = EVENT_STATUS.PENDING
|
||||
}
|
||||
}
|
||||
},
|
||||
validate({ action, currentOrder }) {
|
||||
const refId = action.details?.reference_id
|
||||
if (!isDefined(refId)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Details reference ID is required."
|
||||
)
|
||||
}
|
||||
|
||||
const existing = currentOrder.items.find((item) => item.id === refId)
|
||||
|
||||
if (!existing) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Reference ID "${refId}" not found.`
|
||||
)
|
||||
}
|
||||
|
||||
const quantityRequested = existing?.return_requested_quantity || 0
|
||||
if (action.details.quantity > quantityRequested) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Cannot receive more items than what was requested to be returned."
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,54 @@
|
||||
import { MedusaError, isDefined } from "@medusajs/utils"
|
||||
import { ChangeActionType } from "../action-key"
|
||||
import { OrderChangeProcessing } from "../calculate-order-change"
|
||||
|
||||
OrderChangeProcessing.registerActionType(ChangeActionType.RETURN_ITEM, {
|
||||
isDeduction: true,
|
||||
awaitRequired: true,
|
||||
operation({ action, currentOrder }) {
|
||||
const existing = currentOrder.items.find(
|
||||
(item) => item.id === action.details.reference_id
|
||||
)!
|
||||
|
||||
existing.return_requested_quantity ??= 0
|
||||
existing.return_requested_quantity += action.details.quantity
|
||||
|
||||
return existing.unit_price * action.details.quantity
|
||||
},
|
||||
revert({ action, currentOrder }) {
|
||||
const existing = currentOrder.items.find(
|
||||
(item) => item.id === action.details.reference_id
|
||||
)!
|
||||
|
||||
existing.return_requested_quantity -= action.details.quantity
|
||||
},
|
||||
validate({ action, currentOrder }) {
|
||||
const refId = action.details?.reference_id
|
||||
if (!isDefined(refId)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Details reference ID is required."
|
||||
)
|
||||
}
|
||||
|
||||
const existing = currentOrder.items.find((item) => item.id === refId)
|
||||
|
||||
if (!existing) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Reference ID "${refId}" not found.`
|
||||
)
|
||||
}
|
||||
|
||||
const quantityAvailable =
|
||||
(existing!.fulfilled_quantity ?? 0) -
|
||||
(existing!.return_requested_quantity ?? 0)
|
||||
|
||||
if (action.details.quantity > quantityAvailable) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Cannot request to return more items than what was fulfilled."
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,47 @@
|
||||
import { MedusaError, isDefined } from "@medusajs/utils"
|
||||
import { ChangeActionType } from "../action-key"
|
||||
import { OrderChangeProcessing } from "../calculate-order-change"
|
||||
|
||||
OrderChangeProcessing.registerActionType(ChangeActionType.SHIPPING_ADD, {
|
||||
operation({ action, currentOrder }) {
|
||||
const shipping = Array.isArray(currentOrder.shipping_methods)
|
||||
? currentOrder.shipping_methods
|
||||
: [currentOrder.shipping_methods]
|
||||
|
||||
shipping.push({
|
||||
id: action.reference_id!,
|
||||
price: action.amount as number,
|
||||
})
|
||||
|
||||
currentOrder.shipping_methods = shipping
|
||||
return action.amount
|
||||
},
|
||||
revert({ action, currentOrder }) {
|
||||
const shipping = Array.isArray(currentOrder.shipping_methods)
|
||||
? currentOrder.shipping_methods
|
||||
: [currentOrder.shipping_methods]
|
||||
|
||||
const existingIndex = shipping.findIndex(
|
||||
(item) => item.id === action.reference_id
|
||||
)
|
||||
|
||||
if (existingIndex > -1) {
|
||||
shipping.splice(existingIndex, 1)
|
||||
}
|
||||
},
|
||||
validate({ action }) {
|
||||
if (!action.reference_id) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Reference ID is required."
|
||||
)
|
||||
}
|
||||
|
||||
if (!isDefined(action.amount)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Amount is required."
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,47 @@
|
||||
import { MedusaError, isDefined } from "@medusajs/utils"
|
||||
import { ChangeActionType } from "../action-key"
|
||||
import { OrderChangeProcessing } from "../calculate-order-change"
|
||||
|
||||
OrderChangeProcessing.registerActionType(ChangeActionType.WRITE_OFF_ITEM, {
|
||||
operation({ action, currentOrder }) {
|
||||
const existing = currentOrder.items.find(
|
||||
(item) => item.id === action.details.reference_id
|
||||
)!
|
||||
|
||||
existing.written_off_quantity ??= 0
|
||||
existing.written_off_quantity += action.details.quantity
|
||||
},
|
||||
revert({ action, currentOrder }) {
|
||||
const existing = currentOrder.items.find(
|
||||
(item) => item.id === action.details.reference_id
|
||||
)!
|
||||
|
||||
existing.written_off_quantity -= action.details.quantity
|
||||
},
|
||||
validate({ action, currentOrder }) {
|
||||
const refId = action.details?.reference_id
|
||||
if (!isDefined(refId)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Details reference ID is required."
|
||||
)
|
||||
}
|
||||
|
||||
const existing = currentOrder.items.find((item) => item.id === refId)
|
||||
|
||||
if (!existing) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Reference ID "${refId}" not found.`
|
||||
)
|
||||
}
|
||||
|
||||
const quantityAvailable = existing!.quantity ?? 0
|
||||
if (action.details.quantity > quantityAvailable) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Cannot claim more items than what was ordered."
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,353 @@
|
||||
import {
|
||||
ActionTypeDefinition,
|
||||
EVENT_STATUS,
|
||||
InternalOrderChangeEvent,
|
||||
OrderChangeEvent,
|
||||
OrderSummary,
|
||||
OrderTransaction,
|
||||
VirtualOrder,
|
||||
} from "@types"
|
||||
|
||||
type InternalOrderSummary = OrderSummary & {
|
||||
futureTemporarySum: number
|
||||
}
|
||||
|
||||
export class OrderChangeProcessing {
|
||||
private static typeDefinition: { [key: string]: ActionTypeDefinition } = {}
|
||||
private static defaultConfig = {
|
||||
awaitRequired: false,
|
||||
isDeduction: false,
|
||||
}
|
||||
|
||||
private order: VirtualOrder
|
||||
private transactions: OrderTransaction[]
|
||||
private actions: InternalOrderChangeEvent[]
|
||||
|
||||
private actionsProcessed: { [key: string]: InternalOrderChangeEvent[] } = {}
|
||||
private groupTotal: Record<string, number> = {}
|
||||
private summary: InternalOrderSummary
|
||||
|
||||
public static registerActionType(key: string, type: ActionTypeDefinition) {
|
||||
OrderChangeProcessing.typeDefinition[key] = type
|
||||
}
|
||||
|
||||
constructor({
|
||||
order,
|
||||
transactions,
|
||||
actions,
|
||||
}: {
|
||||
order: VirtualOrder
|
||||
transactions: OrderTransaction[]
|
||||
actions: InternalOrderChangeEvent[]
|
||||
}) {
|
||||
this.order = JSON.parse(JSON.stringify(order))
|
||||
this.transactions = JSON.parse(JSON.stringify(transactions ?? []))
|
||||
this.actions = JSON.parse(JSON.stringify(actions ?? []))
|
||||
|
||||
const transactionTotal = transactions.reduce((acc, transaction) => {
|
||||
return acc + transaction.amount
|
||||
}, 0)
|
||||
|
||||
this.summary = {
|
||||
futureDifference: 0,
|
||||
futureTemporaryDifference: 0,
|
||||
temporaryDifference: 0,
|
||||
pendingDifference: 0,
|
||||
futureTemporarySum: 0,
|
||||
differenceSum: 0,
|
||||
currentOrderTotal: order.total as number,
|
||||
originalOrderTotal: order.total as number,
|
||||
transactionTotal,
|
||||
}
|
||||
}
|
||||
|
||||
private isEventActive(action: InternalOrderChangeEvent): boolean {
|
||||
const status = action.status
|
||||
return (
|
||||
status === undefined ||
|
||||
status === EVENT_STATUS.PENDING ||
|
||||
status === EVENT_STATUS.DONE
|
||||
)
|
||||
}
|
||||
private isEventDone(action: InternalOrderChangeEvent): boolean {
|
||||
const status = action.status
|
||||
return status === EVENT_STATUS.DONE
|
||||
}
|
||||
|
||||
private isEventPending(action: InternalOrderChangeEvent): boolean {
|
||||
const status = action.status
|
||||
return status === undefined || status === EVENT_STATUS.PENDING
|
||||
}
|
||||
|
||||
public processActions() {
|
||||
for (const action of this.actions) {
|
||||
this.processAction_(action)
|
||||
}
|
||||
|
||||
const summary = this.summary
|
||||
for (const action of this.actions) {
|
||||
if (!this.isEventActive(action)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const type = {
|
||||
...OrderChangeProcessing.defaultConfig,
|
||||
...OrderChangeProcessing.typeDefinition[action.action],
|
||||
}
|
||||
|
||||
const amount = action.amount! * (type.isDeduction ? -1 : 1)
|
||||
|
||||
if (action.group_id && !action.evaluationOnly) {
|
||||
this.groupTotal[action.group_id] ??= 0
|
||||
this.groupTotal[action.group_id] += amount
|
||||
}
|
||||
|
||||
if (type.awaitRequired && !this.isEventDone(action)) {
|
||||
if (action.evaluationOnly) {
|
||||
summary.futureTemporarySum += amount
|
||||
} else {
|
||||
summary.temporaryDifference += amount
|
||||
}
|
||||
}
|
||||
|
||||
if (action.evaluationOnly) {
|
||||
summary.futureDifference += amount
|
||||
} else {
|
||||
if (!this.isEventDone(action) && !action.group_id) {
|
||||
summary.differenceSum += amount
|
||||
}
|
||||
summary.currentOrderTotal += amount
|
||||
}
|
||||
}
|
||||
|
||||
const groupSum = Object.values(this.groupTotal).reduce((acc, amount) => {
|
||||
return acc + amount
|
||||
}, 0)
|
||||
|
||||
summary.differenceSum += groupSum
|
||||
|
||||
summary.transactionTotal = this.transactions.reduce((acc, transaction) => {
|
||||
return acc + transaction.amount
|
||||
}, 0)
|
||||
|
||||
summary.futureTemporaryDifference =
|
||||
summary.futureDifference - summary.futureTemporarySum
|
||||
|
||||
summary.temporaryDifference =
|
||||
summary.differenceSum - summary.temporaryDifference
|
||||
|
||||
summary.pendingDifference =
|
||||
summary.currentOrderTotal - summary.transactionTotal
|
||||
}
|
||||
|
||||
private processAction_(
|
||||
action: InternalOrderChangeEvent,
|
||||
isReplay = false
|
||||
): number | void {
|
||||
const type = {
|
||||
...OrderChangeProcessing.defaultConfig,
|
||||
...OrderChangeProcessing.typeDefinition[action.action],
|
||||
}
|
||||
|
||||
this.actionsProcessed[action.action] ??= []
|
||||
|
||||
if (!isReplay) {
|
||||
this.actionsProcessed[action.action].push(action)
|
||||
}
|
||||
|
||||
let previousEvents: InternalOrderChangeEvent[] | undefined
|
||||
if (type.commitsAction) {
|
||||
previousEvents = (this.actionsProcessed[type.commitsAction] ?? []).filter(
|
||||
(ac_) =>
|
||||
ac_.reference_id === action.reference_id &&
|
||||
ac_.status !== EVENT_STATUS.VOIDED
|
||||
)
|
||||
}
|
||||
|
||||
let calculatedAmount: number = action.amount ?? 0
|
||||
const params = {
|
||||
actions: this.actions,
|
||||
action,
|
||||
previousEvents,
|
||||
currentOrder: this.order,
|
||||
summary: this.summary,
|
||||
transactions: this.transactions,
|
||||
type,
|
||||
}
|
||||
if (typeof type.validate === "function") {
|
||||
type.validate(params)
|
||||
}
|
||||
|
||||
if (typeof type.operation === "function") {
|
||||
calculatedAmount = type.operation(params) as number
|
||||
|
||||
action.amount = calculatedAmount ?? 0
|
||||
}
|
||||
|
||||
// If an action commits previous ones, replay them with updated values
|
||||
if (type.commitsAction) {
|
||||
for (const previousEvent of previousEvents ?? []) {
|
||||
this.processAction_(previousEvent, true)
|
||||
}
|
||||
}
|
||||
|
||||
if (action.resolve) {
|
||||
if (action.resolve.reference_id) {
|
||||
this.resolveReferences(action)
|
||||
}
|
||||
const groupId = action.resolve.group_id ?? "__default"
|
||||
if (action.resolve.group_id) {
|
||||
// resolve all actions in the same group
|
||||
this.resolveGroup(action)
|
||||
}
|
||||
if (action.resolve.amount && !action.evaluationOnly) {
|
||||
this.groupTotal[groupId] ??= 0
|
||||
this.groupTotal[groupId] -= action.resolve.amount
|
||||
}
|
||||
}
|
||||
|
||||
return calculatedAmount
|
||||
}
|
||||
|
||||
private resolveReferences(self: InternalOrderChangeEvent) {
|
||||
const resolve = self.resolve
|
||||
const resolveType = OrderChangeProcessing.typeDefinition[self.action]
|
||||
|
||||
Object.keys(this.actionsProcessed).forEach((actionKey) => {
|
||||
const type = OrderChangeProcessing.typeDefinition[actionKey]
|
||||
|
||||
const actions = this.actionsProcessed[actionKey]
|
||||
|
||||
for (const action of actions) {
|
||||
if (
|
||||
action === self ||
|
||||
!this.isEventPending(action) ||
|
||||
action.reference_id !== resolve?.reference_id
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (type.revert && (action.evaluationOnly || resolveType.void)) {
|
||||
let previousEvents: InternalOrderChangeEvent[] | undefined
|
||||
if (type.commitsAction) {
|
||||
previousEvents = (
|
||||
this.actionsProcessed[type.commitsAction] ?? []
|
||||
).filter(
|
||||
(ac_) =>
|
||||
ac_.reference_id === action.reference_id &&
|
||||
ac_.status !== EVENT_STATUS.VOIDED
|
||||
)
|
||||
}
|
||||
|
||||
type.revert({
|
||||
actions: this.actions,
|
||||
action,
|
||||
previousEvents,
|
||||
currentOrder: this.order,
|
||||
summary: this.summary,
|
||||
transactions: this.transactions,
|
||||
type,
|
||||
})
|
||||
|
||||
for (const previousEvent of previousEvents ?? []) {
|
||||
this.processAction_(previousEvent, true)
|
||||
}
|
||||
|
||||
action.status =
|
||||
action.evaluationOnly || resolveType.void
|
||||
? EVENT_STATUS.VOIDED
|
||||
: EVENT_STATUS.DONE
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private resolveGroup(self: InternalOrderChangeEvent) {
|
||||
const resolve = self.resolve
|
||||
|
||||
Object.keys(this.actionsProcessed).forEach((actionKey) => {
|
||||
const type = OrderChangeProcessing.typeDefinition[actionKey]
|
||||
const actions = this.actionsProcessed[actionKey]
|
||||
for (const action of actions) {
|
||||
if (!resolve?.group_id || action?.group_id !== resolve.group_id) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (
|
||||
type.revert &&
|
||||
action.status !== EVENT_STATUS.DONE &&
|
||||
action.status !== EVENT_STATUS.VOIDED &&
|
||||
(action.evaluationOnly || type.void)
|
||||
) {
|
||||
let previousEvents: InternalOrderChangeEvent[] | undefined
|
||||
if (type.commitsAction) {
|
||||
previousEvents = (
|
||||
this.actionsProcessed[type.commitsAction] ?? []
|
||||
).filter(
|
||||
(ac_) =>
|
||||
ac_.reference_id === action.reference_id &&
|
||||
ac_.status !== EVENT_STATUS.VOIDED
|
||||
)
|
||||
}
|
||||
|
||||
type.revert({
|
||||
actions: this.actions,
|
||||
action: action,
|
||||
previousEvents,
|
||||
currentOrder: this.order,
|
||||
summary: this.summary,
|
||||
transactions: this.transactions,
|
||||
type: OrderChangeProcessing.typeDefinition[action.action],
|
||||
})
|
||||
|
||||
for (const previousEvent of previousEvents ?? []) {
|
||||
this.processAction_(previousEvent, true)
|
||||
}
|
||||
|
||||
action.status =
|
||||
action.evaluationOnly || type.void
|
||||
? EVENT_STATUS.VOIDED
|
||||
: EVENT_STATUS.DONE
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public getSummary(): OrderSummary {
|
||||
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,
|
||||
}
|
||||
|
||||
return orderSummary
|
||||
}
|
||||
|
||||
public getCurrentOrder(): VirtualOrder {
|
||||
return this.order
|
||||
}
|
||||
}
|
||||
|
||||
export function calculateOrderChange({
|
||||
order,
|
||||
transactions = [],
|
||||
actions = [],
|
||||
}: {
|
||||
order: VirtualOrder
|
||||
transactions?: OrderTransaction[]
|
||||
actions?: OrderChangeEvent[]
|
||||
}) {
|
||||
const calc = new OrderChangeProcessing({ order, transactions, actions })
|
||||
calc.processActions()
|
||||
|
||||
return {
|
||||
summary: calc.getSummary(),
|
||||
order: calc.getCurrentOrder(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./action-key"
|
||||
export * from "./actions"
|
||||
export * from "./calculate-order-change"
|
||||
@@ -0,0 +1,86 @@
|
||||
import { OrderTypes } from "@medusajs/types"
|
||||
import { isDefined } from "@medusajs/utils"
|
||||
|
||||
export function formatOrder(
|
||||
order
|
||||
): OrderTypes.OrderDTO | OrderTypes.OrderDTO[] {
|
||||
const isArray = Array.isArray(order)
|
||||
const orders = isArray ? order : [order]
|
||||
|
||||
orders.map((order) => {
|
||||
order.items = order.items?.map((orderItem) => {
|
||||
const detail = { ...orderItem }
|
||||
delete detail.order
|
||||
delete detail.item
|
||||
|
||||
return {
|
||||
...orderItem.item,
|
||||
quantity: detail.quantity,
|
||||
raw_quantity: detail.raw_quantity,
|
||||
detail,
|
||||
}
|
||||
})
|
||||
|
||||
return order
|
||||
})
|
||||
|
||||
return isArray ? orders : orders[0]
|
||||
}
|
||||
|
||||
export function mapRepositoryToOrderModel(config) {
|
||||
const conf = { ...config }
|
||||
|
||||
function replace(obj, type): string[] | undefined {
|
||||
if (!isDefined(obj[type])) {
|
||||
return
|
||||
}
|
||||
|
||||
return [
|
||||
...new Set<string>(
|
||||
obj[type].sort().map((rel) => {
|
||||
if (rel == "items.quantity") {
|
||||
if (type === "fields") {
|
||||
obj.populate.push("items.item")
|
||||
}
|
||||
return "items.item.quantity"
|
||||
} else if (rel.includes("items.detail")) {
|
||||
return rel.replace("items.detail", "items")
|
||||
} else if (rel == "items") {
|
||||
return "items.item"
|
||||
} else if (rel.includes("items.") && !rel.includes("items.item")) {
|
||||
return rel.replace("items.", "items.item.")
|
||||
}
|
||||
return rel
|
||||
})
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
conf.options.fields = replace(config.options, "fields")
|
||||
conf.options.populate = replace(config.options, "populate")
|
||||
|
||||
if (conf.where?.items) {
|
||||
const original = { ...conf.where.items }
|
||||
if (original.detail) {
|
||||
delete conf.where.items.detail
|
||||
}
|
||||
|
||||
conf.where.items = {
|
||||
item: conf.where?.items,
|
||||
}
|
||||
|
||||
if (original.quantity) {
|
||||
conf.where.items.quantity = original.quantity
|
||||
delete conf.where.items.item.quantity
|
||||
}
|
||||
|
||||
if (original.detail) {
|
||||
conf.where.items = {
|
||||
...original.detail,
|
||||
...conf.where.items,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return conf
|
||||
}
|
||||
Reference in new issue
Block a user