From f2eb942b41d002ddfb0b3df12171a6e5e35d9697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frane=20Poli=C4=87?= <16856471+fPolic@users.noreply.github.com> Date: Tue, 13 Sep 2022 18:40:21 +0200 Subject: [PATCH] feat: order editing data model (#2184) **What** - add order editing entities - add repositories - add a feature flag for the order editing feature - add the migrations file RESOLVES CORE-490 --- .../loaders/feature-flags/order-editing.ts | 10 ++ .../migrations/1663059812399-order_editing.ts | 53 ++++++ packages/medusa/src/models/order-edit.ts | 157 ++++++++++++++++++ .../medusa/src/models/order-item-change.ts | 102 ++++++++++++ packages/medusa/src/models/order.ts | 23 ++- .../medusa/src/repositories/order-edit.ts | 6 + .../src/repositories/order-item-change.ts | 6 + 7 files changed, 353 insertions(+), 4 deletions(-) create mode 100644 packages/medusa/src/loaders/feature-flags/order-editing.ts create mode 100644 packages/medusa/src/migrations/1663059812399-order_editing.ts create mode 100644 packages/medusa/src/models/order-edit.ts create mode 100644 packages/medusa/src/models/order-item-change.ts create mode 100644 packages/medusa/src/repositories/order-edit.ts create mode 100644 packages/medusa/src/repositories/order-item-change.ts diff --git a/packages/medusa/src/loaders/feature-flags/order-editing.ts b/packages/medusa/src/loaders/feature-flags/order-editing.ts new file mode 100644 index 0000000000..2e6601fc4b --- /dev/null +++ b/packages/medusa/src/loaders/feature-flags/order-editing.ts @@ -0,0 +1,10 @@ +import { FlagSettings } from "../../types/feature-flags" + +const OrderEditingFeatureFlag: FlagSettings = { + key: "order_editing", + default_val: false, + env_key: "MEDUSA_FF_ORDER_EDITING", + description: "[WIP] Enable the order editing feature", +} + +export default OrderEditingFeatureFlag diff --git a/packages/medusa/src/migrations/1663059812399-order_editing.ts b/packages/medusa/src/migrations/1663059812399-order_editing.ts new file mode 100644 index 0000000000..a5dfbbcc9f --- /dev/null +++ b/packages/medusa/src/migrations/1663059812399-order_editing.ts @@ -0,0 +1,53 @@ +import { MigrationInterface, QueryRunner } from "typeorm" + +import OrderEditingFeatureFlag from "../loaders/feature-flags/order-editing" + +export const featureFlag = OrderEditingFeatureFlag.key + +export class orderEditing1663059812399 implements MigrationInterface { + name = "orderEditing1663059812399" + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "order_item_change_type_enum" AS ENUM('item_add', 'item_remove', 'item_update')` + ) + await queryRunner.query( + `CREATE TABLE "order_item_change" ("id" character varying NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP WITH TIME ZONE, "type" "order_item_change_type_enum" NOT NULL, "order_edit_id" character varying NOT NULL, "original_line_item_id" character varying, "line_item_id" character varying, CONSTRAINT "UQ_da93cee3ca0dd50a5246268c2e9" UNIQUE ("order_edit_id", "line_item_id"), CONSTRAINT "UQ_5b7a99181e4db2ea821be0b6196" UNIQUE ("order_edit_id", "original_line_item_id"), CONSTRAINT "REL_5f9688929761f7df108b630e64" UNIQUE ("line_item_id"), CONSTRAINT "PK_d6eb138f77ffdee83567b85af0c" PRIMARY KEY ("id"))` + ) + await queryRunner.query( + `CREATE TABLE "order_edit" ("id" character varying NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP WITH TIME ZONE, "order_id" character varying NOT NULL, "internal_note" character varying, "created_by" character varying NOT NULL, "requested_by" character varying, "requested_at" TIMESTAMP WITH TIME ZONE, "confirmed_by" character varying, "confirmed_at" TIMESTAMP WITH TIME ZONE, "declined_by" character varying, "declined_reason" character varying, "declined_at" TIMESTAMP WITH TIME ZONE, "canceled_by" character varying, "canceled_at" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_58ab6acf2e84b4e827f5f846f7a" PRIMARY KEY ("id"))` + ) + + await queryRunner.query( + `ALTER TABLE "order_item_change" ADD CONSTRAINT "FK_44feeebb258bf4cfa4cc4202281" FOREIGN KEY ("order_edit_id") REFERENCES "order_edit"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "order_item_change" ADD CONSTRAINT "FK_b4d53b8d03c9f5e7d4317e818d9" FOREIGN KEY ("original_line_item_id") REFERENCES "line_item"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "order_item_change" ADD CONSTRAINT "FK_5f9688929761f7df108b630e64a" FOREIGN KEY ("line_item_id") REFERENCES "line_item"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "order_edit" ADD CONSTRAINT "FK_1f3a251488a91510f57e1bf93cd" FOREIGN KEY ("order_id") REFERENCES "order"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "order_edit" DROP CONSTRAINT "FK_1f3a251488a91510f57e1bf93cd"` + ) + await queryRunner.query( + `ALTER TABLE "order_item_change" DROP CONSTRAINT "FK_5f9688929761f7df108b630e64a"` + ) + await queryRunner.query( + `ALTER TABLE "order_item_change" DROP CONSTRAINT "FK_b4d53b8d03c9f5e7d4317e818d9"` + ) + await queryRunner.query( + `ALTER TABLE "order_item_change" DROP CONSTRAINT "FK_44feeebb258bf4cfa4cc4202281"` + ) + + await queryRunner.query(`DROP TABLE "order_edit"`) + await queryRunner.query(`DROP TABLE "order_item_change"`) + await queryRunner.query(`DROP TYPE "order_item_change_type_enum"`) + } +} diff --git a/packages/medusa/src/models/order-edit.ts b/packages/medusa/src/models/order-edit.ts new file mode 100644 index 0000000000..9150477a6f --- /dev/null +++ b/packages/medusa/src/models/order-edit.ts @@ -0,0 +1,157 @@ +import { BeforeInsert, Column, JoinColumn, ManyToOne, OneToMany } from "typeorm" + +import OrderEditingFeatureFlag from "../loaders/feature-flags/order-editing" +import { FeatureFlagEntity } from "../utils/feature-flag-decorators" +import { resolveDbType } from "../utils/db-aware-column" +import { OrderItemChange } from "./order-item-change" +import { SoftDeletableEntity } from "../interfaces" +import { generateEntityId } from "../utils" +import { LineItem } from "./line-item" +import { Order } from "./order" + +@FeatureFlagEntity(OrderEditingFeatureFlag.key) +export class OrderEdit extends SoftDeletableEntity { + @Column() + order_id: string + + @ManyToOne(() => Order, (o) => o.edits) + @JoinColumn({ name: "order_id" }) + order: Order + + @OneToMany(() => OrderItemChange, (oic) => oic.order_edit, { + cascade: true, + }) + changes: OrderItemChange[] + + @Column({ nullable: true }) + internal_note?: string + + @Column() + created_by: string // customer or user ID + + @Column({ nullable: true }) + requested_by?: string // customer or user ID + + @Column({ type: resolveDbType("timestamptz"), nullable: true }) + requested_at?: Date + + @Column({ nullable: true }) + confirmed_by?: string // customer or user ID + + @Column({ type: resolveDbType("timestamptz"), nullable: true }) + confirmed_at?: Date + + @Column({ nullable: true }) + declined_by?: string // customer or user ID + + @Column({ nullable: true }) + declined_reason?: string + + @Column({ type: resolveDbType("timestamptz"), nullable: true }) + declined_at?: Date + + @Column({ nullable: true }) + canceled_by?: string + + @Column({ type: resolveDbType("timestamptz"), nullable: true }) + canceled_at?: Date + + // Computed + subtotal: number + discount_total?: number + tax_total: number + total: number + difference_due: number + + items: LineItem[] + + @BeforeInsert() + private beforeInsert(): void { + this.id = generateEntityId(this.id, "oe") + } +} + +/** + * @schema order_edit + * title: "Order Edit" + * description: "Order edit keeps track of order items changes." + * x-resourceId: order_edit + * required: + * - order_id + * - order + * - changes + * - created_by + * properties: + * id: + * type: string + * description: The order edit's ID + * example: oe_01G8TJSYT9M6AVS5N4EMNFS1EK + * order_id: + * type: string + * description: The ID of the order that is edited + * example: order_01G2SG30J8C85S4A5CHM2S1NS2 + * order: + * description: Order object + * $ref: "#/components/schemas/order" + * changes: + * type: array + * description: Line item changes array. + * items: + * $ref: "#/components/schemas/order_item_changes" + * internal_note: + * description: "An optional note with additional details about the order edit." + * type: string + * example: Included two more items B to the order. + * created_by: + * type: string + * description: "The unique identifier of the user or customer who created the order edit." + * requested_by: + * type: string + * description: "The unique identifier of the user or customer who requested the order edit." + * requested_at: + * type: string + * description: "The date with timezone at which the edit was requested." + * format: date-time + * confirmed_by: + * type: string + * description: "The unique identifier of the user or customer who confirmed the order edit." + * confirmed_at: + * type: string + * description: "The date with timezone at which the edit was confirmed." + * format: date-time + * declined_by: + * type: string + * description: "The unique identifier of the user or customer who declined the order edit." + * declined_at: + * type: string + * description: "The date with timezone at which the edit was declined." + * format: date-time + * declined_reason: + * description: "An optional note why the order edit is declined." + * type: string + * subtotal: + * type: integer + * description: The subtotal for line items computed from changes. + * example: 8000 + * discount_total: + * type: integer + * description: The total of discount + * example: 800 + * tax_total: + * type: integer + * description: The total of tax + * example: 0 + * total: + * type: integer + * description: The total amount of the edited order. + * example: 8200 + * difference_due: + * type: integer + * description: The difference between the total amount of the order and total amount of edited order. + * example: 8200 + * items: + * type: array + * description: Computed line items from the changes. + * items: + * $ref: "#/components/schemas/line_item" + */ diff --git a/packages/medusa/src/models/order-item-change.ts b/packages/medusa/src/models/order-item-change.ts new file mode 100644 index 0000000000..3205377922 --- /dev/null +++ b/packages/medusa/src/models/order-item-change.ts @@ -0,0 +1,102 @@ +import { + BeforeInsert, + Column, + JoinColumn, + ManyToOne, + OneToOne, + Unique, +} from "typeorm" + +import { SoftDeletableEntity } from "../interfaces" +import OrderEditingFeatureFlag from "../loaders/feature-flags/order-editing" +import { FeatureFlagEntity } from "../utils/feature-flag-decorators" +import { generateEntityId } from "../utils" +import { DbAwareColumn } from "../utils/db-aware-column" +import { OrderEdit } from "./order-edit" +import { LineItem } from "./line-item" + +export enum OrderEditItemChangeType { + ITEM_ADD = "item_add", + ITEM_REMOVE = "item_remove", + ITEM_UPDATE = "item_update", +} + +@FeatureFlagEntity(OrderEditingFeatureFlag.key) +@Unique(["order_edit_id", "original_line_item_id"]) +@Unique(["order_edit_id", "line_item_id"]) +export class OrderItemChange extends SoftDeletableEntity { + @DbAwareColumn({ + type: "enum", + enum: OrderEditItemChangeType, + }) + type: OrderEditItemChangeType + + @Column() + order_edit_id: string + + @ManyToOne(() => OrderEdit, (oe) => oe.changes) + @JoinColumn({ name: "order_edit_id" }) + order_edit: OrderEdit + + @Column({ nullable: true }) + original_line_item_id?: string + + @ManyToOne(() => LineItem, { nullable: true }) + @JoinColumn({ name: "original_line_item_id" }) + original_line_item?: LineItem + + @Column({ nullable: true }) + line_item_id?: string + + @OneToOne(() => LineItem, { nullable: true }) + @JoinColumn({ name: "line_item_id" }) + line_item?: LineItem + + @BeforeInsert() + private beforeInsert(): void { + this.id = generateEntityId(this.id, "oic") + } +} + +/** + * @schema order_item_change + * title: "Order Item Change" + * description: "Represents an order edit item change" + * x-resourceId: order_item_change + * required: + * - type + * - order_edit_id + * properties: + * id: + * type: string + * description: The order item change's ID + * example: oic_01G8TJSYT9M6AVS5N4EMNFS1EK + * type: + * type: string + * description: The order's status + * enum: + * - item_add + * - item_remove + * - item_update + * order_edit_id: + * type: string + * description: The ID of the order edit + * example: oe_01G2SG30J8C85S4A5CHM2S1NS2 + * order_edit: + * description: Order edit object + * $ref: "#/components/schemas/order_edit" + * original_line_item_id: + * type: string + * description: The ID of the original line item in the order + * example: item_01G8ZC9GWT6B2GP5FSXRXNFNGN + * original_line_item: + * description: Original line item object. + * $ref: "#/components/schemas/line_item" + * line_item_id: + * type: string + * description: The ID of the cloned line item. + * example: item_01G8ZC9GWT6B2GP5FSXRXNFNGN + * line_item: + * description: Line item object. + * $ref: "#/components/schemas/line_item" + */ diff --git a/packages/medusa/src/models/order.ts b/packages/medusa/src/models/order.ts index 39c17a2a39..dd7771a075 100644 --- a/packages/medusa/src/models/order.ts +++ b/packages/medusa/src/models/order.ts @@ -42,6 +42,8 @@ import { ShippingMethod } from "./shipping-method" import { Swap } from "./swap" import { generateEntityId } from "../utils/generate-entity-id" import { manualAutoIncrement } from "../utils/manual-auto-increment" +import { OrderEdit } from "./order-edit" +import OrderEditingFeatureFlag from "../loaders/feature-flags/order-editing" export enum OrderStatus { PENDING = "pending", @@ -208,6 +210,14 @@ export class Order extends BaseEntity { @JoinColumn({ name: "draft_order_id" }) draft_order: DraftOrder + @FeatureFlagDecorators(OrderEditingFeatureFlag.key, [ + OneToMany( + () => OrderEdit, + (oe) => oe.order + ), + ]) + edits: OrderEdit[] + @OneToMany(() => LineItem, (lineItem) => lineItem.order, { cascade: ["insert"], }) @@ -406,25 +416,25 @@ export class Order extends BaseEntity { * description: The returns associated with the order. Available if the relation `returns` is expanded. * items: * type: object - * description: A return object. + * description: A return object. * claims: * type: array * description: The claims associated with the order. Available if the relation `claims` is expanded. * items: * type: object - * description: A claim order object. + * description: A claim order object. * refunds: * type: array * description: The refunds associated with the order. Available if the relation `refunds` is expanded. * items: * type: object - * description: A refund object. + * description: A refund object. * swaps: * type: array * description: The swaps associated with the order. Available if the relation `swaps` is expanded. * items: * type: object - * description: A swap object. + * description: A swap object. * draft_order_id: * type: string * description: The ID of the draft order this order is associated with. @@ -437,6 +447,11 @@ export class Order extends BaseEntity { * description: The line items that belong to the order. Available if the relation `items` is expanded. * items: * $ref: "#/components/schemas/line_item" + * edits: + * type: array + * description: [EXPERIMENTAL] Order edits done on the order. Available if the relation `edits` is expanded. + * items: + * $ref: "#/components/schemas/order_edit" * gift_card_transactions: * type: array * description: The gift card transactions used in the order. Available if the relation `gift_card_transactions` is expanded. diff --git a/packages/medusa/src/repositories/order-edit.ts b/packages/medusa/src/repositories/order-edit.ts new file mode 100644 index 0000000000..84187e7b39 --- /dev/null +++ b/packages/medusa/src/repositories/order-edit.ts @@ -0,0 +1,6 @@ +import { EntityRepository, Repository } from "typeorm" + +import { OrderEdit } from "../models/order-edit" + +@EntityRepository(OrderEdit) +export class OrderEditRepository extends Repository {} diff --git a/packages/medusa/src/repositories/order-item-change.ts b/packages/medusa/src/repositories/order-item-change.ts new file mode 100644 index 0000000000..ea78b61e48 --- /dev/null +++ b/packages/medusa/src/repositories/order-item-change.ts @@ -0,0 +1,6 @@ +import { EntityRepository, Repository } from "typeorm" + +import { OrderItemChange } from "../models/order-item-change" + +@EntityRepository(OrderItemChange) +export class OrderItemChangeRepository extends Repository {}