chore(): Reorganize modules (#7210)

**What**
Move all modules to the modules directory
This commit is contained in:
Adrien de Peretti
2024-05-02 17:33:34 +02:00
committed by GitHub
parent 7a351eef09
commit 4eae25e1ef
870 changed files with 91 additions and 62 deletions

View File

@@ -0,0 +1,14 @@
import { initializeFactory, Modules } from "@medusajs/modules-sdk"
import { moduleDefinition } from "./module-definition"
export * from "./models"
export * from "./services"
export * from "./types"
export const initialize = initializeFactory({
moduleName: Modules.ORDER,
moduleDefinition,
})
export const runMigrations = moduleDefinition.runMigrations
export const revertMigration = moduleDefinition.revertMigration
export default moduleDefinition

View File

@@ -0,0 +1,35 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { MapToConfig } from "@medusajs/utils"
import { LineItem } from "@models"
import Order from "./models/order"
export const LinkableKeys: Record<string, string> = {
order_id: Order.name,
order_item_id: LineItem.name,
}
const entityLinkableKeysMap: MapToConfig = {}
Object.entries(LinkableKeys).forEach(([key, value]) => {
entityLinkableKeysMap[value] ??= []
entityLinkableKeysMap[value].push({
mapTo: key,
valueFrom: key.split("_").pop()!,
})
})
export const entityNameToLinkableKeysMap: MapToConfig = entityLinkableKeysMap
export const joinerConfig: ModuleJoinerConfig = {
serviceName: Modules.ORDER,
primaryKeys: ["id"],
linkableKeys: LinkableKeys,
alias: [
{
name: ["order", "orders"],
args: {
entity: Order.name,
},
},
],
} as ModuleJoinerConfig

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,532 @@
import { generatePostgresAlterColummnIfExistStatement } from "@medusajs/utils"
import { Migration } from "@mikro-orm/migrations"
export class Migration20240219102530 extends Migration {
async up(): Promise<void> {
const sql = `
CREATE TABLE IF NOT EXISTS "order_address" (
"id" TEXT NOT NULL,
"customer_id" TEXT NULL,
"company" TEXT NULL,
"first_name" TEXT NULL,
"last_name" TEXT NULL,
"address_1" TEXT NULL,
"address_2" TEXT NULL,
"city" TEXT NULL,
"country_code" TEXT NULL,
"province" TEXT NULL,
"postal_code" TEXT NULL,
"phone" TEXT NULL,
"metadata" JSONB NULL,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
CONSTRAINT "order_address_pkey" PRIMARY KEY ("id")
);
CREATE INDEX IF NOT EXISTS "IDX_order_address_customer_id" ON "order_address" (
customer_id
);
CREATE TABLE IF NOT EXISTS "order" (
"id" TEXT NOT NULL,
"region_id" TEXT NULL,
"customer_id" TEXT NULL,
"version" INTEGER NOT NULL DEFAULT 1,
"sales_channel_id" TEXT NULL,
"status" text NOT NULL,
"is_draft_order" BOOLEAN NOT NULL DEFAULT false,
"email" text NULL,
"currency_code" text NOT NULL,
"shipping_address_id" text NULL,
"billing_address_id" text NULL,
"no_notification" boolean NULL,
"metadata" jsonb NULL,
"created_at" timestamptz NOT NULL DEFAULT now(),
"updated_at" timestamptz NOT NULL DEFAULT now(),
"deleted_at" timestamptz NULL,
"canceled_at" timestamptz NULL,
CONSTRAINT "order_pkey" PRIMARY KEY ("id")
);
ALTER TABLE "order"
ADD COLUMN if NOT exists "deleted_at" timestamptz NULL;
ALTER TABLE "order"
ADD COLUMN if NOT exists "is_draft_order" BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE "order"
ADD COLUMN if NOT exists "version" INTEGER NOT NULL DEFAULT 1;
ALTER TABLE "order" ALTER COLUMN status TYPE text;
DROP TYPE IF EXISTS order_status_enum CASCADE;
CREATE TYPE order_status_enum AS ENUM (
'pending',
'completed',
'draft',
'archived',
'canceled',
'requires_action'
);
ALTER TABLE "order" ALTER COLUMN status DROP DEFAULT;
ALTER TABLE "order" ALTER COLUMN status TYPE order_status_enum USING (status::text::order_status_enum);
ALTER TABLE "order" ALTER COLUMN status SET DEFAULT 'pending';
ALTER TABLE "order" DROP constraint if EXISTS "FK_6ff7e874f01b478c115fdd462eb" CASCADE;
ALTER TABLE "order" DROP constraint if EXISTS "FK_19b0c6293443d1b464f604c3316" CASCADE;
ALTER TABLE "order" DROP constraint if EXISTS "FK_717a141f96b76d794d409f38129" CASCADE;
ALTER TABLE "order" DROP constraint if EXISTS "FK_727b872f86c7378474a8fa46147" CASCADE;
ALTER TABLE "order" DROP constraint if EXISTS "FK_5568d3b9ce9f7abeeb37511ecf2" CASCADE;
ALTER TABLE "order" DROP constraint if EXISTS "FK_c99a206eb11ad45f6b7f04f2dcc" CASCADE;
ALTER TABLE "order" DROP constraint if EXISTS "FK_cd7812c96209c5bdd48a6b858b0" CASCADE;
ALTER TABLE "order" DROP constraint if EXISTS "FK_e1fcce2b18dbcdbe0a5ba9a68b8" CASCADE;
ALTER TABLE "order" DROP constraint if EXISTS "REL_c99a206eb11ad45f6b7f04f2dc" CASCADE;
ALTER TABLE "order" DROP constraint if EXISTS "UQ_727b872f86c7378474a8fa46147" CASCADE;
DROP INDEX if exists "IDX_19b0c6293443d1b464f604c331";
DROP INDEX if exists "IDX_579e01fb94f4f58db480857e05";
DROP INDEX if exists "IDX_5568d3b9ce9f7abeeb37511ecf";
DROP INDEX if exists "IDX_c99a206eb11ad45f6b7f04f2dc";
DROP INDEX if exists "IDX_cd7812c96209c5bdd48a6b858b";
DROP INDEX if exists "IDX_e1fcce2b18dbcdbe0a5ba9a68b";
${generatePostgresAlterColummnIfExistStatement(
"order",
["fulfillment_status", "payment_status", "display_id"],
"DROP NOT NULL"
)}
CREATE INDEX IF NOT EXISTS "IDX_order_region_id" ON "order" (
region_id
)
WHERE deleted_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS "IDX_order_customer_id" ON "order" (
customer_id
)
WHERE deleted_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS "IDX_order_customer_id" ON "order" (
customer_id
)
WHERE deleted_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS "IDX_order_currency_code" ON "order" (
currency_code
)
WHERE deleted_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS "IDX_order_shipping_address_id" ON "order" (
shipping_address_id
)
WHERE deleted_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS "IDX_order_billing_address_id" ON "order" (
billing_address_id
)
WHERE deleted_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS "IDX_order_deleted_at" ON "order" (
deleted_at
);
CREATE INDEX IF NOT EXISTS "IDX_order_is_draft_order" ON "order" (
is_draft_order
)
WHERE deleted_at IS NOT NULL;
CREATE TABLE IF NOT EXISTS "order_summary" (
"id" TEXT NOT NULL,
"order_id" TEXT NOT NULL,
"version" INTEGER NOT NULL DEFAULT 1,
"totals" JSONB NULL,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
"deleted_at" timestamptz NULL,
CONSTRAINT "order_summary_pkey" PRIMARY KEY ("id")
);
CREATE INDEX IF NOT EXISTS "IDX_order_summary_order_id_version" ON "order_summary" (
order_id,
version
)
WHERE deleted_at IS NOT NULL;
CREATE TABLE IF NOT EXISTS "order_change" (
"id" TEXT NOT NULL,
"order_id" TEXT NOT NULL,
"version" INTEGER NOT NULL,
"description" TEXT NULL,
"status" text check (
"status" IN (
'confirmed',
'declined',
'requested',
'pending',
'canceled'
)
) NOT NULL DEFAULT 'pending',
"internal_note" text NULL,
"created_by" text NULL,
"requested_by" text NULL,
"requested_at" timestamptz NULL,
"confirmed_by" text NULL,
"confirmed_at" timestamptz NULL,
"declined_by" text NULL,
"declined_reason" text NULL,
"metadata" jsonb NULL,
"declined_at" timestamptz NULL,
"canceled_by" text NULL,
"canceled_at" timestamptz NULL,
"created_at" timestamptz NOT NULL DEFAULT now(),
"updated_at" timestamptz NOT NULL DEFAULT now(),
CONSTRAINT "order_change_pkey" PRIMARY KEY ("id")
);
CREATE INDEX IF NOT EXISTS "IDX_order_change_order_id" ON "order_change" (
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" (
"id" TEXT NOT NULL,
"order_id" TEXT NULL,
"version" INTEGER NULL,
"ordering" BIGSERIAL NOT NULL,
"order_change_id" TEXT NULL,
"reference" TEXT NULL,
"reference_id" TEXT NULL,
"action" TEXT NOT NULL,
"details" JSONB NULL,
"amount" NUMERIC NULL,
"raw_amount" JSONB NULL,
"internal_note" TEXT NULL,
"applied" BOOLEAN NOT NULL DEFAULT false,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
CONSTRAINT "order_change_action_pkey" PRIMARY KEY ("id")
);
CREATE INDEX IF NOT EXISTS "IDX_order_change_action_order_change_id" ON "order_change_action" (
order_change_id
);
CREATE INDEX IF NOT EXISTS "IDX_order_change_action_order_id" ON "order_change_action" (
order_id
);
CREATE INDEX IF NOT EXISTS "IDX_order_change_action_ordering" ON "order_change_action" (
ordering
);
CREATE TABLE IF NOT EXISTS "order_item" (
"id" TEXT NOT NULL,
"order_id" TEXT NOT NULL,
"version" INTEGER NOT NULL,
"item_id" TEXT NOT NULL,
"quantity" NUMERIC NOT NULL,
"raw_quantity" JSONB NOT NULL,
"fulfilled_quantity" NUMERIC NOT NULL,
"raw_fulfilled_quantity" JSONB NOT NULL,
"shipped_quantity" NUMERIC NOT NULL,
"raw_shipped_quantity" JSONB NOT NULL,
"return_requested_quantity" NUMERIC NOT NULL,
"raw_return_requested_quantity" JSONB NOT NULL,
"return_received_quantity" NUMERIC NOT NULL,
"raw_return_received_quantity" JSONB NOT NULL,
"return_dismissed_quantity" NUMERIC NOT NULL,
"raw_return_dismissed_quantity" JSONB NOT NULL,
"written_off_quantity" NUMERIC NOT NULL,
"raw_written_off_quantity" JSONB NOT NULL,
"metadata" JSONB NULL,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
"deleted_at" timestamptz NULL,
CONSTRAINT "order_item_pkey" PRIMARY KEY ("id")
);
CREATE INDEX IF NOT EXISTS "IDX_order_item_order_id" ON "order_item" (
order_id
)
WHERE deleted_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS "IDX_order_item_order_id_version" ON "order_item" (
order_id,
version
)
WHERE deleted_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS "IDX_order_item_item_id" ON "order_item" (
item_id
)
WHERE deleted_at IS NOT NULL;
CREATE TABLE IF NOT EXISTS "order_shipping" (
"id" TEXT NOT NULL,
"order_id" TEXT NOT NULL,
"version" INTEGER NOT NULL,
"shipping_method_id" TEXT NOT NULL,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
"deleted_at" timestamptz NULL,
CONSTRAINT "order_shipping_pkey" PRIMARY KEY ("id")
);
CREATE INDEX IF NOT EXISTS "IDX_order_shipping_order_id" ON "order_shipping" (
order_id
)
WHERE deleted_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS "IDX_order_shipping_order_id_version" ON "order_shipping" (
order_id,
version
)
WHERE deleted_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS "IDX_order_shipping_item_id" ON "order_shipping" (
shipping_method_id
)
WHERE deleted_at IS NOT NULL;
CREATE TABLE IF NOT EXISTS "order_line_item" (
"id" TEXT NOT NULL,
"totals_id" TEXT NULL,
"title" TEXT NOT NULL,
"subtitle" TEXT NULL,
"thumbnail" TEXT NULL,
"variant_id" TEXT NULL,
"product_id" TEXT NULL,
"product_title" TEXT NULL,
"product_description" TEXT NULL,
"product_subtitle" TEXT NULL,
"product_type" TEXT NULL,
"product_collection" TEXT NULL,
"product_handle" TEXT NULL,
"variant_sku" TEXT NULL,
"variant_barcode" TEXT NULL,
"variant_title" TEXT NULL,
"variant_option_values" JSONB NULL,
"requires_shipping" BOOLEAN NOT NULL DEFAULT true,
"is_discountable" BOOLEAN NOT NULL DEFAULT true,
"is_tax_inclusive" BOOLEAN NOT NULL DEFAULT false,
"compare_at_unit_price" NUMERIC NULL,
"raw_compare_at_unit_price" JSONB NULL,
"unit_price" NUMERIC NOT NULL,
"raw_unit_price" JSONB NOT NULL,
"metadata" JSONB NULL,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
CONSTRAINT "order_line_item_pkey" PRIMARY KEY ("id")
);
CREATE INDEX IF NOT EXISTS "IDX_order_line_item_variant_id" ON "order_line_item" (
variant_id
);
CREATE INDEX IF NOT EXISTS "IDX_order_line_item_product_id" ON "order_line_item" (
product_id
);
CREATE TABLE IF NOT EXISTS "order_line_item_tax_line" (
"id" TEXT NOT NULL,
"description" TEXT NULL,
"tax_rate_id" TEXT NULL,
"code" TEXT NOT NULL,
"rate" NUMERIC NOT NULL,
"raw_rate" JSONB NOT NULL,
"provider_id" TEXT NULL,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
"item_id" TEXT NOT NULL,
CONSTRAINT "order_line_item_tax_line_pkey" PRIMARY KEY ("id")
);
CREATE INDEX IF NOT EXISTS "IDX_order_line_item_tax_line_item_id" ON "order_line_item_tax_line" (item_id);
CREATE TABLE IF NOT EXISTS "order_line_item_adjustment" (
"id" TEXT NOT NULL,
"description" TEXT NULL,
"promotion_id" TEXT NULL,
"code" TEXT NULL,
"amount" NUMERIC NOT NULL,
"raw_amount" JSONB NOT NULL,
"provider_id" TEXT NULL,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
"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);
CREATE TABLE IF NOT EXISTS "order_shipping_method" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" JSONB NULL,
"amount" NUMERIC NOT NULL,
"raw_amount" JSONB NOT NULL,
"is_tax_inclusive" BOOLEAN NOT NULL DEFAULT false,
"shipping_option_id" TEXT NULL,
"data" JSONB NULL,
"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")
);
CREATE INDEX IF NOT EXISTS "IDX_order_shipping_method_shipping_option_id" ON "order_shipping_method" (
shipping_option_id
);
CREATE TABLE IF NOT EXISTS "order_shipping_method_adjustment" (
"id" TEXT NOT NULL,
"description" TEXT NULL,
"promotion_id" TEXT NULL,
"code" TEXT NULL,
"amount" NUMERIC NOT NULL,
"raw_amount" JSONB NOT NULL,
"provider_id" TEXT NULL,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
"shipping_method_id" TEXT NOT NULL,
CONSTRAINT "order_shipping_method_adjustment_pkey" PRIMARY KEY ("id")
);
CREATE INDEX IF NOT EXISTS "IDX_order_shipping_method_adjustment_shipping_method_id" ON "order_shipping_method_adjustment" (
shipping_method_id
);
CREATE TABLE IF NOT EXISTS "order_shipping_method_tax_line" (
"id" TEXT NOT NULL,
"description" TEXT NULL,
"tax_rate_id" TEXT NULL,
"code" TEXT NOT NULL,
"rate" NUMERIC NOT NULL,
"raw_rate" JSONB NOT NULL,
"provider_id" TEXT NULL,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
"shipping_method_id" TEXT NOT NULL,
CONSTRAINT "order_shipping_method_tax_line_pkey" PRIMARY KEY ("id")
);
CREATE INDEX IF NOT EXISTS "IDX_order_shipping_method_tax_line_shipping_method_id" ON "order_shipping_method_tax_line" (
shipping_method_id
);
CREATE TABLE IF NOT EXISTS "order_transaction" (
"id" TEXT NOT NULL,
"order_id" TEXT NOT NULL,
"amount" NUMERIC NOT NULL,
"raw_amount" JSONB NOT NULL,
"currency_code" TEXT NOT NULL,
"reference" TEXT NULL,
"reference_id" TEXT NULL,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(),
CONSTRAINT "order_transaction_pkey" PRIMARY KEY ("id")
);
CREATE INDEX IF NOT EXISTS "IDX_order_transaction_order_id" ON "order_transaction" (
order_id
);
CREATE INDEX IF NOT EXISTS "IDX_order_transaction_currency_code" ON "order_transaction" (
currency_code
);
CREATE INDEX IF NOT EXISTS "IDX_order_transaction_reference_id" ON "order_transaction" (
reference_id
);
ALTER TABLE if exists "order"
ADD CONSTRAINT "order_shipping_address_id_foreign" FOREIGN KEY ("shipping_address_id") REFERENCES "order_address" ("id") ON
UPDATE CASCADE ON
DELETE CASCADE;
ALTER TABLE if exists "order"
ADD CONSTRAINT "order_billing_address_id_foreign" FOREIGN KEY ("billing_address_id") REFERENCES "order_address" ("id") ON
UPDATE CASCADE ON
DELETE CASCADE;
ALTER TABLE if exists "order_change"
ADD CONSTRAINT "order_change_order_id_foreign" FOREIGN KEY ("order_id") REFERENCES "order" ("id") ON
UPDATE CASCADE ON
DELETE CASCADE;
ALTER TABLE if exists "order_change_action"
ADD CONSTRAINT "order_change_action_order_change_id_foreign" FOREIGN KEY ("order_change_id") REFERENCES "order_change" ("id") ON
UPDATE CASCADE ON
DELETE CASCADE;
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_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_item" ("id") ON
UPDATE CASCADE ON
DELETE CASCADE;
ALTER TABLE if exists "order_line_item_tax_line"
ADD CONSTRAINT "order_line_item_tax_line_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_adjustment"
ADD CONSTRAINT "order_line_item_adjustment_item_id_foreign" FOREIGN KEY ("item_id") REFERENCES "order_line_item" ("id") ON
UPDATE CASCADE ON
DELETE CASCADE;
ALTER TABLE if exists "order_shipping"
ADD CONSTRAINT "order_shipping_order_id_foreign" FOREIGN KEY ("order_id") REFERENCES "order" ("id") ON
UPDATE CASCADE ON
DELETE CASCADE;
ALTER TABLE if exists "order_shipping_method_adjustment"
ADD CONSTRAINT "order_shipping_method_adjustment_shipping_method_id_foreign" FOREIGN KEY ("shipping_method_id") REFERENCES "order_shipping_method" ("id") ON
UPDATE CASCADE ON
DELETE CASCADE;
ALTER TABLE if exists "order_shipping_method_tax_line"
ADD CONSTRAINT "order_shipping_method_tax_line_shipping_method_id_foreign" FOREIGN KEY ("shipping_method_id") REFERENCES "order_shipping_method" ("id") ON
UPDATE CASCADE ON
DELETE CASCADE;
ALTER TABLE if exists "order_transaction"
ADD CONSTRAINT "order_transaction_order_id_foreign" FOREIGN KEY ("order_id") REFERENCES "order" ("id") ON
UPDATE CASCADE ON
DELETE CASCADE;
`
this.addSql(sql)
}
}

View File

@@ -0,0 +1,90 @@
import { DAL } from "@medusajs/types"
import {
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import {
BeforeCreate,
Entity,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
type OptionalAddressProps = DAL.EntityDateColumns
const CustomerIdIndex = createPsqlIndexStatementHelper({
tableName: "order_address",
columns: "customer_id",
})
@Entity({ tableName: "order_address" })
export default class Address {
[OptionalProps]: OptionalAddressProps
@PrimaryKey({ columnType: "text" })
id!: string
@Property({ columnType: "text", nullable: true })
@CustomerIdIndex.MikroORMIndex()
customer_id: string | null = null
@Property({ columnType: "text", nullable: true })
company: string | null = null
@Property({ columnType: "text", nullable: true })
first_name: string | null = null
@Property({ columnType: "text", nullable: true })
last_name: string | null = null
@Property({ columnType: "text", nullable: true })
address_1: string | null = null
@Property({ columnType: "text", nullable: true })
address_2: string | null = null
@Property({ columnType: "text", nullable: true })
city: string | null = null
@Property({ columnType: "text", nullable: true })
country_code: string | null = null
@Property({ columnType: "text", nullable: true })
province: string | null = null
@Property({ columnType: "text", nullable: true })
postal_code: string | null = null
@Property({ columnType: "text", nullable: true })
phone: string | null = null
@Property({ columnType: "jsonb", nullable: true })
metadata: Record<string, unknown> | null = null
@Property({
onCreate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
created_at: Date
@Property({
onCreate: () => new Date(),
onUpdate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
updated_at: Date
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "ordaddr")
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "ordaddr")
}
}

View File

@@ -0,0 +1,52 @@
import { BigNumberRawValue, DAL } from "@medusajs/types"
import { BigNumber, MikroOrmBigNumberProperty } from "@medusajs/utils"
import { OptionalProps, PrimaryKey, Property } from "@mikro-orm/core"
type OptionalAdjustmentLineProps = DAL.EntityDateColumns
/**
* As per the Mikro ORM docs, superclasses should use the abstract class definition
* Source: https://mikro-orm.io/docs/inheritance-mapping
*/
export default abstract class AdjustmentLine {
[OptionalProps]: OptionalAdjustmentLineProps
@PrimaryKey({ columnType: "text" })
id: string
@Property({ columnType: "text", nullable: true })
description: string | null = null
@Property({
columnType: "text",
nullable: true,
})
promotion_id: string | null = null
@Property({ columnType: "text", nullable: true })
code: string | null = null
@MikroOrmBigNumberProperty()
amount: BigNumber | number
@Property({ columnType: "jsonb" })
raw_amount: BigNumberRawValue
@Property({ columnType: "text", nullable: true })
provider_id: string | null = null
@Property({
onCreate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
created_at: Date
@Property({
onCreate: () => new Date(),
onUpdate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
updated_at: Date
}

View File

@@ -0,0 +1,14 @@
export { default as Address } from "./address"
export { default as LineItem } from "./line-item"
export { default as LineItemAdjustment } from "./line-item-adjustment"
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 OrderItem } from "./order-item"
export { default as OrderShippingMethod } from "./order-shipping-method"
export { default as OrderSummary } from "./order-summary"
export { default as ShippingMethod } from "./shipping-method"
export { default as ShippingMethodAdjustment } from "./shipping-method-adjustment"
export { default as ShippingMethodTaxLine } from "./shipping-method-tax-line"
export { default as Transaction } from "./transaction"

View File

@@ -0,0 +1,42 @@
import {
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import { BeforeCreate, Entity, ManyToOne, OnInit } from "@mikro-orm/core"
import AdjustmentLine from "./adjustment-line"
import LineItem from "./line-item"
const ItemIdIndex = createPsqlIndexStatementHelper({
tableName: "order_line_item_adjustment",
columns: "item_id",
})
@Entity({ tableName: "order_line_item_adjustment" })
export default class LineItemAdjustment extends AdjustmentLine {
@ManyToOne(() => LineItem, {
persist: false,
})
item: LineItem
@ManyToOne({
entity: () => LineItem,
columnType: "text",
fieldName: "item_id",
onDelete: "cascade",
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
}
}

View File

@@ -0,0 +1,49 @@
import {
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import {
BeforeCreate,
Cascade,
Entity,
ManyToOne,
OnInit,
} from "@mikro-orm/core"
import LineItem from "./line-item"
import TaxLine from "./tax-line"
const ItemIdIndex = createPsqlIndexStatementHelper({
tableName: "order_line_item_tax_line",
columns: "item_id",
})
@Entity({ tableName: "order_line_item_tax_line" })
export default class LineItemTaxLine extends TaxLine {
@ManyToOne(() => LineItem, {
fieldName: "item_id",
persist: false,
})
item: LineItem
@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
}
}

View File

@@ -0,0 +1,161 @@
import { BigNumberRawValue, DAL } from "@medusajs/types"
import {
BigNumber,
MikroOrmBigNumberProperty,
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import {
BeforeCreate,
Cascade,
Collection,
Entity,
OnInit,
OneToMany,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import LineItemAdjustment from "./line-item-adjustment"
import LineItemTaxLine from "./line-item-tax-line"
type OptionalLineItemProps =
| "is_discoutable"
| "is_tax_inclusive"
| "compare_at_unit_price"
| "requires_shipping"
| DAL.EntityDateColumns
const ProductIdIndex = createPsqlIndexStatementHelper({
tableName: "order_line_item",
columns: "product_id",
})
const VariantIdIndex = createPsqlIndexStatementHelper({
tableName: "order_line_item",
columns: "variant_id",
})
@Entity({ tableName: "order_line_item" })
export default class LineItem {
[OptionalProps]?: OptionalLineItemProps
@PrimaryKey({ columnType: "text" })
id: string
@Property({ columnType: "text" })
title: string
@Property({ columnType: "text", nullable: true })
subtitle: string | null = null
@Property({ columnType: "text", nullable: true })
thumbnail: string | null = null
@Property({
columnType: "text",
nullable: true,
})
@VariantIdIndex.MikroORMIndex()
variant_id: string | null = null
@Property({
columnType: "text",
nullable: true,
})
@ProductIdIndex.MikroORMIndex()
product_id: string | null = null
@Property({ columnType: "text", nullable: true })
product_title: string | null = null
@Property({ columnType: "text", nullable: true })
product_description: string | null = null
@Property({ columnType: "text", nullable: true })
product_subtitle: string | null = null
@Property({ columnType: "text", nullable: true })
product_type: string | null = null
@Property({ columnType: "text", nullable: true })
product_collection: string | null = null
@Property({ columnType: "text", nullable: true })
product_handle: string | null = null
@Property({ columnType: "text", nullable: true })
variant_sku: string | null = null
@Property({ columnType: "text", nullable: true })
variant_barcode: string | null = null
@Property({ columnType: "text", nullable: true })
variant_title: string | null = null
@Property({ columnType: "jsonb", nullable: true })
variant_option_values: Record<string, unknown> | null = null
@Property({ columnType: "boolean" })
requires_shipping = true
@Property({ columnType: "boolean" })
is_discountable = true
@Property({ columnType: "boolean" })
is_tax_inclusive = false
@MikroOrmBigNumberProperty({
nullable: true,
})
compare_at_unit_price?: BigNumber | number | null = null
@Property({ columnType: "jsonb", nullable: true })
raw_compare_at_unit_price: BigNumberRawValue | null = null
@MikroOrmBigNumberProperty({
nullable: true,
})
unit_price: BigNumber | number
@Property({ columnType: "jsonb" })
raw_unit_price: BigNumberRawValue
@OneToMany(() => LineItemTaxLine, (taxLine) => taxLine.item, {
cascade: [Cascade.PERSIST, "soft-remove" as Cascade],
})
tax_lines = new Collection<LineItemTaxLine>(this)
@OneToMany(() => LineItemAdjustment, (adjustment) => adjustment.item, {
cascade: [Cascade.PERSIST, "soft-remove" as Cascade],
})
adjustments = new Collection<LineItemAdjustment>(this)
@Property({ columnType: "jsonb", nullable: true })
metadata: Record<string, unknown> | null = null
@Property({
onCreate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
created_at: Date
@Property({
onCreate: () => new Date(),
onUpdate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
updated_at: Date
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "ordli")
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "ordli")
}
}

View File

@@ -0,0 +1,151 @@
import { BigNumberRawValue, DAL } from "@medusajs/types"
import {
BigNumber,
MikroOrmBigNumberProperty,
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import {
BeforeCreate,
Entity,
ManyToOne,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import Order from "./order"
import OrderChange from "./order-change"
type OptionalLineItemProps = DAL.EntityDateColumns
const OrderChangeIdIndex = createPsqlIndexStatementHelper({
tableName: "order_change_action",
columns: "order_change_id",
})
const OrderIdIndex = createPsqlIndexStatementHelper({
tableName: "order_change_action",
columns: "order_id",
})
const ActionOrderingIndex = createPsqlIndexStatementHelper({
tableName: "order_change_action",
columns: "ordering",
})
@Entity({ tableName: "order_change_action" })
export default class OrderChangeAction {
[OptionalProps]?: OptionalLineItemProps
@PrimaryKey({ columnType: "text" })
id: string
@Property({ columnType: "integer", autoincrement: true })
@ActionOrderingIndex.MikroORMIndex()
ordering: number
@ManyToOne({
entity: () => Order,
columnType: "text",
fieldName: "order_id",
onDelete: "cascade",
mapToPk: true,
nullable: true,
})
@OrderIdIndex.MikroORMIndex()
order_id: string | null = null
@ManyToOne(() => Order, {
persist: false,
nullable: true,
})
order: Order | null = null
@Property({ columnType: "integer", nullable: true })
version: number | null = null
@ManyToOne({
entity: () => OrderChange,
columnType: "text",
fieldName: "order_change_id",
onDelete: "cascade",
mapToPk: true,
nullable: true,
})
@OrderChangeIdIndex.MikroORMIndex()
order_change_id: string | null
@ManyToOne(() => OrderChange, {
persist: false,
nullable: true,
})
order_change: OrderChange | null = null
@Property({
columnType: "text",
nullable: true,
})
reference: string | null = null
@Property({
columnType: "text",
nullable: true,
})
reference_id: string | null = null
@Property({ columnType: "text" })
action: string
@Property({ columnType: "jsonb" })
details: Record<string, unknown> = {}
@MikroOrmBigNumberProperty({ nullable: true })
amount: BigNumber | number | null = null
@Property({ columnType: "jsonb", nullable: true })
raw_amount: BigNumberRawValue | null = null
@Property({
columnType: "text",
nullable: true,
})
internal_note: string | null = null
@Property({
columnType: "boolean",
defaultRaw: "false",
})
applied: boolean = false
@Property({
onCreate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
created_at: Date
@Property({
onCreate: () => new Date(),
onUpdate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
updated_at: Date
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "ordchact")
this.order_id ??= this.order?.id ?? this.order_change?.order_id ?? null
this.order_change_id ??= this.order_change?.id ?? null
this.version ??= this.order_change?.version ?? null
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "ordchact")
this.order_id ??= this.order?.id ?? this.order_change?.order_id ?? null
this.order_change_id ??= this.order_change?.id ?? null
this.version ??= this.order_change?.version ?? null
}
}

View File

@@ -0,0 +1,156 @@
import { DAL } from "@medusajs/types"
import {
OrderChangeStatus,
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import {
BeforeCreate,
Cascade,
Collection,
Entity,
Enum,
ManyToOne,
OnInit,
OneToMany,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import Order from "./order"
import OrderChangeAction from "./order-change-action"
type OptionalLineItemProps = DAL.EntityDateColumns
const OrderIdIndex = createPsqlIndexStatementHelper({
tableName: "order_change",
columns: "order_id",
})
const OrderChangeStatusIndex = createPsqlIndexStatementHelper({
tableName: "order_change",
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
@ManyToOne({
entity: () => Order,
columnType: "text",
fieldName: "order_id",
onDelete: "cascade",
mapToPk: true,
})
@OrderIdIndex.MikroORMIndex()
order_id: string
@ManyToOne(() => Order, {
persist: false,
})
order: Order
@Property({ columnType: "integer" })
@VersionIndex.MikroORMIndex()
version: number
@OneToMany(() => OrderChangeAction, (action) => action.order_change, {
cascade: [Cascade.PERSIST, "sotf-remove" as Cascade],
})
actions = new Collection<OrderChangeAction>(this)
@Property({
columnType: "text",
nullable: true,
})
description: string | null = null
@Enum({ items: () => OrderChangeStatus, default: OrderChangeStatus.PENDING })
@OrderChangeStatusIndex.MikroORMIndex()
status: OrderChangeStatus = OrderChangeStatus.PENDING
@Property({ columnType: "text", nullable: true })
internal_note: string | null = null
@Property({ columnType: "text", nullable: true })
created_by: string // customer, user, third party, etc.
@Property({ columnType: "text", nullable: true })
requested_by: string | null = null // customer or user ID
@Property({
columnType: "timestamptz",
nullable: true,
})
requested_at: Date | null = null
@Property({ columnType: "text", nullable: true })
confirmed_by: string | null = null // customer or user ID
@Property({
columnType: "timestamptz",
nullable: true,
})
confirmed_at: Date | null = null
@Property({ columnType: "text", nullable: true })
declined_by: string | null = null // customer or user ID
@Property({ columnType: "text", nullable: true })
declined_reason: string | null = null
@Property({ columnType: "jsonb", nullable: true })
metadata: Record<string, unknown> | null = null
@Property({
columnType: "timestamptz",
nullable: true,
})
declined_at?: Date
@Property({ columnType: "text", nullable: true })
canceled_by: string | null = null
@Property({
columnType: "timestamptz",
nullable: true,
})
canceled_at?: Date
@Property({
onCreate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
created_at: Date
@Property({
onCreate: () => new Date(),
onUpdate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
updated_at: Date
@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
}
}

View File

@@ -0,0 +1,164 @@
import { BigNumberRawValue, DAL } from "@medusajs/types"
import {
BigNumber,
MikroOrmBigNumberProperty,
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import {
BeforeCreate,
Entity,
ManyToOne,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import LineItem from "./line-item"
import Order from "./order"
type OptionalLineItemProps = DAL.EntityDateColumns
const OrderIdIndex = createPsqlIndexStatementHelper({
tableName: "order_item",
columns: ["order_id"],
where: "deleted_at IS NOT NULL",
})
const OrderVersionIndex = createPsqlIndexStatementHelper({
tableName: "order_item",
columns: ["version"],
where: "deleted_at IS NOT NULL",
})
const ItemIdIndex = createPsqlIndexStatementHelper({
tableName: "order_item",
columns: ["item_id"],
where: "deleted_at IS NOT NULL",
})
const DeletedAtIndex = createPsqlIndexStatementHelper({
tableName: "order",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
})
@Entity({ tableName: "order_item" })
export default class OrderItem {
[OptionalProps]?: OptionalLineItemProps
@PrimaryKey({ columnType: "text" })
id: string
@ManyToOne({
entity: () => Order,
mapToPk: true,
fieldName: "order_id",
columnType: "text",
})
@OrderIdIndex.MikroORMIndex()
order_id: string
@Property({ columnType: "integer" })
@OrderVersionIndex.MikroORMIndex()
version: number
@ManyToOne(() => Order, {
persist: false,
})
order: Order
@ManyToOne({
entity: () => LineItem,
fieldName: "item_id",
mapToPk: true,
columnType: "text",
})
@ItemIdIndex.MikroORMIndex()
item_id: string
@ManyToOne(() => LineItem, {
persist: false,
})
item: LineItem
@MikroOrmBigNumberProperty()
quantity: BigNumber | number
@Property({ columnType: "jsonb" })
raw_quantity: BigNumberRawValue
@MikroOrmBigNumberProperty()
fulfilled_quantity: BigNumber | number = 0
@Property({ columnType: "jsonb" })
raw_fulfilled_quantity: BigNumberRawValue
@MikroOrmBigNumberProperty()
shipped_quantity: BigNumber | number = 0
@Property({ columnType: "jsonb" })
raw_shipped_quantity: BigNumberRawValue
@MikroOrmBigNumberProperty()
return_requested_quantity: BigNumber | number = 0
@Property({ columnType: "jsonb" })
raw_return_requested_quantity: BigNumberRawValue
@MikroOrmBigNumberProperty()
return_received_quantity: BigNumber | number = 0
@Property({ columnType: "jsonb" })
raw_return_received_quantity: BigNumberRawValue
@MikroOrmBigNumberProperty()
return_dismissed_quantity: BigNumber | number = 0
@Property({ columnType: "jsonb" })
raw_return_dismissed_quantity: BigNumberRawValue
@MikroOrmBigNumberProperty()
written_off_quantity: BigNumber | number = 0
@Property({ columnType: "jsonb" })
raw_written_off_quantity: BigNumberRawValue
@Property({ columnType: "jsonb", nullable: true })
metadata: Record<string, unknown> | null = null
@Property({
onCreate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
created_at: Date
@Property({
onCreate: () => new Date(),
onUpdate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
updated_at: Date
@Property({ columnType: "timestamptz", nullable: true })
@DeletedAtIndex.MikroORMIndex()
deleted_at: Date | null = null
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "orditem")
this.order_id ??= this.order?.id
this.item_id ??= this.item?.id
this.version ??= this.order?.version
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "orditem")
this.order_id ??= this.order?.id
this.item_id ??= this.item?.id
this.version ??= this.order?.version
}
}

View File

@@ -0,0 +1,117 @@
import { DAL } from "@medusajs/types"
import {
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import {
BeforeCreate,
Entity,
ManyToOne,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import Order from "./order"
import ShippingMethod from "./shipping-method"
type OptionalShippingMethodProps = DAL.EntityDateColumns
const OrderIdIndex = createPsqlIndexStatementHelper({
tableName: "order_shipping",
columns: ["order_id"],
where: "deleted_at IS NOT NULL",
})
const OrderVersionIndex = createPsqlIndexStatementHelper({
tableName: "order_shipping",
columns: ["version"],
where: "deleted_at IS NOT NULL",
})
const ItemIdIndex = createPsqlIndexStatementHelper({
tableName: "order_shipping",
columns: ["shipping_method_id"],
where: "deleted_at IS NOT NULL",
})
const DeletedAtIndex = createPsqlIndexStatementHelper({
tableName: "order",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
})
@Entity({ tableName: "order_shipping" })
export default class OrderShippingMethod {
[OptionalProps]?: OptionalShippingMethodProps
@PrimaryKey({ columnType: "text" })
id: string
@ManyToOne({
entity: () => Order,
mapToPk: true,
fieldName: "order_id",
columnType: "text",
})
@OrderIdIndex.MikroORMIndex()
order_id: string
@Property({ columnType: "integer" })
@OrderVersionIndex.MikroORMIndex()
version: number
@ManyToOne(() => Order, {
persist: false,
})
order: Order
@ManyToOne({
entity: () => ShippingMethod,
fieldName: "shipping_method_id",
mapToPk: true,
columnType: "text",
})
@ItemIdIndex.MikroORMIndex()
shipping_method_id: string
@ManyToOne(() => ShippingMethod, {
persist: false,
})
shipping_method: ShippingMethod
@Property({
onCreate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
created_at: Date
@Property({
onCreate: () => new Date(),
onUpdate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
updated_at: Date
@Property({ columnType: "timestamptz", nullable: true })
@DeletedAtIndex.MikroORMIndex()
deleted_at: Date | null = null
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "ordspmv")
this.order_id ??= this.order?.id
this.shipping_method_id ??= this.shipping_method?.id
this.version ??= this.order?.version
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "ordspmv")
this.order_id ??= this.order?.id
this.shipping_method_id ??= this.shipping_method?.id
this.version ??= this.order?.version
}
}

View File

@@ -0,0 +1,112 @@
import {
BigNumber,
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import {
BeforeCreate,
Entity,
ManyToOne,
OnInit,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import { Order } from "@models"
type OrderSummaryTotals = {
total: BigNumber
subtotal: BigNumber
total_tax: BigNumber
ordered_total: BigNumber
fulfilled_total: BigNumber
returned_total: BigNumber
return_request_total: BigNumber
write_off_total: BigNumber
projected_total: BigNumber
net_total: BigNumber
net_subtotal: BigNumber
net_total_tax: BigNumber
future_total: BigNumber
future_subtotal: BigNumber
future_total_tax: BigNumber
future_projected_total: BigNumber
balance: BigNumber
future_balance: BigNumber
}
const OrderIdVersionIndex = createPsqlIndexStatementHelper({
tableName: "order_summary",
columns: ["order_id", "version"],
where: "deleted_at IS NOT NULL",
})
const DeletedAtIndex = createPsqlIndexStatementHelper({
tableName: "order",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
})
@Entity({ tableName: "order_summary" })
@OrderIdVersionIndex.MikroORMIndex()
export default class OrderSummary {
@PrimaryKey({ columnType: "text" })
id: string
@ManyToOne({
entity: () => Order,
columnType: "text",
fieldName: "order_id",
mapToPk: true,
onDelete: "cascade",
})
order_id: string
@ManyToOne(() => Order, {
persist: false,
})
order: Order
@Property({
columnType: "integer",
defaultRaw: "1",
})
version: number = 1
@Property({ columnType: "jsonb" })
totals: OrderSummaryTotals | null = {} as OrderSummaryTotals
@Property({
onCreate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
created_at: Date
@Property({
onCreate: () => new Date(),
onUpdate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
updated_at: Date
@Property({ columnType: "timestamptz", nullable: true })
@DeletedAtIndex.MikroORMIndex()
deleted_at: Date | null = null
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "ordsum")
this.order_id ??= this.order?.id
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "ordsum")
this.order_id ??= this.order?.id
}
}

View File

@@ -0,0 +1,214 @@
import { DAL } from "@medusajs/types"
import {
OrderStatus,
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import {
BeforeCreate,
Cascade,
Collection,
Entity,
Enum,
ManyToOne,
OnInit,
OneToMany,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import Address from "./address"
import OrderItem from "./order-item"
import OrderShippingMethod from "./order-shipping-method"
import OrderSummary from "./order-summary"
import Transaction from "./transaction"
type OptionalOrderProps =
| "shipping_address"
| "billing_address"
| DAL.EntityDateColumns
const RegionIdIndex = createPsqlIndexStatementHelper({
tableName: "order",
columns: "region_id",
where: "deleted_at IS NOT NULL",
})
const CustomerIdIndex = createPsqlIndexStatementHelper({
tableName: "order",
columns: "customer_id",
where: "deleted_at IS NOT NULL",
})
const SalesChannelIdIndex = createPsqlIndexStatementHelper({
tableName: "order",
columns: "customer_id",
where: "deleted_at IS NOT NULL",
})
const OrderDeletedAtIndex = createPsqlIndexStatementHelper({
tableName: "order",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
})
const CurrencyCodeIndex = createPsqlIndexStatementHelper({
tableName: "order",
columns: "currency_code",
where: "deleted_at IS NOT NULL",
})
const ShippingAddressIdIndex = createPsqlIndexStatementHelper({
tableName: "order",
columns: "shipping_address_id",
where: "deleted_at IS NOT NULL",
})
const BillingAddressIdIndex = createPsqlIndexStatementHelper({
tableName: "order",
columns: "billing_address_id",
where: "deleted_at IS NOT NULL",
})
const IsDraftOrderIndex = createPsqlIndexStatementHelper({
tableName: "order",
columns: "is_draft_order",
where: "deleted_at IS NOT NULL",
})
@Entity({ tableName: "order" })
export default class Order {
[OptionalProps]?: OptionalOrderProps
@PrimaryKey({ columnType: "text" })
id: string
@Property({
columnType: "text",
nullable: true,
})
@RegionIdIndex.MikroORMIndex()
region_id: string | null = null
@Property({
columnType: "text",
nullable: true,
})
@CustomerIdIndex.MikroORMIndex()
customer_id: string | null = null
@Property({
columnType: "integer",
defaultRaw: "1",
})
version: number = 1
@Property({
columnType: "text",
nullable: true,
})
@SalesChannelIdIndex.MikroORMIndex()
sales_channel_id: string | null = null
@Enum({ items: () => OrderStatus, default: OrderStatus.PENDING })
status: OrderStatus
@Property({
columnType: "boolean",
})
@IsDraftOrderIndex.MikroORMIndex()
is_draft_order = false
@Property({ columnType: "text", nullable: true })
email: string | null = null
@Property({ columnType: "text" })
@CurrencyCodeIndex.MikroORMIndex()
currency_code: string
@Property({ columnType: "text", nullable: true })
@ShippingAddressIdIndex.MikroORMIndex()
shipping_address_id?: string | null
@ManyToOne({
entity: () => Address,
fieldName: "shipping_address_id",
nullable: true,
cascade: [Cascade.PERSIST],
})
shipping_address?: Address | null
@Property({ columnType: "text", nullable: true })
@BillingAddressIdIndex.MikroORMIndex()
billing_address_id?: string | null
@ManyToOne({
entity: () => Address,
fieldName: "billing_address_id",
nullable: true,
cascade: [Cascade.PERSIST],
})
billing_address?: Address | null
@Property({ columnType: "boolean", nullable: true })
no_notification: boolean | null = null
@OneToMany(() => OrderSummary, (summary) => summary.order, {
cascade: [Cascade.PERSIST],
})
summary = new Collection<OrderSummary>(this)
@Property({ columnType: "jsonb", nullable: true })
metadata: Record<string, unknown> | null = null
@OneToMany(() => OrderItem, (itemDetail) => itemDetail.order, {
cascade: [Cascade.PERSIST],
})
items = new Collection<OrderItem>(this)
@OneToMany(
() => OrderShippingMethod,
(shippingMethod) => shippingMethod.order,
{
cascade: [Cascade.PERSIST],
}
)
shipping_methods = new Collection<OrderShippingMethod>(this)
@OneToMany(() => Transaction, (transaction) => transaction.order, {
cascade: [Cascade.PERSIST],
})
transactions = new Collection<Transaction>(this)
@Property({
onCreate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
created_at: Date
@Property({
onCreate: () => new Date(),
onUpdate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
updated_at: Date
@Property({ columnType: "timestamptz", nullable: true })
@OrderDeletedAtIndex.MikroORMIndex()
deleted_at: Date | null = null
@Property({ columnType: "timestamptz", nullable: true })
canceled_at: Date | null = null
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "order")
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "order")
}
}

View File

@@ -0,0 +1,42 @@
import {
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import { BeforeCreate, Entity, ManyToOne, OnInit } from "@mikro-orm/core"
import AdjustmentLine from "./adjustment-line"
import ShippingMethod from "./shipping-method"
const ShippingMethodIdIdIndex = createPsqlIndexStatementHelper({
tableName: "order_shipping_method_adjustment",
columns: "shipping_method_id",
})
@Entity({ tableName: "order_shipping_method_adjustment" })
export default class ShippingMethodAdjustment extends AdjustmentLine {
@ManyToOne(() => ShippingMethod, {
persist: false,
})
shipping_method: ShippingMethod
@ManyToOne({
entity: () => ShippingMethod,
columnType: "text",
fieldName: "shipping_method_id",
mapToPk: true,
onDelete: "cascade",
})
@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
}
}

View File

@@ -0,0 +1,42 @@
import {
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import { BeforeCreate, Entity, ManyToOne, OnInit } from "@mikro-orm/core"
import ShippingMethod from "./shipping-method"
import TaxLine from "./tax-line"
const ShippingMethodIdIdIndex = createPsqlIndexStatementHelper({
tableName: "order_shipping_method_tax_line",
columns: "shipping_method_id",
})
@Entity({ tableName: "order_shipping_method_tax_line" })
export default class ShippingMethodTaxLine extends TaxLine {
@ManyToOne(() => ShippingMethod, {
persist: false,
})
shipping_method: ShippingMethod
@ManyToOne({
entity: () => ShippingMethod,
fieldName: "shipping_method_id",
columnType: "text",
mapToPk: true,
onDelete: "cascade",
})
@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
}
}

View File

@@ -0,0 +1,100 @@
import { BigNumberRawValue } from "@medusajs/types"
import {
BigNumber,
createPsqlIndexStatementHelper,
generateEntityId,
MikroOrmBigNumberProperty,
} from "@medusajs/utils"
import {
BeforeCreate,
Cascade,
Collection,
Entity,
OneToMany,
OnInit,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import ShippingMethodAdjustment from "./shipping-method-adjustment"
import ShippingMethodTaxLine from "./shipping-method-tax-line"
const ShippingOptionIdIndex = createPsqlIndexStatementHelper({
tableName: "order_shipping_method",
columns: "shipping_option_id",
})
@Entity({ tableName: "order_shipping_method" })
export default class ShippingMethod {
@PrimaryKey({ columnType: "text" })
id: string
@Property({ columnType: "text" })
name: string
@Property({ columnType: "jsonb", nullable: true })
description: string | null = null
@MikroOrmBigNumberProperty()
amount: BigNumber | number
@Property({ columnType: "jsonb" })
raw_amount: BigNumberRawValue
@Property({ columnType: "boolean" })
is_tax_inclusive = false
@Property({
columnType: "text",
nullable: true,
})
@ShippingOptionIdIndex.MikroORMIndex()
shipping_option_id: string | null = null
@Property({ columnType: "jsonb", nullable: true })
data: Record<string, unknown> | null = null
@Property({ columnType: "jsonb", nullable: true })
metadata: Record<string, unknown> | null = null
@OneToMany(
() => ShippingMethodTaxLine,
(taxLine) => taxLine.shipping_method,
{
cascade: [Cascade.PERSIST],
}
)
tax_lines = new Collection<ShippingMethodTaxLine>(this)
@OneToMany(
() => ShippingMethodAdjustment,
(adjustment) => adjustment.shipping_method,
{
cascade: [Cascade.PERSIST],
}
)
adjustments = new Collection<ShippingMethodAdjustment>(this)
@Property({
onCreate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
created_at: Date
@Property({
onCreate: () => new Date(),
onUpdate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
updated_at: Date
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "ordsm")
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "ordsm")
}
}

View File

@@ -0,0 +1,48 @@
import { BigNumberRawValue } from "@medusajs/types"
import {BigNumber, MikroOrmBigNumberProperty} from "@medusajs/utils"
import { PrimaryKey, Property } from "@mikro-orm/core"
/**
* As per the Mikro ORM docs, superclasses should use the abstract class definition
* Source: https://mikro-orm.io/docs/inheritance-mapping
*/
export default abstract class TaxLine {
@PrimaryKey({ columnType: "text" })
id: string
@Property({ columnType: "text", nullable: true })
description?: string | null
@Property({
columnType: "text",
nullable: true,
})
tax_rate_id?: string | null
@Property({ columnType: "text" })
code: string
@MikroOrmBigNumberProperty()
rate: BigNumber | number
@Property({ columnType: "jsonb" })
raw_rate: BigNumberRawValue
@Property({ columnType: "text", nullable: true })
provider_id?: string | null
@Property({
onCreate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
created_at: Date
@Property({
onCreate: () => new Date(),
onUpdate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
updated_at: Date
}

View File

@@ -0,0 +1,107 @@
import { BigNumberRawValue, DAL } from "@medusajs/types"
import {
BigNumber,
MikroOrmBigNumberProperty,
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import {
BeforeCreate,
Entity,
ManyToOne,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import Order from "./order"
type OptionalLineItemProps = DAL.EntityDateColumns
const ReferenceIdIndex = createPsqlIndexStatementHelper({
tableName: "order_transaction",
columns: "reference_id",
})
const OrderIdIndex = createPsqlIndexStatementHelper({
tableName: "order_transaction",
columns: "order_id",
})
const CurrencyCodeIndex = createPsqlIndexStatementHelper({
tableName: "order_transaction",
columns: "currency_code",
})
@Entity({ tableName: "order_transaction" })
export default class Transaction {
[OptionalProps]?: OptionalLineItemProps
@PrimaryKey({ columnType: "text" })
id: string
@ManyToOne({
entity: () => Order,
columnType: "text",
fieldName: "order_id",
onDelete: "cascade",
mapToPk: true,
})
@OrderIdIndex.MikroORMIndex()
order_id: string
@ManyToOne(() => Order, {
persist: false,
})
order: Order
@MikroOrmBigNumberProperty()
amount: BigNumber | number
@Property({ columnType: "jsonb" })
raw_amount: BigNumberRawValue
@Property({ columnType: "text" })
@CurrencyCodeIndex.MikroORMIndex()
currency_code: string
@Property({
columnType: "text",
nullable: true,
})
reference: string | null = null
@Property({
columnType: "text",
nullable: true,
})
@ReferenceIdIndex.MikroORMIndex()
reference_id: string | null = null
@Property({
onCreate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
created_at: Date
@Property({
onCreate: () => new Date(),
onUpdate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
updated_at: Date
@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
}
}

View File

@@ -0,0 +1,44 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleExports } from "@medusajs/types"
import { ModulesSdkUtils } from "@medusajs/utils"
import * as Models from "@models"
import * as ModuleModels from "@models"
import * as ModuleRepositories from "@repositories"
import * as ModuleServices from "@services"
import { OrderModuleService } from "@services"
const migrationScriptOptions = {
moduleName: Modules.ORDER,
models: Models,
pathToMigrations: __dirname + "/migrations",
}
const runMigrations = ModulesSdkUtils.buildMigrationScript(
migrationScriptOptions
)
const revertMigration = ModulesSdkUtils.buildRevertMigrationScript(
migrationScriptOptions
)
const containerLoader = ModulesSdkUtils.moduleContainerLoaderFactory({
moduleModels: ModuleModels,
moduleRepositories: ModuleRepositories,
moduleServices: ModuleServices,
})
const connectionLoader = ModulesSdkUtils.mikroOrmConnectionLoaderFactory({
moduleName: Modules.ORDER,
moduleModels: Object.values(Models),
migrationsPath: __dirname + "/migrations",
})
const service = OrderModuleService
const loaders = [containerLoader, connectionLoader] as any
export const moduleDefinition: ModuleExports = {
service,
loaders,
revertMigration,
runMigrations,
}

View File

@@ -0,0 +1,2 @@
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
export * from "./order"

View File

@@ -0,0 +1,121 @@
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_)
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
config.options.populateWhere.summary ??= {}
config.options.populateWhere.summary.version = version
config.options.populateWhere.shipping_methods ??= {}
config.options.populateWhere.shipping_methods.version = version
if (!config.options.orderBy) {
config.options.orderBy = { id: "ASC" }
}
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_)
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
config.options.populateWhere.summary ??= {}
config.options.populateWhere.summary.version = version
config.options.populateWhere.shipping_methods ??= {}
config.options.populateWhere.shipping_methods.version = version
if (!config.options.orderBy) {
config.options.orderBy = { id: "ASC" }
}
return await manager.findAndCount(Order, config.where, config.options)
}
}

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env node
import { Modules } from "@medusajs/modules-sdk"
import { ModulesSdkUtils } from "@medusajs/utils"
import * as Models from "@models"
import { EOL } from "os"
const args = process.argv
const path = args.pop() as string
export default (async () => {
const { config } = await import("dotenv")
config()
if (!path) {
throw new Error(
`filePath is required.${EOL}Example: medusa-order-seed <filePath>`
)
}
const run = ModulesSdkUtils.buildSeedScript({
moduleName: Modules.ORDER,
models: Models,
pathToMigrations: __dirname + "/../../migrations",
seedHandler: async ({ manager, data }) => {
// TODO: Add seed logic
},
})
await run({ path })
})()

View File

@@ -0,0 +1,160 @@
import { OrderChangeEvent } from "../../../../types"
import { ChangeActionType, calculateOrderChange } from "../../../../utils"
describe("Order Exchange - Actions", function () {
const originalOrder = {
items: [
{
id: "1",
quantity: 1,
unit_price: 10,
detail: {
quantity: 1,
shipped_quantity: 1,
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,
detail: {
quantity: 2,
shipped_quantity: 1,
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,
detail: {
quantity: 3,
shipped_quantity: 3,
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,
}
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,
})
const sumToJSON = JSON.parse(JSON.stringify(changes.summary))
expect(sumToJSON).toEqual({
transactionTotal: 0,
originalOrderTotal: 270,
currentOrderTotal: 312.5,
temporaryDifference: 62.5,
futureDifference: 0,
futureTemporaryDifference: 0,
pendingDifference: 312.5,
differenceSum: 42.5,
})
const toJson = JSON.parse(JSON.stringify(changes.order.items))
expect(toJson).toEqual([
{
id: "1",
quantity: 1,
unit_price: 10,
detail: {
quantity: 1,
shipped_quantity: 1,
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,
detail: {
quantity: 2,
shipped_quantity: 1,
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,
detail: {
quantity: 3,
shipped_quantity: 3,
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,
},
])
})
})

View File

@@ -0,0 +1,305 @@
import { OrderChangeEvent } from "../../../../types"
import { ChangeActionType, calculateOrderChange } from "../../../../utils"
describe("Order Return - Actions", function () {
const originalOrder = {
items: [
{
id: "1",
quantity: 1,
unit_price: 10,
detail: {
quantity: 1,
shipped_quantity: 1,
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,
detail: {
quantity: 2,
shipped_quantity: 1,
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,
detail: {
quantity: 3,
shipped_quantity: 3,
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,
}
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 shipped for item 1."
)
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,
})
const toJson = JSON.parse(JSON.stringify(changes.order.items))
expect(toJson).toEqual([
{
id: "1",
quantity: 1,
unit_price: 10,
detail: {
quantity: 1,
shipped_quantity: 1,
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,
detail: {
quantity: 2,
shipped_quantity: 1,
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,
detail: {
quantity: 3,
shipped_quantity: 3,
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 shipped for item 3."
)
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 for item 3."
)
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 for item 3."
)
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,
},
},
],
})
const toJsonReceived = JSON.parse(
JSON.stringify(receivedChanges.order.items)
)
expect(toJsonReceived).toEqual([
{
id: "1",
quantity: 1,
unit_price: 10,
detail: {
quantity: 1,
shipped_quantity: 1,
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,
detail: {
quantity: 2,
shipped_quantity: 1,
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,
detail: {
quantity: 3,
shipped_quantity: 3,
fulfilled_quantity: 3,
return_requested_quantity: "0",
return_received_quantity: "1",
return_dismissed_quantity: "1",
written_off_quantity: 0,
},
},
])
})
})

View File

@@ -0,0 +1,3 @@
export { default as OrderChangeService } from "./order-change-service"
export { default as OrderModuleService } from "./order-module-service"
export { default as OrderService } from "./order-service"

View File

@@ -0,0 +1,139 @@
import {
Context,
DAL,
FindConfig,
OrderTypes,
RepositoryService,
} from "@medusajs/types"
import {
InjectManager,
InjectTransactionManager,
MedusaContext,
MedusaError,
ModulesSdkUtils,
OrderChangeStatus,
deduplicate,
} from "@medusajs/utils"
import { OrderChange } from "@models"
type InjectedDependencies = {
orderChangeRepository: DAL.RepositoryService
}
export default class OrderChangeService<
TEntity extends OrderChange = OrderChange
> extends ModulesSdkUtils.internalModuleServiceFactory<InjectedDependencies>(
OrderChange
)<TEntity> {
protected readonly orderChangeRepository_: RepositoryService<TEntity>
constructor(container: InjectedDependencies) {
// @ts-ignore
super(...arguments)
this.orderChangeRepository_ = container.orderChangeRepository
}
@InjectManager("orderChangeRepository_")
async listCurrentOrderChange<TEntityMethod = OrderTypes.OrderDTO>(
orderId: string | string[],
config: FindConfig<TEntityMethod> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity[]> {
const allChanges = await super.list(
{ order_id: orderId },
config ?? {
select: ["order_id", "status", "version"],
order: {
order_id: "ASC",
version: "DESC",
},
}
)
if (!allChanges.length) {
return []
}
const lastChanges: string[] = []
const seen = new Set()
for (let i = 0; i < allChanges.length; i++) {
if (seen.has(allChanges[i].order_id)) {
continue
}
seen.add(allChanges[i].order_id)
if (this.isActive(allChanges[i])) {
lastChanges.push(allChanges[i].id)
}
}
let orderChange!: TEntity
if (allChanges?.length > 0) {
if (this.isActive(allChanges[0])) {
orderChange = allChanges[0]
}
}
if (!orderChange) {
return []
}
const relations = deduplicate([...(config.relations ?? []), "actions"])
config.relations = relations
const queryConfig = ModulesSdkUtils.buildQuery<TEntity>(
{
id: lastChanges,
order: {
items: {
version: orderChange.version,
},
},
},
config
)
return await this.orderChangeRepository_.find(queryConfig, sharedContext)
}
isActive(orderChange: OrderChange): boolean {
return (
orderChange.status === OrderChangeStatus.PENDING ||
orderChange.status === OrderChangeStatus.REQUESTED
)
}
async create(
data: Partial<TEntity>[],
sharedContext?: Context
): Promise<TEntity[]>
async create(
data: Partial<TEntity>,
sharedContext?: Context
): Promise<TEntity>
@InjectTransactionManager("orderChangeRepository_")
async create(
data: Partial<TEntity>[] | Partial<TEntity>,
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity[] | TEntity> {
const dataArr = Array.isArray(data) ? data : [data]
const activeOrderEdit = await this.listCurrentOrderChange(
dataArr.map((d) => d.order_id!),
{},
sharedContext
)
if (activeOrderEdit.length > 0) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`An active order change already exists for the order(s) ${activeOrderEdit
.map((a) => a.order_id)
.join(",")}`
)
}
return await super.create(dataArr, sharedContext)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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
}
}

View File

@@ -0,0 +1,11 @@
import { OrderTypes } from "@medusajs/types"
export type UpsertOrderAddressDTO = OrderTypes.UpsertOrderAddressDTO
export interface UpdateOrderAddressDTO extends UpsertOrderAddressDTO {
id: string
}
export interface CreateOrderAddressDTO extends UpsertOrderAddressDTO {}
export type OrderAddressDTO = OrderTypes.OrderAddressDTO

View File

@@ -0,0 +1,18 @@
import { IEventBusModuleService, Logger } from "@medusajs/types"
export * from "./address"
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
eventBusService?: IEventBusModuleService
}

View File

@@ -0,0 +1,9 @@
import { OrderTypes } from "@medusajs/types"
export type CreateOrderLineItemAdjustmentDTO =
OrderTypes.CreateOrderLineItemAdjustmentDTO
export interface UpdateOrderLineItemAdjustmentDTO
extends Partial<CreateOrderLineItemAdjustmentDTO> {
id: string
}

View File

@@ -0,0 +1,9 @@
import { CreateOrderTaxLineDTO, UpdateOrderTaxLineDTO } from "./tax-line"
export interface CreateOrderLineItemTaxLineDTO extends CreateOrderTaxLineDTO {
item_id: string
}
export interface UpdateOrderLineItemTaxLineDTO extends UpdateOrderTaxLineDTO {
item_id: string
}

View File

@@ -0,0 +1,40 @@
import { BigNumberInput } from "@medusajs/types"
interface PartialUpsertOrderLineItemDTO {
subtitle?: string
thumbnail?: string
product_id?: string
product_title?: string
product_description?: string
product_subtitle?: string
product_type?: string
product_collection?: string
product_handle?: string
variant_id?: string
variant_sku?: string
variant_barcode?: string
variant_title?: string
variant_option_values?: Record<string, unknown>
requires_shipping?: boolean
is_discountable?: boolean
is_tax_inclusive?: boolean
compare_at_unit_price?: BigNumberInput
}
export interface CreateOrderLineItemDTO extends PartialUpsertOrderLineItemDTO {
version?: number
title: string
quantity: BigNumberInput
unit_price: BigNumberInput
order_id: string
}
export interface UpdateOrderLineItemDTO
extends PartialUpsertOrderLineItemDTO,
Partial<CreateOrderLineItemDTO> {
id: string
}

View File

@@ -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
}

View File

@@ -0,0 +1,25 @@
import { OrderStatus } from "@medusajs/utils"
export interface CreateOrderDTO {
region_id?: string
customer_id?: string
sales_channel_id?: string
email?: string
currency_code: string
status?: OrderStatus
no_notification?: boolean
metadata?: Record<string, unknown>
}
export interface UpdateOrderDTO {
id: string
version?: number
region_id?: string
customer_id?: string
sales_channel_id?: string
email?: string
currency_code?: string
status?: OrderStatus
no_notification?: boolean
metadata?: Record<string, unknown>
}

View File

@@ -0,0 +1,7 @@
import { OrderTypes } from "@medusajs/types"
export type CreateOrderShippingMethodAdjustmentDTO =
OrderTypes.CreateOrderShippingMethodAdjustmentDTO
export type UpdateOrderShippingMethodAdjustmentDTO =
OrderTypes.UpdateOrderShippingMethodAdjustmentDTO

View File

@@ -0,0 +1,11 @@
import { CreateOrderTaxLineDTO, UpdateOrderTaxLineDTO } from "./tax-line"
export interface CreateOrderShippingMethodTaxLineDTO
extends CreateOrderTaxLineDTO {
shipping_method_id: string
}
export interface UpdateOrderShippingMethodTaxLineDTO
extends UpdateOrderTaxLineDTO {
shipping_method_id: string
}

View File

@@ -0,0 +1,18 @@
import { BigNumberInput } from "@medusajs/types"
export interface CreateOrderShippingMethodDTO {
name: string
shipping_option_id?: string
order_id: string
version?: number
amount: BigNumberInput
data?: Record<string, unknown>
}
export interface UpdateOrderShippingMethodDTO {
id: string
shipping_option_id?: string
name?: string
amount?: BigNumberInput
data?: Record<string, unknown>
}

View File

@@ -0,0 +1,5 @@
import { OrderTypes } from "@medusajs/types"
export type CreateOrderTaxLineDTO = OrderTypes.CreateOrderTaxLineDTO
export type UpdateOrderTaxLineDTO = OrderTypes.UpdateOrderTaxLineDTO

View File

@@ -0,0 +1,3 @@
import { OrderTypes } from "@medusajs/types"
export type OrderTransactionDTO = OrderTypes.OrderTransactionDTO

View File

@@ -0,0 +1,98 @@
import { BigNumberInput } from "@medusajs/types"
export type VirtualOrder = {
items: {
id: string
unit_price: BigNumberInput
quantity: BigNumberInput
detail: {
id?: string
quantity: BigNumberInput
shipped_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>
}
}[]
shipping_methods: {
id: string
price: BigNumberInput
}[]
total: BigNumberInput
metadata?: Record<string, unknown>
}
export enum EVENT_STATUS {
PENDING = "pending",
VOIDED = "voided",
DONE = "done",
}
export interface OrderSummaryCalculated {
currentOrderTotal: BigNumberInput
originalOrderTotal: BigNumberInput
transactionTotal: BigNumberInput
futureDifference: BigNumberInput
pendingDifference: BigNumberInput
futureTemporaryDifference: BigNumberInput
temporaryDifference: BigNumberInput
differenceSum: BigNumberInput
}
export interface OrderTransaction {
amount: BigNumberInput
}
export interface OrderChangeEvent {
action: string
amount?: BigNumberInput
reference?: string
reference_id?: string
group_id?: string
evaluationOnly?: boolean
details?: any
resolve?: {
group_id?: string
reference_id?: string
amount?: BigNumberInput
}
}
export type InternalOrderChangeEvent = OrderChangeEvent & {
status?: EVENT_STATUS
original_?: InternalOrderChangeEvent
}
export type OrderReferences = {
action: InternalOrderChangeEvent
previousEvents?: InternalOrderChangeEvent[]
currentOrder: VirtualOrder
summary: OrderSummaryCalculated
transactions: OrderTransaction[]
type: ActionTypeDefinition
actions: InternalOrderChangeEvent[]
}
export interface ActionTypeDefinition {
isDeduction?: boolean
awaitRequired?: boolean
versioning?: boolean
void?: boolean
commitsAction?: string
operation?: (obj: OrderReferences) => BigNumberInput | void
revert?: (obj: OrderReferences) => BigNumberInput | void
validate?: (obj: OrderReferences) => void
[key: string]: unknown
}

View File

@@ -0,0 +1,13 @@
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",
SHIP_ITEM = "SHIP_ITEM",
WRITE_OFF_ITEM = "WRITE_OFF_ITEM",
}

View File

@@ -0,0 +1,73 @@
import { MathBN, 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.detail.return_requested_quantity ??= 0
existing.detail.return_requested_quantity = MathBN.sub(
existing.detail.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.detail.return_requested_quantity = MathBN.add(
existing.detail.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."
)
}
if (!isDefined(action.amount) && !isDefined(action.details?.unit_price)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Unit price of item ${action.reference_id} is required if no action.amount is provided.`
)
}
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) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Quantity to cancel return of item ${refId} is required.`
)
}
const greater = MathBN.gt(
action.details?.quantity,
existing.detail?.return_requested_quantity
)
if (greater) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cannot cancel more items than what was requested to return for item ${refId}.`
)
}
},
})

View File

@@ -0,0 +1,6 @@
import { ChangeActionType } from "../action-key"
import { OrderChangeProcessing } from "../calculate-order-change"
OrderChangeProcessing.registerActionType(ChangeActionType.CANCEL, {
void: true,
})

View File

@@ -0,0 +1,71 @@
import { MathBN, 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.detail.fulfilled_quantity ??= 0
existing.detail.fulfilled_quantity = MathBN.add(
existing.detail.fulfilled_quantity,
action.details.quantity
)
},
revert({ action, currentOrder }) {
const existing = currentOrder.items.find(
(item) => item.id === action.reference_id
)!
existing.detail.fulfilled_quantity = MathBN.sub(
existing.detail.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) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Quantity to fulfill of item ${refId} is required.`
)
}
if (action.details?.quantity < 1) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Quantity of item ${refId} must be greater than 0.`
)
}
const notFulfilled = MathBN.sub(
existing.quantity,
existing.detail?.fulfilled_quantity
)
const greater = MathBN.gt(action.details?.quantity, notFulfilled)
if (greater) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cannot fulfill more items than what was ordered for item ${refId}.`
)
}
},
})

View File

@@ -0,0 +1,10 @@
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 "./ship-item"
export * from "./shipping-add"

View File

@@ -0,0 +1,79 @@
import { MathBN, 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.detail.quantity ??= 0
existing.quantity = MathBN.add(existing.quantity, action.details.quantity)
existing.detail.quantity = MathBN.add(
existing.detail.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 MathBN.mult(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 = MathBN.sub(existing.quantity, action.details.quantity)
existing.detail.quantity = MathBN.sub(
existing.detail.quantity,
action.details.quantity
)
if (MathBN.lte(existing.quantity, 0)) {
currentOrder.items.splice(existingIndex, 1)
}
}
},
validate({ action }) {
const refId = action.reference_id
if (!isDefined(action.reference_id)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Reference ID is required."
)
}
if (!isDefined(action.amount) && !isDefined(action.details?.unit_price)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Unit price of item ${refId} is required if no action.amount is provided.`
)
}
if (!action.details?.quantity) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Quantity of item ${refId} is required.`
)
}
if (MathBN.lt(action.details?.quantity, 1)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Quantity of item ${refId} must be greater than 0.`
)
}
},
})

View File

@@ -0,0 +1,99 @@
import { MathBN, 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.detail.quantity ??= 0
existing.quantity = MathBN.sub(existing.quantity, action.details.quantity)
existing.detail.quantity = MathBN.sub(
existing.detail.quantity,
action.details.quantity
)
if (MathBN.lte(existing.quantity, 0)) {
currentOrder.items.splice(existingIndex, 1)
}
return MathBN.mult(existing.unit_price, action.details.quantity)
},
revert({ action, currentOrder }) {
const existing = currentOrder.items.find(
(item) => item.id === action.reference_id
)
if (existing) {
existing.quantity = MathBN.add(existing.quantity, action.details.quantity)
existing.detail.quantity = MathBN.add(
existing.detail.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.amount) && !isDefined(action.details?.unit_price)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Unit price of item ${refId} is required if no action.amount is provided.`
)
}
if (!action.details?.quantity) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Quantity of item ${refId} is required.`
)
}
if (MathBN.lt(action.details?.quantity, 1)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Quantity of item ${refId} must be greater than 0.`
)
}
const notFulfilled = MathBN.sub(
existing.quantity,
existing.detail?.fulfilled_quantity
)
const greater = MathBN.gt(action.details?.quantity, notFulfilled)
if (greater) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cannot remove fulfilled item: Item ${refId}.`
)
}
},
})

View File

@@ -0,0 +1,112 @@
import { MathBN, 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.detail.return_dismissed_quantity ??= 0
existing.detail.return_requested_quantity ??= 0
existing.detail.return_dismissed_quantity = MathBN.add(
existing.detail.return_dismissed_quantity,
toReturn
)
existing.detail.return_requested_quantity = MathBN.sub(
existing.detail.return_requested_quantity,
toReturn
)
if (previousEvents) {
for (const previousEvent of previousEvents) {
previousEvent.original_ = JSON.parse(JSON.stringify(previousEvent))
let ret = MathBN.min(toReturn, previousEvent.details.quantity)
toReturn = MathBN.sub(toReturn, ret)
previousEvent.details.quantity = MathBN.sub(
previousEvent.details.quantity,
ret
)
if (MathBN.lte(previousEvent.details.quantity, 0)) {
previousEvent.status = EVENT_STATUS.DONE
}
}
}
return MathBN.mult(existing.unit_price, action.details.quantity)
},
revert({ action, currentOrder, previousEvents }) {
const existing = currentOrder.items.find(
(item) => item.id === action.details.reference_id
)!
existing.detail.return_dismissed_quantity = MathBN.sub(
existing.detail.return_dismissed_quantity,
action.details.quantity
)
existing.detail.return_requested_quantity = MathBN.add(
existing.detail.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.`
)
}
if (!action.details?.quantity) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Quantity to return of item ${refId} is required.`
)
}
const quantityRequested = existing?.detail.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 for item ${refId}.`
)
}
},
}
)

View File

@@ -0,0 +1,119 @@
import {
MathBN,
MedusaError,
isDefined,
transformPropertiesToBigNumber,
} 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.detail.return_received_quantity ??= 0
existing.detail.return_requested_quantity ??= 0
existing.detail.return_received_quantity = MathBN.add(
existing.detail.return_received_quantity,
toReturn
)
existing.detail.return_requested_quantity = MathBN.sub(
existing.detail.return_requested_quantity,
toReturn
)
if (previousEvents) {
for (const previousEvent of previousEvents) {
previousEvent.original_ = JSON.parse(JSON.stringify(previousEvent))
let ret = MathBN.min(toReturn, previousEvent.details.quantity)
toReturn = MathBN.sub(toReturn, ret)
previousEvent.details.quantity = MathBN.sub(
previousEvent.details.quantity,
ret
)
if (MathBN.lte(previousEvent.details.quantity, 0)) {
previousEvent.status = EVENT_STATUS.DONE
}
}
}
return MathBN.mult(existing.unit_price, action.details.quantity)
},
revert({ action, currentOrder, previousEvents }) {
const existing = currentOrder.items.find(
(item) => item.id === action.details.reference_id
)!
existing.detail.return_received_quantity = MathBN.sub(
existing.detail.return_received_quantity,
action.details.quantity
)
existing.detail.return_requested_quantity = MathBN.add(
existing.detail.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)
)
transformPropertiesToBigNumber(previousEvent.details?.metadata)
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.`
)
}
if (!action.details?.quantity) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Quantity to receive return of item ${refId} is required.`
)
}
const quantityRequested = existing?.detail?.return_requested_quantity || 0
const greater = MathBN.gt(action.details?.quantity, quantityRequested)
if (greater) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cannot receive more items than what was requested to be returned for item ${refId}.`
)
}
},
})

View File

@@ -0,0 +1,69 @@
import { MathBN, 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.detail.return_requested_quantity ??= 0
existing.detail.return_requested_quantity = MathBN.add(
existing.detail.return_requested_quantity,
action.details.quantity
)
return MathBN.mult(existing.unit_price, action.details.quantity)
},
revert({ action, currentOrder }) {
const existing = currentOrder.items.find(
(item) => item.id === action.details.reference_id
)!
existing.detail.return_requested_quantity = MathBN.sub(
existing.detail.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.`
)
}
if (!action.details?.quantity) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Quantity to return of item ${refId} is required.`
)
}
const quantityAvailable = MathBN.sub(
existing!.detail?.shipped_quantity ?? 0,
existing!.detail?.return_requested_quantity ?? 0
)
const greater = MathBN.gt(action.details?.quantity, quantityAvailable)
if (greater) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cannot request to return more items than what was shipped for item ${refId}.`
)
}
},
})

View File

@@ -0,0 +1,72 @@
import { MathBN, MedusaError, isDefined } from "@medusajs/utils"
import { ChangeActionType } from "../action-key"
import { OrderChangeProcessing } from "../calculate-order-change"
OrderChangeProcessing.registerActionType(ChangeActionType.SHIP_ITEM, {
operation({ action, currentOrder }) {
const existing = currentOrder.items.find(
(item) => item.id === action.details.reference_id
)!
existing.detail.shipped_quantity ??= 0
existing.detail.shipped_quantity = MathBN.add(
existing.detail.shipped_quantity,
action.details.quantity
)
},
revert({ action, currentOrder }) {
const existing = currentOrder.items.find(
(item) => item.id === action.reference_id
)!
existing.detail.shipped_quantity = MathBN.sub(
existing.detail.shipped_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) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Quantity to ship of item ${refId} is required.`
)
}
if (MathBN.lt(action.details?.quantity, 1)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Quantity of item ${refId} must be greater than 0.`
)
}
const notShipped = MathBN.sub(
existing.detail?.fulfilled_quantity,
existing.detail?.shipped_quantity
)
const greater = MathBN.gt(action.details?.quantity, notShipped)
if (greater) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cannot ship more items than what was fulfilled for item ${refId}.`
)
}
},
})

View File

@@ -0,0 +1,46 @@
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
},
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."
)
}
},
})

View File

@@ -0,0 +1,61 @@
import { MathBN, 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.detail.written_off_quantity ??= 0
existing.detail.written_off_quantity = MathBN.add(
existing.detail.written_off_quantity,
action.details.quantity
)
},
revert({ action, currentOrder }) {
const existing = currentOrder.items.find(
(item) => item.id === action.details.reference_id
)!
existing.detail.written_off_quantity = MathBN.sub(
existing.detail.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.`
)
}
if (!action.details?.quantity) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Quantity to write-off item ${refId} is required.`
)
}
const quantityAvailable = existing!.quantity ?? 0
const greater = MathBN.gt(action.details?.quantity, quantityAvailable)
if (greater) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Cannot claim more items than what was ordered."
)
}
},
})

View File

@@ -0,0 +1,412 @@
import { BigNumberInput, OrderSummaryDTO } from "@medusajs/types"
import {
BigNumber,
MathBN,
isDefined,
transformPropertiesToBigNumber,
} from "@medusajs/utils"
import {
ActionTypeDefinition,
EVENT_STATUS,
InternalOrderChangeEvent,
OrderChangeEvent,
OrderSummaryCalculated,
OrderTransaction,
VirtualOrder,
} from "@types"
type InternalOrderSummary = OrderSummaryCalculated & {
futureTemporarySum: BigNumberInput
}
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, BigNumberInput> = {}
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 = MathBN.add(...transactions.map((tr) => tr.amount))
transformPropertiesToBigNumber(this.order.metadata)
this.summary = {
futureDifference: 0,
futureTemporaryDifference: 0,
temporaryDifference: 0,
pendingDifference: 0,
futureTemporarySum: 0,
differenceSum: 0,
currentOrderTotal: this.order.total ?? 0,
originalOrderTotal: this.order.total ?? 0,
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 = MathBN.mult(action.amount!, type.isDeduction ? -1 : 1)
if (action.group_id && !action.evaluationOnly) {
this.groupTotal[action.group_id] ??= 0
this.groupTotal[action.group_id] = MathBN.add(
this.groupTotal[action.group_id],
amount
)
}
if (type.awaitRequired && !this.isEventDone(action)) {
if (action.evaluationOnly) {
summary.futureTemporarySum = MathBN.add(
summary.futureTemporarySum,
amount
)
} else {
summary.temporaryDifference = MathBN.add(
summary.temporaryDifference,
amount
)
}
}
if (action.evaluationOnly) {
summary.futureDifference = MathBN.add(summary.futureDifference, amount)
} else {
if (!this.isEventDone(action) && !action.group_id) {
summary.differenceSum = MathBN.add(summary.differenceSum, amount)
}
summary.currentOrderTotal = MathBN.add(
summary.currentOrderTotal,
amount
)
}
}
const groupSum = MathBN.add(...Object.values(this.groupTotal))
summary.differenceSum = MathBN.add(summary.differenceSum, groupSum)
summary.transactionTotal = MathBN.sum(
...this.transactions.map((tr) => tr.amount)
)
summary.futureTemporaryDifference = MathBN.sub(
summary.futureDifference,
summary.futureTemporarySum
)
summary.temporaryDifference = MathBN.sub(
summary.differenceSum,
summary.temporaryDifference
)
summary.pendingDifference = MathBN.sub(
summary.currentOrderTotal,
summary.transactionTotal
)
}
private processAction_(
action: InternalOrderChangeEvent,
isReplay = false
): BigNumberInput | 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 = 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 BigNumberInput
// the action.amount has priority over the calculated amount
if (!isDefined(action.amount)) {
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] = MathBN.sub(
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(): OrderSummaryDTO {
const summary = this.summary
const orderSummary = {
transactionTotal: new BigNumber(summary.transactionTotal),
originalOrderTotal: new BigNumber(summary.originalOrderTotal),
currentOrderTotal: new BigNumber(summary.currentOrderTotal),
temporaryDifference: new BigNumber(summary.temporaryDifference),
futureDifference: new BigNumber(summary.futureDifference),
futureTemporaryDifference: new BigNumber(
summary.futureTemporaryDifference
),
pendingDifference: new BigNumber(summary.pendingDifference),
differenceSum: new BigNumber(summary.differenceSum),
} as unknown as OrderSummaryDTO
/*
{
total: summary.currentOrderTotal
subtotal: number
total_tax: number
ordered_total: summary.originalOrderTotal
fulfilled_total: number
returned_total: number
return_request_total: number
write_off_total: number
projected_total: number
net_total: number
net_subtotal: number
net_total_tax: number
future_total: number
future_subtotal: number
future_total_tax: number
future_projected_total: number
balance: summary.pendingDifference
future_balance: number
}
*/
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(),
}
}

View File

@@ -0,0 +1,3 @@
export * from "./action-key"
export * from "./actions"
export * from "./calculate-order-change"

View File

@@ -0,0 +1,123 @@
import { OrderTypes } from "@medusajs/types"
import {
createRawPropertiesFromBigNumber,
decorateCartTotals,
deduplicate,
isDefined,
} from "@medusajs/utils"
export function formatOrder(
order,
options: {
includeTotals?: boolean
}
): 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,
}
})
order.shipping_methods = order.shipping_methods?.map((shippingMethod) => {
const sm = { ...shippingMethod.shipping_method }
delete shippingMethod.shipping_method
return {
...sm,
order_id: shippingMethod.order_id,
detail: {
...shippingMethod,
},
}
})
order.summary = order.summary?.[0]?.totals
return options?.includeTotals
? createRawPropertiesFromBigNumber(decorateCartTotals(order))
: 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 deduplicate(
obj[type].sort().map((rel) => {
if (rel == "items.quantity") {
if (type === "fields") {
obj.populate.push("items.item")
}
return "items.item.quantity"
}
if (rel == "summary" && type === "fields") {
obj.populate.push("summary")
return "summary.totals"
} else if (
rel.includes("shipping_methods") &&
!rel.includes("shipping_methods.shipping_method")
) {
obj.populate.push("shipping_methods.shipping_method")
return rel.replace(
"shipping_methods",
"shipping_methods.shipping_method"
)
} else if (rel.includes("items.detail")) {
return rel.replace("items.detail", "items")
} else if (rel == "items") {
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
}